diff --git a/.codex/config.toml b/.codex/config.toml new file mode 100644 index 000000000..4a39b9307 --- /dev/null +++ b/.codex/config.toml @@ -0,0 +1,6 @@ +sandbox_mode = "danger-full-access" +[mcp_servers.context7] +url = "https://mcp.context7.com/mcp" + +[sandbox_workspace_write] +network_access = true diff --git a/.codex/skills/chat2db-dev-server/SKILL.md b/.codex/skills/chat2db-dev-server/SKILL.md new file mode 100644 index 000000000..ee0f98b0b --- /dev/null +++ b/.codex/skills/chat2db-dev-server/SKILL.md @@ -0,0 +1,63 @@ +--- +name: chat2db-dev-server +description: Start, restart, stop, and inspect the local Chat2DB development servers in E:\workspace\Chat2DB. Use when the user asks to 启动/重启/打开/检查 Chat2DB 前端后端, restart backend after Java changes, restart frontend after React changes, verify ports/logs, or fix dev-server startup issues for this workspace. +--- + +# Chat2DB Dev Server + +## Quick Start + +Use `scripts/manage-chat2db-dev.ps1` for repeatable server operations. + +```powershell +# Start both frontend and backend. +& "E:\workspace\Chat2DB\.codex\skills\chat2db-dev-server\scripts\manage-chat2db-dev.ps1" -Action start + +# Restart both. Install backend dependency modules first when Java backend modules changed. +& "E:\workspace\Chat2DB\.codex\skills\chat2db-dev-server\scripts\manage-chat2db-dev.ps1" -Action restart -InstallBackendDeps + +# Check listening ports and log tails. +& "E:\workspace\Chat2DB\.codex\skills\chat2db-dev-server\scripts\manage-chat2db-dev.ps1" -Action status +``` + +This is a project-local skill. Keep it under `E:\workspace\Chat2DB\.codex\skills`, not under the global user skills directory. + +## Workspace Defaults + +- Workspace: `E:\workspace\Chat2DB` +- Frontend directory: `chat2db-client` +- Backend directory: `chat2db-server` +- Frontend URL: `http://localhost:8000` +- Backend URL: `http://localhost:10821` +- Logs: `E:\workspace\Chat2DB\logs\frontend.log` and `E:\workspace\Chat2DB\logs\backend.log` +- Java: set `$env:JAVA_HOME = "D:\tool\Java\jdk-17"` before Maven commands. +- Frontend package manager: use `D:\nvm4w\nodejs\yarn.cmd`; do not use npm. + +## Backend Restart Rule + +When backend code changed outside `chat2db-server-start`, run the script with `-InstallBackendDeps` before restart. This installs the modules that `chat2db-server-start` consumes so `spring-boot:run -pl chat2db-server-start` does not load stale local Maven artifacts. + +This is required after changes in: + +- `chat2db-server-domain-api` +- `chat2db-server-domain-core` +- `chat2db-server-domain-repository` +- `chat2db-server-web-api` + +## Verification + +After starting or restarting: + +1. Confirm ports `8000` and `10821` are listening. +2. Check backend log for `Started Chat2dbLiteApplication` and `[Startup] Chat2dbLiteApplication started successfully`. +3. Check frontend log for `App listening at` and `Local: http://localhost:8000`. +4. If an endpoint was added, verify it without mutating data when possible, for example: + +```powershell +Invoke-WebRequest -Uri 'http://localhost:10821/api/task/cleanup' -Method Options -UseBasicParsing +``` + +## Notes + +- Early frontend proxy `ECONNREFUSED` entries can appear while the backend is still starting; treat them as transient if backend later starts and `/api` calls succeed. +- If a requested endpoint returns `NoHandlerFoundException`, install backend dependencies with `-InstallBackendDeps` and restart backend again. diff --git a/.codex/skills/chat2db-dev-server/agents/openai.yaml b/.codex/skills/chat2db-dev-server/agents/openai.yaml new file mode 100644 index 000000000..6511529e7 --- /dev/null +++ b/.codex/skills/chat2db-dev-server/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Chat2DB Dev Server" + short_description: "Manage Chat2DB dev servers." + default_prompt: "Start or restart the Chat2DB frontend and backend, verify ports and logs, and report the URLs." diff --git a/.codex/skills/chat2db-dev-server/scripts/manage-chat2db-dev.ps1 b/.codex/skills/chat2db-dev-server/scripts/manage-chat2db-dev.ps1 new file mode 100644 index 000000000..4eca06f7c --- /dev/null +++ b/.codex/skills/chat2db-dev-server/scripts/manage-chat2db-dev.ps1 @@ -0,0 +1,101 @@ +param( + [ValidateSet("start", "restart", "stop", "status")] + [string]$Action = "status", + + [ValidateSet("all", "frontend", "backend")] + [string]$Component = "all", + + [string]$Workspace = "E:\workspace\Chat2DB", + + [string]$JavaHome = "D:\tool\Java\jdk-17", + + [string]$YarnCmd = "D:\nvm4w\nodejs\yarn.cmd", + + [switch]$InstallBackendDeps +) + +$ErrorActionPreference = "Stop" + +$logDir = Join-Path $Workspace "logs" +$frontendLog = Join-Path $logDir "frontend.log" +$backendLog = Join-Path $logDir "backend.log" +$frontendPort = 8000 +$backendPort = 10821 + +function Ensure-LogDir { + New-Item -ItemType Directory -Force -Path $logDir | Out-Null +} + +function Stop-Port { + param([int]$Port) + Get-NetTCPConnection -LocalPort $Port -State Listen -ErrorAction SilentlyContinue | + ForEach-Object { + Stop-Process -Id $_.OwningProcess -Force -ErrorAction SilentlyContinue + } +} + +function Install-BackendDependencies { + $env:JAVA_HOME = $JavaHome + Push-Location (Join-Path $Workspace "chat2db-server") + try { + mvn -pl chat2db-server-web/chat2db-server-web-api,chat2db-server-domain/chat2db-server-domain-core -am -DskipTests install + } finally { + Pop-Location + } +} + +function Start-Backend { + Ensure-LogDir + $command = "Set-Location '$Workspace\chat2db-server'; " + + "`$env:JAVA_HOME='$JavaHome'; " + + "mvn spring-boot:run -pl chat2db-server-start *> '$backendLog'" + Start-Process powershell.exe -WindowStyle Hidden -PassThru -ArgumentList @("-NoProfile", "-Command", $command) +} + +function Start-Frontend { + Ensure-LogDir + $command = "Set-Location '$Workspace\chat2db-client'; " + + "& '$YarnCmd' run start:web *> '$frontendLog'" + Start-Process powershell.exe -WindowStyle Hidden -PassThru -ArgumentList @("-NoProfile", "-Command", $command) +} + +function Show-Status { + $ports = @($frontendPort, $backendPort) + Get-NetTCPConnection -LocalPort $ports -State Listen -ErrorAction SilentlyContinue | + Select-Object LocalAddress, LocalPort, State, OwningProcess + + foreach ($log in @($backendLog, $frontendLog)) { + if (Test-Path $log) { + Write-Host "" + Write-Host "== $log ==" + Get-Content $log -Tail 20 + } + } +} + +if ($Action -in @("restart", "stop")) { + if ($Component -in @("all", "frontend")) { + Stop-Port -Port $frontendPort + } + if ($Component -in @("all", "backend")) { + Stop-Port -Port $backendPort + } + Start-Sleep -Seconds 2 +} + +if ($Action -in @("start", "restart")) { + if (($Component -in @("all", "backend")) -and $InstallBackendDeps) { + Install-BackendDependencies + } + if ($Component -in @("all", "backend")) { + $backend = Start-Backend + Write-Host "backendPid=$($backend.Id)" + } + if ($Component -in @("all", "frontend")) { + $frontend = Start-Frontend + Write-Host "frontendPid=$($frontend.Id)" + } + Start-Sleep -Seconds 12 +} + +Show-Status diff --git a/.gitignore b/.gitignore index f8a263aaa..e415c2575 100644 --- a/.gitignore +++ b/.gitignore @@ -28,4 +28,8 @@ package-lock.json /chat2db-server/ali-dbhub-server-domain/ali-dbhub-server-domain-support/src/main/resources/lib/* /chat2db-server/ali-dbhub-server-domain/ali-dbhub-server-domain-support/lib/* /lib -/out/* \ No newline at end of file +/out/* +/chat2db-gateway/target +.playwright-mcp +*.png +logs diff --git a/.opencode/opencode.json b/.opencode/opencode.json new file mode 100644 index 000000000..17ec01caa --- /dev/null +++ b/.opencode/opencode.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://opencode.ai/config.json", + "lsp":true, + "mcp": { + "browsermcp": { + "type": "local", + "command": ["npx","@browsermcp/mcp@latest"], + "enabled": true + } + } +} diff --git a/.opencode/skills/build/SKILL.md b/.opencode/skills/build/SKILL.md new file mode 100644 index 000000000..f6e8a571f --- /dev/null +++ b/.opencode/skills/build/SKILL.md @@ -0,0 +1,51 @@ +--- +name: build +description: 编译当前项目 +license: MIT +compatibility: opencode +--- + +## Build Backend JAR + +编译后端 JAR 包: + +```powershell +$env:JAVA_HOME="D:\tool\Java\jdk-17"; cd chat2db-server; mvn clean package -DskipTests +``` + +生成的 JAR 文件: +- `chat2db-server/chat2db-server-start/target/chat2db-server-start.jar` + +运行方式: +```powershell +java -jar chat2db-server/chat2db-server-start/target/chat2db-server-start.jar +``` + +## Build Backend (Compile Only) + +仅编译不打包: + +```powershell +$env:JAVA_HOME="D:\tool\Java\jdk-17"; cd chat2db-server; mvn clean compile -DskipTests +``` + +## Build Frontend + +编译前端: + +```powershell +cd chat2db-client; yarn install; yarn run build:web +``` + +开发模式运行前端: +```powershell +cd chat2db-client; yarn install; yarn run start:web +``` + +```powershell +# Requirements +# - Java 17 (设置 JAVA_HOME 环境变量) +# - Node.js 16+ +# - Maven 3.6.1+ +# - Yarn 4.x +``` \ No newline at end of file diff --git a/.opencode/skills/chat2db-dev-server/SKILL.md b/.opencode/skills/chat2db-dev-server/SKILL.md new file mode 100644 index 000000000..ee0f98b0b --- /dev/null +++ b/.opencode/skills/chat2db-dev-server/SKILL.md @@ -0,0 +1,63 @@ +--- +name: chat2db-dev-server +description: Start, restart, stop, and inspect the local Chat2DB development servers in E:\workspace\Chat2DB. Use when the user asks to 启动/重启/打开/检查 Chat2DB 前端后端, restart backend after Java changes, restart frontend after React changes, verify ports/logs, or fix dev-server startup issues for this workspace. +--- + +# Chat2DB Dev Server + +## Quick Start + +Use `scripts/manage-chat2db-dev.ps1` for repeatable server operations. + +```powershell +# Start both frontend and backend. +& "E:\workspace\Chat2DB\.codex\skills\chat2db-dev-server\scripts\manage-chat2db-dev.ps1" -Action start + +# Restart both. Install backend dependency modules first when Java backend modules changed. +& "E:\workspace\Chat2DB\.codex\skills\chat2db-dev-server\scripts\manage-chat2db-dev.ps1" -Action restart -InstallBackendDeps + +# Check listening ports and log tails. +& "E:\workspace\Chat2DB\.codex\skills\chat2db-dev-server\scripts\manage-chat2db-dev.ps1" -Action status +``` + +This is a project-local skill. Keep it under `E:\workspace\Chat2DB\.codex\skills`, not under the global user skills directory. + +## Workspace Defaults + +- Workspace: `E:\workspace\Chat2DB` +- Frontend directory: `chat2db-client` +- Backend directory: `chat2db-server` +- Frontend URL: `http://localhost:8000` +- Backend URL: `http://localhost:10821` +- Logs: `E:\workspace\Chat2DB\logs\frontend.log` and `E:\workspace\Chat2DB\logs\backend.log` +- Java: set `$env:JAVA_HOME = "D:\tool\Java\jdk-17"` before Maven commands. +- Frontend package manager: use `D:\nvm4w\nodejs\yarn.cmd`; do not use npm. + +## Backend Restart Rule + +When backend code changed outside `chat2db-server-start`, run the script with `-InstallBackendDeps` before restart. This installs the modules that `chat2db-server-start` consumes so `spring-boot:run -pl chat2db-server-start` does not load stale local Maven artifacts. + +This is required after changes in: + +- `chat2db-server-domain-api` +- `chat2db-server-domain-core` +- `chat2db-server-domain-repository` +- `chat2db-server-web-api` + +## Verification + +After starting or restarting: + +1. Confirm ports `8000` and `10821` are listening. +2. Check backend log for `Started Chat2dbLiteApplication` and `[Startup] Chat2dbLiteApplication started successfully`. +3. Check frontend log for `App listening at` and `Local: http://localhost:8000`. +4. If an endpoint was added, verify it without mutating data when possible, for example: + +```powershell +Invoke-WebRequest -Uri 'http://localhost:10821/api/task/cleanup' -Method Options -UseBasicParsing +``` + +## Notes + +- Early frontend proxy `ECONNREFUSED` entries can appear while the backend is still starting; treat them as transient if backend later starts and `/api` calls succeed. +- If a requested endpoint returns `NoHandlerFoundException`, install backend dependencies with `-InstallBackendDeps` and restart backend again. diff --git a/.opencode/skills/chat2db-dev-server/agents/openai.yaml b/.opencode/skills/chat2db-dev-server/agents/openai.yaml new file mode 100644 index 000000000..6511529e7 --- /dev/null +++ b/.opencode/skills/chat2db-dev-server/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Chat2DB Dev Server" + short_description: "Manage Chat2DB dev servers." + default_prompt: "Start or restart the Chat2DB frontend and backend, verify ports and logs, and report the URLs." diff --git a/.opencode/skills/chat2db-dev-server/scripts/manage-chat2db-dev.ps1 b/.opencode/skills/chat2db-dev-server/scripts/manage-chat2db-dev.ps1 new file mode 100644 index 000000000..4eca06f7c --- /dev/null +++ b/.opencode/skills/chat2db-dev-server/scripts/manage-chat2db-dev.ps1 @@ -0,0 +1,101 @@ +param( + [ValidateSet("start", "restart", "stop", "status")] + [string]$Action = "status", + + [ValidateSet("all", "frontend", "backend")] + [string]$Component = "all", + + [string]$Workspace = "E:\workspace\Chat2DB", + + [string]$JavaHome = "D:\tool\Java\jdk-17", + + [string]$YarnCmd = "D:\nvm4w\nodejs\yarn.cmd", + + [switch]$InstallBackendDeps +) + +$ErrorActionPreference = "Stop" + +$logDir = Join-Path $Workspace "logs" +$frontendLog = Join-Path $logDir "frontend.log" +$backendLog = Join-Path $logDir "backend.log" +$frontendPort = 8000 +$backendPort = 10821 + +function Ensure-LogDir { + New-Item -ItemType Directory -Force -Path $logDir | Out-Null +} + +function Stop-Port { + param([int]$Port) + Get-NetTCPConnection -LocalPort $Port -State Listen -ErrorAction SilentlyContinue | + ForEach-Object { + Stop-Process -Id $_.OwningProcess -Force -ErrorAction SilentlyContinue + } +} + +function Install-BackendDependencies { + $env:JAVA_HOME = $JavaHome + Push-Location (Join-Path $Workspace "chat2db-server") + try { + mvn -pl chat2db-server-web/chat2db-server-web-api,chat2db-server-domain/chat2db-server-domain-core -am -DskipTests install + } finally { + Pop-Location + } +} + +function Start-Backend { + Ensure-LogDir + $command = "Set-Location '$Workspace\chat2db-server'; " + + "`$env:JAVA_HOME='$JavaHome'; " + + "mvn spring-boot:run -pl chat2db-server-start *> '$backendLog'" + Start-Process powershell.exe -WindowStyle Hidden -PassThru -ArgumentList @("-NoProfile", "-Command", $command) +} + +function Start-Frontend { + Ensure-LogDir + $command = "Set-Location '$Workspace\chat2db-client'; " + + "& '$YarnCmd' run start:web *> '$frontendLog'" + Start-Process powershell.exe -WindowStyle Hidden -PassThru -ArgumentList @("-NoProfile", "-Command", $command) +} + +function Show-Status { + $ports = @($frontendPort, $backendPort) + Get-NetTCPConnection -LocalPort $ports -State Listen -ErrorAction SilentlyContinue | + Select-Object LocalAddress, LocalPort, State, OwningProcess + + foreach ($log in @($backendLog, $frontendLog)) { + if (Test-Path $log) { + Write-Host "" + Write-Host "== $log ==" + Get-Content $log -Tail 20 + } + } +} + +if ($Action -in @("restart", "stop")) { + if ($Component -in @("all", "frontend")) { + Stop-Port -Port $frontendPort + } + if ($Component -in @("all", "backend")) { + Stop-Port -Port $backendPort + } + Start-Sleep -Seconds 2 +} + +if ($Action -in @("start", "restart")) { + if (($Component -in @("all", "backend")) -and $InstallBackendDeps) { + Install-BackendDependencies + } + if ($Component -in @("all", "backend")) { + $backend = Start-Backend + Write-Host "backendPid=$($backend.Id)" + } + if ($Component -in @("all", "frontend")) { + $frontend = Start-Frontend + Write-Host "frontendPid=$($frontend.Id)" + } + Start-Sleep -Seconds 12 +} + +Show-Status diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..6fd00e24d --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,30 @@ +{ + // 使用 IntelliSense 了解相关属性。 + // 悬停以查看现有属性的描述。 + // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + + + { + "type": "java", + "name": "Chat2dbLiteApplication", + "request": "launch", + "mainClass": "ai.chat2db.server.start.Chat2dbLiteApplication", + "projectName": "chat2db-server-start" + }, + { + "type": "java", + "name": "Current File", + "request": "launch", + "mainClass": "${file}" + }, + { + "type": "java", + "name": "Application", + "request": "launch", + "mainClass": "ai.chat2db.server.start.Application", + "projectName": "chat2db-server-start" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index b2967e218..19e6916d8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -87,5 +87,9 @@ "yizhoumo", "zustand" ], - "java.compile.nullAnalysis.mode": "automatic" + "java.compile.nullAnalysis.mode": "automatic", + "java.debug.settings.onBuildFailureProceed": true, + "java.configuration.updateBuildConfiguration": "interactive", + "npm.packageManager": "yarn", + "js/ts.tsdk.path": "chat2db-client/node_modules/typescript/lib" } diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 000000000..4a9a960c3 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,26 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Frontend Dev (Node 24)", + "type": "shell", + "command": "powershell", + "args": [ + "-Command", + "yarn start:web:hot" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "presentation": { + "reveal": "always", + "panel": "new" + }, + "options": { + "cwd": "${workspaceFolder}/chat2db-client" + }, + "problemMatcher": [] + } + ] +} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..ccc0ec582 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,267 @@ +# AGENTS.md - Chat2DB Development Guide + +This document provides essential information for AI coding agents working on the Chat2DB codebase. + +## Project Overview + +Chat2DB is a multi-database client tool with AI capabilities. It consists of: +- **chat2db-client**: Frontend React/Electron application (TypeScript) +- **chat2db-server**: Backend Spring Boot application (Java 17) + +## Build/Lint/Test Commands + +### Frontend (chat2db-client) + +```powershell +# Install dependencies (must use yarn) +cd chat2db-client +yarn install + +# Development server +yarn run start:web # Web version +yarn run start # Desktop version (Electron + Web) + +# Build +yarn run build:web # Build for web +yarn run build:desktop # Build for desktop +yarn run build:prod # Production build + +# Linting +yarn run lint # Run ESLint + +# Package manager: yarn 4.9.1 (REQUIRED - do not use npm) +``` + +### Backend (chat2db-server) + +```powershell +# Build (from chat2db-server directory) +cd chat2db-server +mvn clean install -DskipTests # Build without tests +mvn clean install # Build with tests + +# Run a single test class +mvn test -Dtest=TableOperationsTest -pl chat2db-server-test + +# Run a single test method +mvn test -Dtest=TableOperationsTest#table -pl chat2db-server-test + +# Run application +mvn spring-boot:run -pl chat2db-server-start + +# Run packaged JAR +java -jar chat2db-server-start/target/chat2db-server-start.jar +``` + +## Requirements + +- **Java**: 17 or higher + - **Environment Setup**: `$env:JAVA_HOME = "D:\tool\Java\jdk-17"` before compiling Java code +- **Node.js**: 16 or higher +- **Maven**: 3.6.1 or higher +- **Yarn**: 4.x (required for frontend) + +## Code Style Guidelines + +### TypeScript/React (Frontend) + +#### Imports +```typescript +// React imports first +import React, { memo, useCallback, useEffect, useRef } from 'react'; + +// Third-party libraries +import classnames from 'classnames'; +import { message } from 'antd'; + +// Internal imports (use @ alias for src) +import { useWorkspaceStore } from '@/pages/main/workspace/store'; +import DraggableContainer from '@/components/DraggableContainer'; + +// Styles last +import styles from './index.less'; +``` + +#### Formatting (Prettier) +- Print width: 120 characters +- Single quotes +- Trailing commas: 'all' +- Use `prettier-plugin-organize-imports` for import ordering + +#### Naming Conventions +- Components: PascalCase (`WorkspaceLeft`, `TableController`) +- Files: camelCase for utilities, PascalCase for components +- Hooks: camelCase with `use` prefix (`useWorkspaceStore`) +- Stores: camelCase with `Store` suffix (`IWorkspaceStore`) +- Interfaces: PascalCase with `I` prefix (`IConfigStore`, `IStore`) +- Types: PascalCase with `Type` suffix or descriptive names + +#### React Patterns +- Use `memo()` for component exports: `const Component = memo(() => { ... })` +- Use functional components with hooks +- Use Zustand for state management +- Use `@/` alias for src imports + +#### ESLint Rules +- Max line length: 120 characters +- Prefer arrow functions +- Prefer const +- React hooks rules enforced +- TypeScript strict mode (noImplicitAny: false in tsconfig) + +### Java (Backend) + +#### Package Structure +``` +ai.chat2db.server.{module} +├── controller/ # REST controllers +├── service/ # Business logic interfaces and implementations +├── param/ # Request parameters +├── converter/ # Object converters/mappers +├── vo/ # View Objects (response DTOs) +└── model/ # Domain models +``` + +#### Naming Conventions +- Classes: PascalCase (`TableServiceImpl`, `TableController`) +- Methods: camelCase (`queryColumns`, `buildSql`) +- Constants: UPPER_SNAKE_CASE (`TABLE_NAME`) +- Parameters: camelCase with `Param` suffix (`TableQueryParam`) +- Services: Interface without suffix, impl with `Impl` suffix +- Converters: `*Converter` suffix +- VOs: `*VO` suffix + +#### Code Style +- Use Lombok annotations (`@Data`, `@Builder`, `@Slf4j`, `@AllArgsConstructor`) +- Use Spring annotations (`@Service`, `@RestController`, `@Autowired`) +- Use Jakarta validation (`@Valid`) +- 4-space indentation +- Opening braces on same line + +#### Service Layer Pattern +```java +public interface TableService { + DataResult query(TableQueryParam param, TableSelector selector); + ActionResult drop(DropParam param); +} + +@Service +@Slf4j +public class TableServiceImpl implements TableService { + @Autowired + private PinService pinService; + + @Override + public DataResult
query(TableQueryParam param, TableSelector selector) { + // Implementation + } +} +``` + +#### Controller Pattern +```java +@Slf4j +@ConnectionInfoAspect +@RequestMapping("/api/rdb/table") +@RestController +public class TableController { + @Autowired + private TableService tableService; + + @GetMapping("/list") + public WebPageResult list(@Valid TableBriefQueryRequest request) { + // Implementation + } +} +``` + +#### Error Handling +- Use `ActionResult` for operations without return data +- Use `DataResult` for single object returns +- Use `ListResult` for list returns +- Use `PageResult` for paginated results +- Log errors with `@Slf4j` and `log.error()`/`log.warn()` + +## Project Structure + +``` +Chat2DB/ +├── chat2db-client/ # Frontend +│ ├── src/ +│ │ ├── blocks/ # Reusable UI blocks +│ │ ├── components/ # React components +│ │ ├── pages/ # Page components +│ │ ├── service/ # API service layer +│ │ ├── hooks/ # Custom React hooks +│ │ ├── store/ # Zustand stores +│ │ ├── typings/ # TypeScript types +│ │ ├── utils/ # Utility functions +│ │ └── i18n/ # Internationalization +│ ├── .eslintrc.js +│ ├── .prettierrc +│ └── package.json +│ +├── chat2db-server/ # Backend +│ ├── chat2db-server-domain/ # Domain layer +│ │ ├── chat2db-server-domain-api/ # Service interfaces +│ │ └── chat2db-server-domain-core/ # Service implementations +│ ├── chat2db-server-web/ # Web/API layer +│ │ ├── chat2db-server-web-api/ # API controllers +│ │ └── chat2db-server-common-api/ # Common APIs +│ ├── chat2db-server-tools/ # Utilities +│ ├── chat2db-spi/ # Service Provider Interface +│ ├── chat2db-plugins/ # Database plugins (MySQL, PostgreSQL, etc.) +│ ├── chat2db-server-start/ # Main application +│ └── chat2db-server-test/ # Test module +│ +└── docker/ # Docker configuration +``` + +## Testing + +### Backend Tests +- Located in `chat2db-server-test/src/test/java/` +- Use JUnit 5 (`@Test`, `@Order`) +- Extend `BaseTest` for integration tests +- Use `@SpringBootTest` for integration tests + +### Running Tests +```powershell +# All tests +mvn test + +# Single test class +mvn test -Dtest=ClassName + +# Single test method +mvn test -Dtest=ClassName#methodName +``` + +## Key Technologies + +### Frontend +- React 18 with TypeScript +- UmiJS 4 framework +- Ant Design 5 +- Zustand for state management +- Monaco Editor for SQL editing +- Electron for desktop app + +### Backend +- Spring Boot 3.1 +- Java 17 +- MyBatis Plus for database access +- Sa-Token for authentication +- Lombok for boilerplate reduction +- MapStruct for object mapping +- H2 as embedded database + +## Important Notes + +1. **Always use yarn** for frontend package management, not npm +2. **Java 17** is required for the backend +3. **Node 16+** is required for the frontend +4. Tests in the server use `@TestMethodOrder(OrderAnnotation.class)` for execution order +5. Database plugins follow the SPI pattern in `chat2db-spi` +6. Use `@Valid` annotation for request validation in controllers +7. Follow existing patterns when adding new controllers or services \ No newline at end of file diff --git a/INITIALIZATION_GUIDE.md b/INITIALIZATION_GUIDE.md new file mode 100644 index 000000000..6b473e681 --- /dev/null +++ b/INITIALIZATION_GUIDE.md @@ -0,0 +1,131 @@ +# Chat2DB Project Initialization Guide + +This document outlines the steps required to initialize and build the Chat2DB project from scratch. + +## Prerequisites + +Before starting, ensure you have the following software installed: + +- **Java**: Version 17 or higher (Java 17.0.9 was used in this initialization) +- **Node.js**: Version 16 or higher (Node.js 20.19.1 was used in this initialization) +- **Yarn**: Version 4.9.1 or higher (Yarn 4.9.1 was used in this initialization) +- **Maven**: Version 3.6.1 or higher (Maven 3.6.1 was used in this initialization) + +## Project Structure + +Chat2DB is a full-stack application with the following main components: + +- `chat2db-client`: The frontend React/Electron application +- `chat2db-server`: The backend Spring Boot application + +## Step-by-step Initialization Process + +### 1. Clone the Repository + +```bash +git clone +cd Chat2DB +``` + +### 2. Initialize Client Application + +#### Navigate to the client directory: +```bash +cd chat2db-client +``` + +#### Install client dependencies: +```bash +yarn install +``` + +This will install all necessary JavaScript/TypeScript dependencies for the frontend. + +#### Build the client application: +```bash +yarn run build:web +``` + +This compiles the React application for production use. + +### 3. Initialize Server Application + +#### Navigate to the server directory: +```bash +cd ../chat2db-server +``` + +#### Install server dependencies and build the project: +```bash +mvn clean install -DskipTests +``` + +This command: +- Downloads all required Maven dependencies +- Compiles all Java components +- Builds executable JAR files for the server +- Skips running tests to speed up the build process + +### 4. Verify Build Output + +After successful initialization, you should find these executable JAR files: + +- **Main Server**: `chat2db-server\chat2db-server-start\target\chat2db-server-start.jar` +- **Web Server**: `chat2db-server\chat2db-server-web-start\target\chat2db-server-web-start.jar` + +## Running the Application + +### For Development + +#### Frontend development server: +```bash +cd chat2db-client +yarn run start:web +``` + +#### Backend development: +```bash +cd chat2db-server +mvn spring-boot:run -pl chat2db-server-start +``` + +### For Production + +Run the server application: +```bash +java -jar chat2db-server\chat2db-server-start\target\chat2db-server-start.jar +``` + +The application will be accessible via the web interface or through the Electron desktop application. + +## Troubleshooting + +### Common Issues + +1. **Maven Build Fails with Memory Issues** + - Ensure you have sufficient memory allocated to Maven + - You may need to set `MAVEN_OPTS` environment variable with increased heap size + +2. **Yarn Install Fails** + - Clear the yarn cache: `yarn cache clean` + - Try installing with network timeout tolerance: `yarn install --network-timeout 100000` + +3. **JDK Version Issues** + - Ensure you're using Java 17 or higher + - Verify with: `java -version` + +## Additional Notes + +- The project uses a multi-module Maven structure for server components +- Database plugins are built as separate modules (MySQL, PostgreSQL, Oracle, etc.) +- Client-side uses Umi framework with React and Ant Design +- The application supports various databases through plugin architecture +- AI integration features are available using ChatGPT-like APIs + +## Development Workflow + +After initialization, developers can: +- Modify frontend components in the `chat2db-client` directory +- Modify backend logic in the `chat2db-server` directory +- Test with the development servers before building for production +- Add new database plugins following the existing plugin architecture \ No newline at end of file diff --git a/chat2db-client/.gitignore b/chat2db-client/.gitignore index c5fcd3df3..6ffb61b89 100644 --- a/chat2db-client/.gitignore +++ b/chat2db-client/.gitignore @@ -10,8 +10,10 @@ /dist .swc ./yarn-error.log +/.yarn/* /release /static -/versions \ No newline at end of file +/versions +/.yarn diff --git a/chat2db-client/.nvmrc b/chat2db-client/.nvmrc new file mode 100644 index 000000000..a45fd52cc --- /dev/null +++ b/chat2db-client/.nvmrc @@ -0,0 +1 @@ +24 diff --git a/chat2db-client/.umirc.ts b/chat2db-client/.umirc.ts index 6ea951156..49e3d7092 100644 --- a/chat2db-client/.umirc.ts +++ b/chat2db-client/.umirc.ts @@ -48,6 +48,10 @@ export default defineConfig({ path: '/workspace', component: 'main', }, + { + path: '/taskCenter', + component: 'main', + }, { path: '/', component: 'main', @@ -61,6 +65,14 @@ export default defineConfig({ plugins: ['@umijs/plugins/dist/dva'], chainWebpack, proxy: { + '/api/ai/chat': { + target: 'http://127.0.0.1:10821', + changeOrigin: true, + proxyTimeout: 3600000, + headers: { + Connection: 'keep-alive', + }, + }, '/api': { target: 'http://127.0.0.1:10821', changeOrigin: true, @@ -100,10 +112,10 @@ export default defineConfig({ // window.addEventListener("appinstalled", () => { // deferredPrompt = null; // })`, - { - src: 'https://www.googletagmanager.com/gtag/js?id=G-V8M4E5SF61', - async: true, - }, + // { + // src: 'https://www.googletagmanager.com/gtag/js?id=G-V8M4E5SF61', + // async: true, + // }, // `window.dataLayer = window.dataLayer || []; // function gtag() { // window.dataLayer.push(arguments); diff --git a/chat2db-client/.yarnrc.yml b/chat2db-client/.yarnrc.yml new file mode 100644 index 000000000..3186f3f07 --- /dev/null +++ b/chat2db-client/.yarnrc.yml @@ -0,0 +1 @@ +nodeLinker: node-modules diff --git a/chat2db-client/package.json b/chat2db-client/package.json index b0b8afaa6..f7fb37f64 100644 --- a/chat2db-client/package.json +++ b/chat2db-client/package.json @@ -1,13 +1,13 @@ { "name": "chat2db", - "version": "1.0.0", + "version": "2.1.16", "private": true, "repository": { "type": "git", "url": "https://github.com/chat2db/Chat2DB" }, "author": "fjy, hexi", - "main": "src/main/main.js", + "main": "src/main/index.js", "scripts": { "build": "npm run build:web && npm run build:main", "build:desktop": "npm run build:web:desktop && npm run build:main:prod", @@ -15,18 +15,20 @@ "build:main:prod": "cross-env NODE_ENV=production electron-builder", "build:prod": "npm run build:web:prod && npm run build:main:prod", "build:web": "umi build", - "build:web:desktop": "cross-env UMI_ENV=desktop cross-env APP_VERSION=${npm_config_app_version} cross-env APP_PORT=${npm_config_app_port} umi build", - "build:web:prod": "cross-env UMI_ENV=prod cross-env APP_VERSION=${npm_config_app_version} cross-env APP_PORT=${npm_config_app_port} cross-env UMI_PublicPath=${npm_config_public_path} umi build", + "build:web:desktop": "cross-env UMI_ENV=desktop umi build", + "build:web:prod": "cross-env UMI_ENV=prod umi build", "postinstall": "umi setup", "lint": "umi lint", "start": "concurrently \"npm run start:web\" \"npm run start:main\"", "start:main": "cross-env NODE_ENV=development electron .", "start:main:prod": "cross-env NODE_ENV=production electron .", - "start:web": "cross-env UMI_ENV=local HMR=none cross-env APP_VERSION=${npm_config_app_version} umi dev", - "start:web:hot": "cross-env UMI_ENV=local cross-env APP_VERSION=${npm_config_app_version} umi dev" + "start:web": "cross-env UMI_ENV=local HMR=none umi dev", + "start:web:hot": "cross-env UMI_ENV=local umi dev" }, "dependencies": { + "@dagrejs/dagre": "^3.0.0", "@dnd-kit/modifiers": "^6.0.1", + "@xyflow/react": "^12.10.2", "ahooks": "^3.7.8", "ali-react-table": "^2.6.1", "antd": "^5.12.1", @@ -35,13 +37,16 @@ "echarts-for-react": "^3.0.2", "event-source-polyfill": "^1.0.31", "highlight.js": "^11.9.0", + "html-to-image": "^1.11.13", "lodash": "^4.17.21", "markdown-it-link-attributes": "^4.0.1", "monaco-editor": "^0.44.0", "monaco-editor-esm-webpack-plugin": "^2.1.0", "monaco-editor-webpack-plugin": "^7.0.1", + "react-markdown": "^8.0.7", "react-monaco-editor": "^0.54.0", "react-sortablejs": "^6.1.4", + "remark-gfm": "3", "sql-formatter": "^13.0.4", "styled-components": "^6.0.1", "umi": "^4.0.87", @@ -76,6 +81,8 @@ "prettier": "^2", "prettier-plugin-organize-imports": "^2", "prettier-plugin-packagejson": "^2", + "stylelint": "^17.8.0", + "stylelint-config-standard": "^40.0.0", "tailwindcss": "^3", "typescript": "^5.0.3" }, @@ -83,6 +90,7 @@ "react": "^16.8.0", "react-dom": "^16.8.0" }, + "packageManager": "yarn@4.13.0+sha512.5c20ba010c99815433e5c8453112165e673f1c7948d8d2b267f4b5e52097538658388ebc9f9580656d9b75c5cc996f990f611f99304a2197d4c56d21eea370e7", "engines": { "node": ">=16" }, diff --git a/chat2db-client/src/assets/font/demo.css b/chat2db-client/src/assets/font/demo.css deleted file mode 100644 index a67054a0a..000000000 --- a/chat2db-client/src/assets/font/demo.css +++ /dev/null @@ -1,539 +0,0 @@ -/* Logo 字体 */ -@font-face { - font-family: "iconfont logo"; - src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834'); - src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'), - url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'), - url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'), - url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg'); -} - -.logo { - font-family: "iconfont logo"; - font-size: 160px; - font-style: normal; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -/* tabs */ -.nav-tabs { - position: relative; -} - -.nav-tabs .nav-more { - position: absolute; - right: 0; - bottom: 0; - height: 42px; - line-height: 42px; - color: #666; -} - -#tabs { - border-bottom: 1px solid #eee; -} - -#tabs li { - cursor: pointer; - width: 100px; - height: 40px; - line-height: 40px; - text-align: center; - font-size: 16px; - border-bottom: 2px solid transparent; - position: relative; - z-index: 1; - margin-bottom: -1px; - color: #666; -} - - -#tabs .active { - border-bottom-color: #f00; - color: #222; -} - -.tab-container .content { - display: none; -} - -/* 页面布局 */ -.main { - padding: 30px 100px; - width: 960px; - margin: 0 auto; -} - -.main .logo { - color: #333; - text-align: left; - margin-bottom: 30px; - line-height: 1; - height: 110px; - margin-top: -50px; - overflow: hidden; - *zoom: 1; -} - -.main .logo a { - font-size: 160px; - color: #333; -} - -.helps { - margin-top: 40px; -} - -.helps pre { - padding: 20px; - margin: 10px 0; - border: solid 1px #e7e1cd; - background-color: #fffdef; - overflow: auto; -} - -.icon_lists { - width: 100% !important; - overflow: hidden; - *zoom: 1; -} - -.icon_lists li { - width: 100px; - margin-bottom: 10px; - margin-right: 20px; - text-align: center; - list-style: none !important; - cursor: default; -} - -.icon_lists li .code-name { - line-height: 1.2; -} - -.icon_lists .icon { - display: block; - height: 100px; - line-height: 100px; - font-size: 42px; - margin: 10px auto; - color: #333; - -webkit-transition: font-size 0.25s linear, width 0.25s linear; - -moz-transition: font-size 0.25s linear, width 0.25s linear; - transition: font-size 0.25s linear, width 0.25s linear; -} - -.icon_lists .icon:hover { - font-size: 100px; -} - -.icon_lists .svg-icon { - /* 通过设置 font-size 来改变图标大小 */ - width: 1em; - /* 图标和文字相邻时,垂直对齐 */ - vertical-align: -0.15em; - /* 通过设置 color 来改变 SVG 的颜色/fill */ - fill: currentColor; - /* path 和 stroke 溢出 viewBox 部分在 IE 下会显示 - normalize.css 中也包含这行 */ - overflow: hidden; -} - -.icon_lists li .name, -.icon_lists li .code-name { - color: #666; -} - -/* markdown 样式 */ -.markdown { - color: #666; - font-size: 14px; - line-height: 1.8; -} - -.highlight { - line-height: 1.5; -} - -.markdown img { - vertical-align: middle; - max-width: 100%; -} - -.markdown h1 { - color: #404040; - font-weight: 500; - line-height: 40px; - margin-bottom: 24px; -} - -.markdown h2, -.markdown h3, -.markdown h4, -.markdown h5, -.markdown h6 { - color: #404040; - margin: 1.6em 0 0.6em 0; - font-weight: 500; - clear: both; -} - -.markdown h1 { - font-size: 28px; -} - -.markdown h2 { - font-size: 22px; -} - -.markdown h3 { - font-size: 16px; -} - -.markdown h4 { - font-size: 14px; -} - -.markdown h5 { - font-size: 12px; -} - -.markdown h6 { - font-size: 12px; -} - -.markdown hr { - height: 1px; - border: 0; - background: #e9e9e9; - margin: 16px 0; - clear: both; -} - -.markdown p { - margin: 1em 0; -} - -.markdown>p, -.markdown>blockquote, -.markdown>.highlight, -.markdown>ol, -.markdown>ul { - width: 80%; -} - -.markdown ul>li { - list-style: circle; -} - -.markdown>ul li, -.markdown blockquote ul>li { - margin-left: 20px; - padding-left: 4px; -} - -.markdown>ul li p, -.markdown>ol li p { - margin: 0.6em 0; -} - -.markdown ol>li { - list-style: decimal; -} - -.markdown>ol li, -.markdown blockquote ol>li { - margin-left: 20px; - padding-left: 4px; -} - -.markdown code { - margin: 0 3px; - padding: 0 5px; - background: #eee; - border-radius: 3px; -} - -.markdown strong, -.markdown b { - font-weight: 600; -} - -.markdown>table { - border-collapse: collapse; - border-spacing: 0px; - empty-cells: show; - border: 1px solid #e9e9e9; - width: 95%; - margin-bottom: 24px; -} - -.markdown>table th { - white-space: nowrap; - color: #333; - font-weight: 600; -} - -.markdown>table th, -.markdown>table td { - border: 1px solid #e9e9e9; - padding: 8px 16px; - text-align: left; -} - -.markdown>table th { - background: #F7F7F7; -} - -.markdown blockquote { - font-size: 90%; - color: #999; - border-left: 4px solid #e9e9e9; - padding-left: 0.8em; - margin: 1em 0; -} - -.markdown blockquote p { - margin: 0; -} - -.markdown .anchor { - opacity: 0; - transition: opacity 0.3s ease; - margin-left: 8px; -} - -.markdown .waiting { - color: #ccc; -} - -.markdown h1:hover .anchor, -.markdown h2:hover .anchor, -.markdown h3:hover .anchor, -.markdown h4:hover .anchor, -.markdown h5:hover .anchor, -.markdown h6:hover .anchor { - opacity: 1; - display: inline-block; -} - -.markdown>br, -.markdown>p>br { - clear: both; -} - - -.hljs { - display: block; - background: white; - padding: 0.5em; - color: #333333; - overflow-x: auto; -} - -.hljs-comment, -.hljs-meta { - color: #969896; -} - -.hljs-string, -.hljs-variable, -.hljs-template-variable, -.hljs-strong, -.hljs-emphasis, -.hljs-quote { - color: #df5000; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-type { - color: #a71d5d; -} - -.hljs-literal, -.hljs-symbol, -.hljs-bullet, -.hljs-attribute { - color: #0086b3; -} - -.hljs-section, -.hljs-name { - color: #63a35c; -} - -.hljs-tag { - color: #333333; -} - -.hljs-title, -.hljs-attr, -.hljs-selector-id, -.hljs-selector-class, -.hljs-selector-attr, -.hljs-selector-pseudo { - color: #795da3; -} - -.hljs-addition { - color: #55a532; - background-color: #eaffea; -} - -.hljs-deletion { - color: #bd2c00; - background-color: #ffecec; -} - -.hljs-link { - text-decoration: underline; -} - -/* 代码高亮 */ -/* PrismJS 1.15.0 -https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */ -/** - * prism.js default theme for JavaScript, CSS and HTML - * Based on dabblet (http://dabblet.com) - * @author Lea Verou - */ -code[class*="language-"], -pre[class*="language-"] { - color: black; - background: none; - text-shadow: 0 1px white; - font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; - text-align: left; - white-space: pre; - word-spacing: normal; - word-break: normal; - word-wrap: normal; - line-height: 1.5; - - -moz-tab-size: 4; - -o-tab-size: 4; - tab-size: 4; - - -webkit-hyphens: none; - -moz-hyphens: none; - -ms-hyphens: none; - hyphens: none; -} - -pre[class*="language-"]::-moz-selection, -pre[class*="language-"] ::-moz-selection, -code[class*="language-"]::-moz-selection, -code[class*="language-"] ::-moz-selection { - text-shadow: none; - background: #b3d4fc; -} - -pre[class*="language-"]::selection, -pre[class*="language-"] ::selection, -code[class*="language-"]::selection, -code[class*="language-"] ::selection { - text-shadow: none; - background: #b3d4fc; -} - -@media print { - - code[class*="language-"], - pre[class*="language-"] { - text-shadow: none; - } -} - -/* Code blocks */ -pre[class*="language-"] { - padding: 1em; - margin: .5em 0; - overflow: auto; -} - -:not(pre)>code[class*="language-"], -pre[class*="language-"] { - background: #f5f2f0; -} - -/* Inline code */ -:not(pre)>code[class*="language-"] { - padding: .1em; - border-radius: .3em; - white-space: normal; -} - -.token.comment, -.token.prolog, -.token.doctype, -.token.cdata { - color: slategray; -} - -.token.punctuation { - color: #999; -} - -.namespace { - opacity: .7; -} - -.token.property, -.token.tag, -.token.boolean, -.token.number, -.token.constant, -.token.symbol, -.token.deleted { - color: #905; -} - -.token.selector, -.token.attr-name, -.token.string, -.token.char, -.token.builtin, -.token.inserted { - color: #690; -} - -.token.operator, -.token.entity, -.token.url, -.language-css .token.string, -.style .token.string { - color: #9a6e3a; - background: hsla(0, 0%, 100%, .5); -} - -.token.atrule, -.token.attr-value, -.token.keyword { - color: #07a; -} - -.token.function, -.token.class-name { - color: #DD4A68; -} - -.token.regex, -.token.important, -.token.variable { - color: #e90; -} - -.token.important, -.token.bold { - font-weight: bold; -} - -.token.italic { - font-style: italic; -} - -.token.entity { - cursor: help; -} diff --git a/chat2db-client/src/assets/font/demo_index.html b/chat2db-client/src/assets/font/demo_index.html deleted file mode 100644 index 6a0d556a8..000000000 --- a/chat2db-client/src/assets/font/demo_index.html +++ /dev/null @@ -1,4880 +0,0 @@ - - - - - iconfont Demo - - - - - - - - - - - - - -
-

- - -

- -
-
-
    - -
  • - -
    right_on_5
    -
    
    -
  • - -
  • - -
    right_off_5-01
    -
    
    -
  • - -
  • - -
    left_on_2
    -
    
    -
  • - -
  • - -
    left_off
    -
    
    -
  • - -
  • - -
    minimize21
    -
    
    -
  • - -
  • - -
    restore
    -
    
    -
  • - -
  • - -
    resize
    -
    
    -
  • - -
  • - -
    close
    -
    
    -
  • - -
  • - -
    筛选
    -
    
    -
  • - -
  • - -
    排序
    -
    
    -
  • - -
  • - -
    305信息-线性圆框
    -
    
    -
  • - -
  • - -
    加号
    -
    
    -
  • - -
  • - -
    列表
    -
    
    -
  • - -
  • - -
    减去
    -
    
    -
  • - -
  • - -
    database
    -
    
    -
  • - -
  • - -
    筛选
    -
    
    -
  • - -
  • - -
    刷新
    -
    
    -
  • - -
  • - -
    加号_o
    -
    
    -
  • - -
  • - -
    数据库_jurassic
    -
    
    -
  • - -
  • - -
    权限
    -
    
    -
  • - -
  • - -
    sharpicons_add-database
    -
    
    -
  • - -
  • - -
    组织管理
    -
    
    -
  • - -
  • - -
    空间
    -
    
    -
  • - -
  • - 𐂾 -
    下箭头-copy
    -
    𐂾
    -
  • - -
  • - -
    查看
    -
    
    -
  • - -
  • - -
    clone
    -
    
    -
  • - -
  • - -
    提交
    -
    
    -
  • - -
  • - -
    查看
    -
    
    -
  • - -
  • - -
    复制
    -
    
    -
  • - -
  • - -
    icon_answer
    -
    
    -
  • - -
  • - -
    icon_question
    -
    
    -
  • - -
  • - 𐂽 -
    发送
    -
    𐂽
    -
  • - -
  • - -
    重启
    -
    
    -
  • - -
  • - -
    提醒
    -
    
    -
  • - -
  • - -
    提醒
    -
    
    -
  • - -
  • - -
    提醒
    -
    
    -
  • - -
  • - -
    升级
    -
    
    -
  • - -
  • - -
    全局_升级
    -
    
    -
  • - -
  • - -
    关于我们
    -
    
    -
  • - -
  • - -
    ico版本更新
    -
    
    -
  • - -
  • - -
    对话气泡
    -
    
    -
  • - -
  • - -
    角色权限
    -
    
    -
  • - -
  • - -
    preview
    -
    
    -
  • - -
  • - -
    导入
    -
    
    -
  • - -
  • - -
    终止
    -
    
    -
  • - -
  • - -
    退出
    -
    
    -
  • - -
  • - -
    控桩终端
    -
    
    -
  • - -
  • - -
    撤销
    -
    
    -
  • - -
  • - -
    向上
    -
    
    -
  • - -
  • - -
    查看
    -
    
    -
  • - -
  • - -
    编辑数据_编辑录入操作_jurassic
    -
    
    -
  • - -
  • - -
    编辑表格_编辑录入操作_jurassic
    -
    
    -
  • - -
  • - -
    报表数据录入
    -
    
    -
  • - -
  • - -
    播放5
    -
    
    -
  • - -
  • - -
    清空@3x
    -
    
    -
  • - -
  • - -
    删除
    -
    
    -
  • - -
  • - -
    new-document-worksheet
    -
    
    -
  • - -
  • - -
    file-excel
    -
    
    -
  • - -
  • - -
    file-markdown
    -
    
    -
  • - -
  • - -
    file-word
    -
    
    -
  • - -
  • - -
    HTML5
    -
    
    -
  • - -
  • - -
    HTML
    -
    
    -
  • - -
  • - -
    pdf
    -
    
    -
  • - -
  • - -
    个人用户
    -
    
    -
  • - -
  • - -
    后台管理
    -
    
    -
  • - -
  • - -
    字体代码
    -
    
    -
  • - -
  • - -
    版本
    -
    
    -
  • - -
  • - -
    车位管理
    -
    
    -
  • - -
  • - -
    dictate
    -
    
    -
  • - -
  • - -
    circle-f
    -
    
    -
  • - -
  • - -
    图表-函数
    -
    
    -
  • - -
  • - -
    视图管理器
    -
    
    -
  • - -
  • - -
    回车
    -
    
    -
  • - -
  • - -
    缺省
    -
    
    -
  • - -
  • - -
    进入箭头
    -
    
    -
  • - -
  • - -
    右箭头
    -
    
    -
  • - -
  • - -
    向右箭头
    -
    
    -
  • - -
  • - -
    数据源
    -
    
    -
  • - -
  • - -
    question
    -
    
    -
  • - -
  • - -
    星星-copy
    -
    
    -
  • - -
  • - -
    控制台
    -
    
    -
  • - -
  • - -
    星系
    -
    
    -
  • - -
  • - -
    暂无数据 (1)
    -
    
    -
  • - -
  • - -
    开始
    -
    
    -
  • - -
  • - -
    关闭
    -
    
    -
  • - -
  • - -
    下箭头
    -
    
    -
  • - -
  • - -
    more
    -
    
    -
  • - -
  • - -
    设置
    -
    
    -
  • - -
  • - -
    对话-未选
    -
    
    -
  • - -
  • - -
    图表-未选
    -
    
    -
  • - -
  • - -
    编组 13备份 3
    -
    
    -
  • - -
  • - -
    编组备份
    -
    
    -
  • - -
  • - -
    表格
    -
    
    -
  • - -
  • - -
    收藏 (1)
    -
    
    -
  • - -
  • - -
    guthub-未选
    -
    
    -
  • - -
  • - -
    数据-未选
    -
    
    -
  • - -
  • - -
    编组 4
    -
    
    -
  • - -
  • - -
    编组 14备份
    -
    
    -
  • - -
  • - -
    guthub-未选
    -
    
    -
  • - -
  • - -
    24gl-folderMinus
    -
    
    -
  • - -
  • -  -
    24gl-folderOpen
    -
    
    -
  • - -
  • - -
    24gf-folderOpen
    -
    
    -
  • - -
  • - -
    云数据库
    -
    
    -
  • - -
  • - -
    报表
    -
    
    -
  • - -
  • - -
    工作台
    -
    
    -
  • - -
  • - -
    mongodb
    -
    
    -
  • - -
  • - -
    Redis
    -
    
    -
  • - -
  • - -
    HIVE_2
    -
    
    -
  • - -
  • - -
    Kingbase
    -
    
    -
  • - -
  • - -
    仪表盘
    -
    
    -
  • - -
  • - -
    presto
    -
    
    -
  • - -
  • - -
    DB2
    -
    
    -
  • - -
  • - -
    oceanbase
    -
    
    -
  • - -
  • - -
    达梦
    -
    
    -
  • - -
  • - -
    proxy
    -
    
    -
  • - -
  • - -
    openai
    -
    
    -
  • - -
  • - -
    关于
    -
    
    -
  • - -
  • - -
    衣服
    -
    
    -
  • - -
  • - -
    数据库
    -
    
    -
  • - -
  • - -
    数据源配置
    -
    
    -
  • - -
  • - -
    服务器_数据库_jurassic
    -
    
    -
  • - -
  • - -
    数据库
    -
    
    -
  • - -
  • - -
    数据库
    -
    
    -
  • - -
  • - -
    数据库数据
    -
    
    -
  • - -
  • - -
    数据库
    -
    
    -
  • - -
  • - -
    配置数据源
    -
    
    -
  • - -
  • - -
    SQL历史查询
    -
    
    -
  • - -
  • - -
    重命名
    -
    
    -
  • - -
  • - -
    ico_数据查询与统计_预约情况查询
    -
    
    -
  • - -
  • - -
    clickhouse-云数据库ClickHouse
    -
    
    -
  • - -
  • - -
    rds_mariadb
    -
    
    -
  • - -
  • - -
    减少减去减号
    -
    
    -
  • - -
  • - -
    sqlserver
    -
    
    -
  • - -
  • - -
    sqlite
    -
    
    -
  • - -
  • - -
    缺省页_暂无数据
    -
    
    -
  • - -
  • - -
    未完成
    -
    
    -
  • - -
  • - -
    完成-01
    -
    
    -
  • - -
  • - -
    成功
    -
    
    -
  • - -
  • - -
    机器人
    -
    
    -
  • - -
  • - -
    换一换
    -
    
    -
  • - -
  • - -
    icon_infomation
    -
    
    -
  • - -
  • - -
    key
    -
    
    -
  • - -
  • - -
    mysql
    -
    
    -
  • - -
  • - -
    oracle
    -
    
    -
  • - -
  • - -
    postgresql
    -
    
    -
  • - -
  • - -
    h2
    -
    
    -
  • - -
  • - -
    cc-schema
    -
    
    -
  • - -
  • - -
    新建表格
    -
    
    -
  • - -
  • - -
    export
    -
    
    -
  • - -
  • - -
    角色管理
    -
    
    -
  • - -
  • - -
    console
    -
    
    -
  • - -
  • - -
    24gf-folderMinus
    -
    
    -
  • - -
  • - -
    查看
    -
    
    -
  • - -
  • - -
    复制_o
    -
    
    -
  • - -
  • - -
    执行
    -
    
    -
  • - -
  • - -
    m-格式化文字
    -
    
    -
  • - -
  • - -
    github-fill
    -
    
    -
  • - -
  • - -
    保存
    -
    
    -
  • - -
  • - -
    箭头_向左两次_o
    -
    
    -
  • - -
  • - -
    新建窗口
    -
    
    -
  • - -
  • - -
    loading
    -
    
    -
  • - -
  • - -
    链接克隆
    -
    
    -
  • - -
  • - -
    SQL升级文件
    -
    
    -
  • - -
  • - -
    sql
    -
    
    -
  • - -
  • - -
    连接流
    -
    
    -
  • - -
  • - -
    跳转/退出
    -
    
    -
  • - -
  • - -
    key
    -
    
    -
  • - -
  • - -
    播放记录
    -
    
    -
  • - -
  • - -
    成功
    -
    
    -
  • - -
  • - -
    失败
    -
    
    -
  • - -
  • - -
    收回 上下
    -
    
    -
  • - -
  • - -
    展开 上下
    -
    
    -
  • - -
  • - -
    数据库
    -
    
    -
  • - -
  • - -
    保存
    -
    
    -
  • - -
  • - -
    查询
    -
    
    -
  • - -
  • - -
    对勾
    -
    
    -
  • - -
  • - -
    check
    -
    
    -
  • - -
  • - -
    概览
    -
    
    -
  • - -
  • - -
    概览
    -
    
    -
  • - -
  • - -
    编辑
    -
    
    -
  • - -
  • - -
    刷新
    -
    
    -
  • - -
  • - -
    菜单/列表
    -
    
    -
  • - -
  • - -
    表格
    -
    
    -
  • - -
  • - -
    展开
    -
    
    -
  • - -
  • - -
    收起
    -
    
    -
  • - -
  • - -
    主题_o
    -
    
    -
  • - -
  • - -
    断开连接
    -
    
    -
  • - -
  • - -
    修改
    -
    
    -
  • - -
  • - -
    删除
    -
    
    -
  • - -
  • - -
    更多
    -
    
    -
  • - -
  • - -
    减少
    -
    
    -
  • - -
  • - -
    -
    
    -
  • - -
  • - -
    加号
    -
    
    -
  • - -
  • - -
    arrow drop down
    -
    
    -
  • - -
  • - -
    search
    -
    
    -
  • - -
  • - -
    download
    -
    
    -
  • - -
  • - -
    向右箭头
    -
    
    -
  • - -
  • - -
    删除线型
    -
    
    -
  • - -
  • - -
    cross
    -
    
    -
  • - -
  • - -
    刷新
    -
    
    -
  • - -
  • - -
    提醒
    -
    
    -
  • - -
  • - -
    138设置、系统设置、功能设置、属性
    -
    
    -
  • - -
  • - -
    执行sql脚本
    -
    
    -
  • - -
  • - -
    虚拟数据库管理
    -
    
    -
  • - -
-
-

Unicode 引用

-
- -

Unicode 是字体在网页端最原始的应用方式,特点是:

-
    -
  • 支持按字体的方式去动态调整图标大小,颜色等等。
  • -
  • 默认情况下不支持多色,直接添加多色图标会自动去色。
  • -
-
-

注意:新版 iconfont 支持两种方式引用多色图标:SVG symbol 引用方式和彩色字体图标模式。(使用彩色字体图标需要在「编辑项目」中开启「彩色」选项后并重新生成。)

-
-

Unicode 使用步骤如下:

-

第一步:拷贝项目下面生成的 @font-face

-
@font-face {
-  font-family: 'iconfont';
-  src: url('iconfont.woff2?t=1704794525154') format('woff2'),
-       url('iconfont.woff?t=1704794525154') format('woff'),
-       url('iconfont.ttf?t=1704794525154') format('truetype');
-}
-
-

第二步:定义使用 iconfont 的样式

-
.iconfont {
-  font-family: "iconfont" !important;
-  font-size: 16px;
-  font-style: normal;
-  -webkit-font-smoothing: antialiased;
-  -moz-osx-font-smoothing: grayscale;
-}
-
-

第三步:挑选相应图标并获取字体编码,应用于页面

-
-<span class="iconfont">&#x33;</span>
-
-
-

"iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。

-
-
-
-
-
    - -
  • - -
    - right_on_5 -
    -
    .icon-right_on_5 -
    -
  • - -
  • - -
    - right_off_5-01 -
    -
    .icon-right_off_5-01 -
    -
  • - -
  • - -
    - left_on_2 -
    -
    .icon-a-left_on_huaban11 -
    -
  • - -
  • - -
    - left_off -
    -
    .icon-a-left_off_huaban1 -
    -
  • - -
  • - -
    - minimize21 -
    -
    .icon-minimize21 -
    -
  • - -
  • - -
    - restore -
    -
    .icon-restore_button2 -
    -
  • - -
  • - -
    - resize -
    -
    .icon-resize_button2 -
    -
  • - -
  • - -
    - close -
    -
    .icon-close_button2 -
    -
  • - -
  • - -
    - 筛选 -
    -
    .icon-shaixuan -
    -
  • - -
  • - -
    - 排序 -
    -
    .icon-a-44tubiao-122 -
    -
  • - -
  • - -
    - 305信息-线性圆框 -
    -
    .icon-xinxi-xianxingyuankuang -
    -
  • - -
  • - -
    - 加号 -
    -
    .icon-jiahao -
    -
  • - -
  • - -
    - 列表 -
    -
    .icon-liebiao -
    -
  • - -
  • - -
    - 减去 -
    -
    .icon-jianqu -
    -
  • - -
  • - -
    - database -
    -
    .icon-database -
    -
  • - -
  • - -
    - 筛选 -
    -
    .icon-shaixuan1 -
    -
  • - -
  • - -
    - 刷新 -
    -
    .icon-shuaxin2 -
    -
  • - -
  • - -
    - 加号_o -
    -
    .icon-jiahao_o -
    -
  • - -
  • - -
    - 数据库_jurassic -
    -
    .icon-jurassic_data -
    -
  • - -
  • - -
    - 权限 -
    -
    .icon-quanxian -
    -
  • - -
  • - -
    - sharpicons_add-database -
    -
    .icon-sharpicons_add-database -
    -
  • - -
  • - -
    - 组织管理 -
    -
    .icon-zuzhiguanli- -
    -
  • - -
  • - -
    - 空间 -
    -
    .icon-moxing-miaobian -
    -
  • - -
  • - -
    - 下箭头-copy -
    -
    .icon-xiajiantou1-copy -
    -
  • - -
  • - -
    - 查看 -
    -
    .icon-chakan2 -
    -
  • - -
  • - -
    - clone -
    -
    .icon-clone -
    -
  • - -
  • - -
    - 提交 -
    -
    .icon-tijiao -
    -
  • - -
  • - -
    - 查看 -
    -
    .icon-chakan1 -
    -
  • - -
  • - -
    - 复制 -
    -
    .icon-fuzhi -
    -
  • - -
  • - -
    - icon_answer -
    -
    .icon-icon_answer -
    -
  • - -
  • - -
    - icon_question -
    -
    .icon-icon_question -
    -
  • - -
  • - -
    - 发送 -
    -
    .icon-fasong -
    -
  • - -
  • - -
    - 重启 -
    -
    .icon-zhongqi -
    -
  • - -
  • - -
    - 提醒 -
    -
    .icon-tixing2 -
    -
  • - -
  • - -
    - 提醒 -
    -
    .icon-tixing3 -
    -
  • - -
  • - -
    - 提醒 -
    -
    .icon-tixing1 -
    -
  • - -
  • - -
    - 升级 -
    -
    .icon-shengji -
    -
  • - -
  • - -
    - 全局_升级 -
    -
    .icon-quanju_shengji -
    -
  • - -
  • - -
    - 关于我们 -
    -
    .icon-guanyuwomen1 -
    -
  • - -
  • - -
    - ico版本更新 -
    -
    .icon-icobanbengengxin -
    -
  • - -
  • - -
    - 对话气泡 -
    -
    .icon-duihuaqipao -
    -
  • - -
  • - -
    - 角色权限 -
    -
    .icon-jiaosequanxian -
    -
  • - -
  • - -
    - preview -
    -
    .icon-preview1 -
    -
  • - -
  • - -
    - 导入 -
    -
    .icon-daoru -
    -
  • - -
  • - -
    - 终止 -
    -
    .icon-zhongzhi -
    -
  • - -
  • - -
    - 退出 -
    -
    .icon-tuichu -
    -
  • - -
  • - -
    - 控桩终端 -
    -
    .icon-kongzhuangzhongduan -
    -
  • - -
  • - -
    - 撤销 -
    -
    .icon-chexiao1 -
    -
  • - -
  • - -
    - 向上 -
    -
    .icon-xiangshang -
    -
  • - -
  • - -
    - 查看 -
    -
    .icon-chakan-copy -
    -
  • - -
  • - -
    - 编辑数据_编辑录入操作_jurassic -
    -
    .icon-jurassic_edit-data -
    -
  • - -
  • - -
    - 编辑表格_编辑录入操作_jurassic -
    -
    .icon-jurassic_edit-table -
    -
  • - -
  • - -
    - 报表数据录入 -
    -
    .icon-baobiaoshujuluru -
    -
  • - -
  • - -
    - 播放5 -
    -
    .icon-bofang5 -
    -
  • - -
  • - -
    - 清空@3x -
    -
    .icon-a-qingkong3x -
    -
  • - -
  • - -
    - 删除 -
    -
    .icon-shanchu -
    -
  • - -
  • - -
    - new-document-worksheet -
    -
    .icon-newdocumentworksheet -
    -
  • - -
  • - -
    - file-excel -
    -
    .icon-file-excel -
    -
  • - -
  • - -
    - file-markdown -
    -
    .icon-file-markdown -
    -
  • - -
  • - -
    - file-word -
    -
    .icon-file-word -
    -
  • - -
  • - -
    - HTML5 -
    -
    .icon-HTML -
    -
  • - -
  • - -
    - HTML -
    -
    .icon-HTML1 -
    -
  • - -
  • - -
    - pdf -
    -
    .icon-pdf -
    -
  • - -
  • - -
    - 个人用户 -
    -
    .icon-gerenyonghu -
    -
  • - -
  • - -
    - 后台管理 -
    -
    .icon-houtaiguanli -
    -
  • - -
  • - -
    - 字体代码 -
    -
    .icon-zitidaima -
    -
  • - -
  • - -
    - 版本 -
    -
    .icon-banben -
    -
  • - -
  • - -
    - 车位管理 -
    -
    .icon-cheweiguanli -
    -
  • - -
  • - -
    - dictate -
    -
    .icon-dianzhelidaochu -
    -
  • - -
  • - -
    - circle-f -
    -
    .icon-circle-f -
    -
  • - -
  • - -
    - 图表-函数 -
    -
    .icon-tubiao-hanshu -
    -
  • - -
  • - -
    - 视图管理器 -
    -
    .icon-shituguanliqi -
    -
  • - -
  • - -
    - 回车 -
    -
    .icon-huiche -
    -
  • - -
  • - -
    - 缺省 -
    -
    .icon-quesheng -
    -
  • - -
  • - -
    - 进入箭头 -
    -
    .icon-jinrujiantou -
    -
  • - -
  • - -
    - 右箭头 -
    -
    .icon-youjiantou_huaban -
    -
  • - -
  • - -
    - 向右箭头 -
    -
    .icon-xiangyoujiantou1 -
    -
  • - -
  • - -
    - 数据源 -
    -
    .icon-shujuyuan -
    -
  • - -
  • - -
    - question -
    -
    .icon-question -
    -
  • - -
  • - -
    - 星星-copy -
    -
    .icon-xingxing -
    -
  • - -
  • - -
    - 控制台 -
    -
    .icon-kongzhitai -
    -
  • - -
  • - -
    - 星系 -
    -
    .icon-xingxi -
    -
  • - -
  • - -
    - 暂无数据 (1) -
    -
    .icon-a-zanwushuju1 -
    -
  • - -
  • - -
    - 开始 -
    -
    .icon-kaishi -
    -
  • - -
  • - -
    - 关闭 -
    -
    .icon-guanbi -
    -
  • - -
  • - -
    - 下箭头 -
    -
    .icon-xiajiantou -
    -
  • - -
  • - -
    - more -
    -
    .icon-gengduo -
    -
  • - -
  • - -
    - 设置 -
    -
    .icon-shezhi -
    -
  • - -
  • - -
    - 对话-未选 -
    -
    .icon-duihua-weixuan -
    -
  • - -
  • - -
    - 图表-未选 -
    -
    .icon-tubiao-weixuan -
    -
  • - -
  • - -
    - 编组 13备份 3 -
    -
    .icon-a-bianzu13beifen3 -
    -
  • - -
  • - -
    - 编组备份 -
    -
    .icon-bianzubeifen -
    -
  • - -
  • - -
    - 表格 -
    -
    .icon-biaoge1 -
    -
  • - -
  • - -
    - 收藏 (1) -
    -
    .icon-a-shoucang1 -
    -
  • - -
  • - -
    - guthub-未选 -
    -
    .icon-guthub-weixuan1 -
    -
  • - -
  • - -
    - 数据-未选 -
    -
    .icon-shuju-weixuan -
    -
  • - -
  • - -
    - 编组 4 -
    -
    .icon-a-bianzu4 -
    -
  • - -
  • - -
    - 编组 14备份 -
    -
    .icon-a-bianzu14beifen -
    -
  • - -
  • - -
    - guthub-未选 -
    -
    .icon-guthub-weixuan -
    -
  • - -
  • - -
    - 24gl-folderMinus -
    -
    .icon-24gl-folderMinus -
    -
  • - -
  • - -
    - 24gl-folderOpen -
    -
    .icon-24gl-folderOpen -
    -
  • - -
  • - -
    - 24gf-folderOpen -
    -
    .icon-24gf-folderOpen -
    -
  • - -
  • - -
    - 云数据库 -
    -
    .icon-yunshujuku -
    -
  • - -
  • - -
    - 报表 -
    -
    .icon-baobiao -
    -
  • - -
  • - -
    - 工作台 -
    -
    .icon-gongzuotai -
    -
  • - -
  • - -
    - mongodb -
    -
    .icon-mongodb -
    -
  • - -
  • - -
    - Redis -
    -
    .icon-Redis -
    -
  • - -
  • - -
    - HIVE_2 -
    -
    .icon-HIVE -
    -
  • - -
  • - -
    - Kingbase -
    -
    .icon-Kingbase -
    -
  • - -
  • - -
    - 仪表盘 -
    -
    .icon-yibiaopan -
    -
  • - -
  • - -
    - presto -
    -
    .icon-presto_sql -
    -
  • - -
  • - -
    - DB2 -
    -
    .icon-shujukuleixingtubiao-kuozhan- -
    -
  • - -
  • - -
    - oceanbase -
    -
    .icon-oceanbase -
    -
  • - -
  • - -
    - 达梦 -
    -
    .icon-dameng1 -
    -
  • - -
  • - -
    - proxy -
    -
    .icon-proxy -
    -
  • - -
  • - -
    - openai -
    -
    .icon-openai -
    -
  • - -
  • - -
    - 关于 -
    -
    .icon-guanyu -
    -
  • - -
  • - -
    - 衣服 -
    -
    .icon-yifu -
    -
  • - -
  • - -
    - 数据库 -
    -
    .icon-shujuku4 -
    -
  • - -
  • - -
    - 数据源配置 -
    -
    .icon-shujuyuanpeizhi -
    -
  • - -
  • - -
    - 服务器_数据库_jurassic -
    -
    .icon-jurassic_server -
    -
  • - -
  • - -
    - 数据库 -
    -
    .icon-shujuku2 -
    -
  • - -
  • - -
    - 数据库 -
    -
    .icon-shujuku3 -
    -
  • - -
  • - -
    - 数据库数据 -
    -
    .icon-shujukushuju -
    -
  • - -
  • - -
    - 数据库 -
    -
    .icon-shujuku1 -
    -
  • - -
  • - -
    - 配置数据源 -
    -
    .icon-peizhishujuyuan -
    -
  • - -
  • - -
    - SQL历史查询 -
    -
    .icon-SQLlishichaxun -
    -
  • - -
  • - -
    - 重命名 -
    -
    .icon-zhongmingming -
    -
  • - -
  • - -
    - ico_数据查询与统计_预约情况查询 -
    -
    .icon-ico_shujuchaxunyutongji_yuyueqingkuangchaxun -
    -
  • - -
  • - -
    - clickhouse-云数据库ClickHouse -
    -
    .icon-clickhouse-yunshujukuClickHouse -
    -
  • - -
  • - -
    - rds_mariadb -
    -
    .icon-rds_mariadb -
    -
  • - -
  • - -
    - 减少减去减号 -
    -
    .icon-jianshaojianqujianhao -
    -
  • - -
  • - -
    - sqlserver -
    -
    .icon-sqlserver -
    -
  • - -
  • - -
    - sqlite -
    -
    .icon-sqlite -
    -
  • - -
  • - -
    - 缺省页_暂无数据 -
    -
    .icon-queshengye_zanwushuju -
    -
  • - -
  • - -
    - 未完成 -
    -
    .icon-weiwancheng -
    -
  • - -
  • - -
    - 完成-01 -
    -
    .icon-wancheng- -
    -
  • - -
  • - -
    - 成功 -
    -
    .icon-chenggong1 -
    -
  • - -
  • - -
    - 机器人 -
    -
    .icon-jiqiren -
    -
  • - -
  • - -
    - 换一换 -
    -
    .icon-huanyihuan -
    -
  • - -
  • - -
    - icon_infomation -
    -
    .icon-icon_infomation -
    -
  • - -
  • - -
    - key -
    -
    .icon-key1 -
    -
  • - -
  • - -
    - mysql -
    -
    .icon-mysql -
    -
  • - -
  • - -
    - oracle -
    -
    .icon-oracle -
    -
  • - -
  • - -
    - postgresql -
    -
    .icon-postgresql -
    -
  • - -
  • - -
    - h2 -
    -
    .icon-h2 -
    -
  • - -
  • - -
    - cc-schema -
    -
    .icon-cc-schema -
    -
  • - -
  • - -
    - 新建表格 -
    -
    .icon-xinjianbiaoge -
    -
  • - -
  • - -
    - export -
    -
    .icon-export -
    -
  • - -
  • - -
    - 角色管理 -
    -
    .icon-jiaoseguanli -
    -
  • - -
  • - -
    - console -
    -
    .icon-console -
    -
  • - -
  • - -
    - 24gf-folderMinus -
    -
    .icon-24gf-folderMinus -
    -
  • - -
  • - -
    - 查看 -
    -
    .icon-chakan -
    -
  • - -
  • - -
    - 复制_o -
    -
    .icon-fuzhi_o -
    -
  • - -
  • - -
    - 执行 -
    -
    .icon-zhihang -
    -
  • - -
  • - -
    - m-格式化文字 -
    -
    .icon-m-geshihuawenzi -
    -
  • - -
  • - -
    - github-fill -
    -
    .icon-github-fill -
    -
  • - -
  • - -
    - 保存 -
    -
    .icon-baocun2 -
    -
  • - -
  • - -
    - 箭头_向左两次_o -
    -
    .icon-jiantou_xiangzuoliangci_o -
    -
  • - -
  • - -
    - 新建窗口 -
    -
    .icon-xinjianchuangkou -
    -
  • - -
  • - -
    - loading -
    -
    .icon-loading2 -
    -
  • - -
  • - -
    - 链接克隆 -
    -
    .icon-lianjiekelong -
    -
  • - -
  • - -
    - SQL升级文件 -
    -
    .icon-SQLshengjiwenjian -
    -
  • - -
  • - -
    - sql -
    -
    .icon-sql -
    -
  • - -
  • - -
    - 连接流 -
    -
    .icon-lianjieliu -
    -
  • - -
  • - -
    - 跳转/退出 -
    -
    .icon-tiaozhuan -
    -
  • - -
  • - -
    - key -
    -
    .icon-key -
    -
  • - -
  • - -
    - 播放记录 -
    -
    .icon-bofangjilu -
    -
  • - -
  • - -
    - 成功 -
    -
    .icon-chenggong -
    -
  • - -
  • - -
    - 失败 -
    -
    .icon-shibai -
    -
  • - -
  • - -
    - 收回 上下 -
    -
    .icon-shouhuishangxia -
    -
  • - -
  • - -
    - 展开 上下 -
    -
    .icon-zhankaishangxia -
    -
  • - -
  • - -
    - 数据库 -
    -
    .icon-shujuku -
    -
  • - -
  • - -
    - 保存 -
    -
    .icon-baocun -
    -
  • - -
  • - -
    - 查询 -
    -
    .icon-chaxun -
    -
  • - -
  • - -
    - 对勾 -
    -
    .icon-duigou11 -
    -
  • - -
  • - -
    - check -
    -
    .icon-check1 -
    -
  • - -
  • - -
    - 概览 -
    -
    .icon-gailan -
    -
  • - -
  • - -
    - 概览 -
    -
    .icon-huaban2 -
    -
  • - -
  • - -
    - 编辑 -
    -
    .icon-bianji -
    -
  • - -
  • - -
    - 刷新 -
    -
    .icon-shuaxin1 -
    -
  • - -
  • - -
    - 菜单/列表 -
    -
    .icon-caidan -
    -
  • - -
  • - -
    - 表格 -
    -
    .icon-biaoge -
    -
  • - -
  • - -
    - 展开 -
    -
    .icon-zhankai -
    -
  • - -
  • - -
    - 收起 -
    -
    .icon-shouqi -
    -
  • - -
  • - -
    - 主题_o -
    -
    .icon-zhuti_o -
    -
  • - -
  • - -
    - 断开连接 -
    -
    .icon-duankailianjie -
    -
  • - -
  • - -
    - 修改 -
    -
    .icon-xiugai -
    -
  • - -
  • - -
    - 删除 -
    -
    .icon-delete -
    -
  • - -
  • - -
    - 更多 -
    -
    .icon-gengduo1 -
    -
  • - -
  • - -
    - 减少 -
    -
    .icon-jianshao -
    -
  • - -
  • - -
    - 加 -
    -
    .icon-jia -
    -
  • - -
  • - -
    - 加号 -
    -
    .icon-hao -
    -
  • - -
  • - -
    - arrow drop down -
    -
    .icon-right -
    -
  • - -
  • - -
    - search -
    -
    .icon-search1 -
    -
  • - -
  • - -
    - download -
    -
    .icon-download1 -
    -
  • - -
  • - -
    - 向右箭头 -
    -
    .icon-xiangyoujiantou -
    -
  • - -
  • - -
    - 删除线型 -
    -
    .icon-shanchuxianxing -
    -
  • - -
  • - -
    - cross -
    -
    .icon-cross-copy -
    -
  • - -
  • - -
    - 刷新 -
    -
    .icon-shuaxin -
    -
  • - -
  • - -
    - 提醒 -
    -
    .icon-tixing -
    -
  • - -
  • - -
    - 138设置、系统设置、功能设置、属性 -
    -
    .icon-shezhixitongshezhigongnengshezhishuxing -
    -
  • - -
  • - -
    - 执行sql脚本 -
    -
    .icon-zhihangsqljiaoben -
    -
  • - -
  • - -
    - 虚拟数据库管理 -
    -
    .icon-xunishujukuguanli -
    -
  • - -
-
-

font-class 引用

-
- -

font-class 是 Unicode 使用方式的一种变种,主要是解决 Unicode 书写不直观,语意不明确的问题。

-

与 Unicode 使用方式相比,具有如下特点:

-
    -
  • 相比于 Unicode 语意明确,书写更直观。可以很容易分辨这个 icon 是什么。
  • -
  • 因为使用 class 来定义图标,所以当要替换图标时,只需要修改 class 里面的 Unicode 引用。
  • -
-

使用步骤如下:

-

第一步:引入项目下面生成的 fontclass 代码:

-
<link rel="stylesheet" href="./iconfont.css">
-
-

第二步:挑选相应图标并获取类名,应用于页面:

-
<span class="iconfont icon-xxx"></span>
-
-
-

" - iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。

-
-
-
-
-
    - -
  • - -
    right_on_5
    -
    #icon-right_on_5
    -
  • - -
  • - -
    right_off_5-01
    -
    #icon-right_off_5-01
    -
  • - -
  • - -
    left_on_2
    -
    #icon-a-left_on_huaban11
    -
  • - -
  • - -
    left_off
    -
    #icon-a-left_off_huaban1
    -
  • - -
  • - -
    minimize21
    -
    #icon-minimize21
    -
  • - -
  • - -
    restore
    -
    #icon-restore_button2
    -
  • - -
  • - -
    resize
    -
    #icon-resize_button2
    -
  • - -
  • - -
    close
    -
    #icon-close_button2
    -
  • - -
  • - -
    筛选
    -
    #icon-shaixuan
    -
  • - -
  • - -
    排序
    -
    #icon-a-44tubiao-122
    -
  • - -
  • - -
    305信息-线性圆框
    -
    #icon-xinxi-xianxingyuankuang
    -
  • - -
  • - -
    加号
    -
    #icon-jiahao
    -
  • - -
  • - -
    列表
    -
    #icon-liebiao
    -
  • - -
  • - -
    减去
    -
    #icon-jianqu
    -
  • - -
  • - -
    database
    -
    #icon-database
    -
  • - -
  • - -
    筛选
    -
    #icon-shaixuan1
    -
  • - -
  • - -
    刷新
    -
    #icon-shuaxin2
    -
  • - -
  • - -
    加号_o
    -
    #icon-jiahao_o
    -
  • - -
  • - -
    数据库_jurassic
    -
    #icon-jurassic_data
    -
  • - -
  • - -
    权限
    -
    #icon-quanxian
    -
  • - -
  • - -
    sharpicons_add-database
    -
    #icon-sharpicons_add-database
    -
  • - -
  • - -
    组织管理
    -
    #icon-zuzhiguanli-
    -
  • - -
  • - -
    空间
    -
    #icon-moxing-miaobian
    -
  • - -
  • - -
    下箭头-copy
    -
    #icon-xiajiantou1-copy
    -
  • - -
  • - -
    查看
    -
    #icon-chakan2
    -
  • - -
  • - -
    clone
    -
    #icon-clone
    -
  • - -
  • - -
    提交
    -
    #icon-tijiao
    -
  • - -
  • - -
    查看
    -
    #icon-chakan1
    -
  • - -
  • - -
    复制
    -
    #icon-fuzhi
    -
  • - -
  • - -
    icon_answer
    -
    #icon-icon_answer
    -
  • - -
  • - -
    icon_question
    -
    #icon-icon_question
    -
  • - -
  • - -
    发送
    -
    #icon-fasong
    -
  • - -
  • - -
    重启
    -
    #icon-zhongqi
    -
  • - -
  • - -
    提醒
    -
    #icon-tixing2
    -
  • - -
  • - -
    提醒
    -
    #icon-tixing3
    -
  • - -
  • - -
    提醒
    -
    #icon-tixing1
    -
  • - -
  • - -
    升级
    -
    #icon-shengji
    -
  • - -
  • - -
    全局_升级
    -
    #icon-quanju_shengji
    -
  • - -
  • - -
    关于我们
    -
    #icon-guanyuwomen1
    -
  • - -
  • - -
    ico版本更新
    -
    #icon-icobanbengengxin
    -
  • - -
  • - -
    对话气泡
    -
    #icon-duihuaqipao
    -
  • - -
  • - -
    角色权限
    -
    #icon-jiaosequanxian
    -
  • - -
  • - -
    preview
    -
    #icon-preview1
    -
  • - -
  • - -
    导入
    -
    #icon-daoru
    -
  • - -
  • - -
    终止
    -
    #icon-zhongzhi
    -
  • - -
  • - -
    退出
    -
    #icon-tuichu
    -
  • - -
  • - -
    控桩终端
    -
    #icon-kongzhuangzhongduan
    -
  • - -
  • - -
    撤销
    -
    #icon-chexiao1
    -
  • - -
  • - -
    向上
    -
    #icon-xiangshang
    -
  • - -
  • - -
    查看
    -
    #icon-chakan-copy
    -
  • - -
  • - -
    编辑数据_编辑录入操作_jurassic
    -
    #icon-jurassic_edit-data
    -
  • - -
  • - -
    编辑表格_编辑录入操作_jurassic
    -
    #icon-jurassic_edit-table
    -
  • - -
  • - -
    报表数据录入
    -
    #icon-baobiaoshujuluru
    -
  • - -
  • - -
    播放5
    -
    #icon-bofang5
    -
  • - -
  • - -
    清空@3x
    -
    #icon-a-qingkong3x
    -
  • - -
  • - -
    删除
    -
    #icon-shanchu
    -
  • - -
  • - -
    new-document-worksheet
    -
    #icon-newdocumentworksheet
    -
  • - -
  • - -
    file-excel
    -
    #icon-file-excel
    -
  • - -
  • - -
    file-markdown
    -
    #icon-file-markdown
    -
  • - -
  • - -
    file-word
    -
    #icon-file-word
    -
  • - -
  • - -
    HTML5
    -
    #icon-HTML
    -
  • - -
  • - -
    HTML
    -
    #icon-HTML1
    -
  • - -
  • - -
    pdf
    -
    #icon-pdf
    -
  • - -
  • - -
    个人用户
    -
    #icon-gerenyonghu
    -
  • - -
  • - -
    后台管理
    -
    #icon-houtaiguanli
    -
  • - -
  • - -
    字体代码
    -
    #icon-zitidaima
    -
  • - -
  • - -
    版本
    -
    #icon-banben
    -
  • - -
  • - -
    车位管理
    -
    #icon-cheweiguanli
    -
  • - -
  • - -
    dictate
    -
    #icon-dianzhelidaochu
    -
  • - -
  • - -
    circle-f
    -
    #icon-circle-f
    -
  • - -
  • - -
    图表-函数
    -
    #icon-tubiao-hanshu
    -
  • - -
  • - -
    视图管理器
    -
    #icon-shituguanliqi
    -
  • - -
  • - -
    回车
    -
    #icon-huiche
    -
  • - -
  • - -
    缺省
    -
    #icon-quesheng
    -
  • - -
  • - -
    进入箭头
    -
    #icon-jinrujiantou
    -
  • - -
  • - -
    右箭头
    -
    #icon-youjiantou_huaban
    -
  • - -
  • - -
    向右箭头
    -
    #icon-xiangyoujiantou1
    -
  • - -
  • - -
    数据源
    -
    #icon-shujuyuan
    -
  • - -
  • - -
    question
    -
    #icon-question
    -
  • - -
  • - -
    星星-copy
    -
    #icon-xingxing
    -
  • - -
  • - -
    控制台
    -
    #icon-kongzhitai
    -
  • - -
  • - -
    星系
    -
    #icon-xingxi
    -
  • - -
  • - -
    暂无数据 (1)
    -
    #icon-a-zanwushuju1
    -
  • - -
  • - -
    开始
    -
    #icon-kaishi
    -
  • - -
  • - -
    关闭
    -
    #icon-guanbi
    -
  • - -
  • - -
    下箭头
    -
    #icon-xiajiantou
    -
  • - -
  • - -
    more
    -
    #icon-gengduo
    -
  • - -
  • - -
    设置
    -
    #icon-shezhi
    -
  • - -
  • - -
    对话-未选
    -
    #icon-duihua-weixuan
    -
  • - -
  • - -
    图表-未选
    -
    #icon-tubiao-weixuan
    -
  • - -
  • - -
    编组 13备份 3
    -
    #icon-a-bianzu13beifen3
    -
  • - -
  • - -
    编组备份
    -
    #icon-bianzubeifen
    -
  • - -
  • - -
    表格
    -
    #icon-biaoge1
    -
  • - -
  • - -
    收藏 (1)
    -
    #icon-a-shoucang1
    -
  • - -
  • - -
    guthub-未选
    -
    #icon-guthub-weixuan1
    -
  • - -
  • - -
    数据-未选
    -
    #icon-shuju-weixuan
    -
  • - -
  • - -
    编组 4
    -
    #icon-a-bianzu4
    -
  • - -
  • - -
    编组 14备份
    -
    #icon-a-bianzu14beifen
    -
  • - -
  • - -
    guthub-未选
    -
    #icon-guthub-weixuan
    -
  • - -
  • - -
    24gl-folderMinus
    -
    #icon-24gl-folderMinus
    -
  • - -
  • - -
    24gl-folderOpen
    -
    #icon-24gl-folderOpen
    -
  • - -
  • - -
    24gf-folderOpen
    -
    #icon-24gf-folderOpen
    -
  • - -
  • - -
    云数据库
    -
    #icon-yunshujuku
    -
  • - -
  • - -
    报表
    -
    #icon-baobiao
    -
  • - -
  • - -
    工作台
    -
    #icon-gongzuotai
    -
  • - -
  • - -
    mongodb
    -
    #icon-mongodb
    -
  • - -
  • - -
    Redis
    -
    #icon-Redis
    -
  • - -
  • - -
    HIVE_2
    -
    #icon-HIVE
    -
  • - -
  • - -
    Kingbase
    -
    #icon-Kingbase
    -
  • - -
  • - -
    仪表盘
    -
    #icon-yibiaopan
    -
  • - -
  • - -
    presto
    -
    #icon-presto_sql
    -
  • - -
  • - -
    DB2
    -
    #icon-shujukuleixingtubiao-kuozhan-
    -
  • - -
  • - -
    oceanbase
    -
    #icon-oceanbase
    -
  • - -
  • - -
    达梦
    -
    #icon-dameng1
    -
  • - -
  • - -
    proxy
    -
    #icon-proxy
    -
  • - -
  • - -
    openai
    -
    #icon-openai
    -
  • - -
  • - -
    关于
    -
    #icon-guanyu
    -
  • - -
  • - -
    衣服
    -
    #icon-yifu
    -
  • - -
  • - -
    数据库
    -
    #icon-shujuku4
    -
  • - -
  • - -
    数据源配置
    -
    #icon-shujuyuanpeizhi
    -
  • - -
  • - -
    服务器_数据库_jurassic
    -
    #icon-jurassic_server
    -
  • - -
  • - -
    数据库
    -
    #icon-shujuku2
    -
  • - -
  • - -
    数据库
    -
    #icon-shujuku3
    -
  • - -
  • - -
    数据库数据
    -
    #icon-shujukushuju
    -
  • - -
  • - -
    数据库
    -
    #icon-shujuku1
    -
  • - -
  • - -
    配置数据源
    -
    #icon-peizhishujuyuan
    -
  • - -
  • - -
    SQL历史查询
    -
    #icon-SQLlishichaxun
    -
  • - -
  • - -
    重命名
    -
    #icon-zhongmingming
    -
  • - -
  • - -
    ico_数据查询与统计_预约情况查询
    -
    #icon-ico_shujuchaxunyutongji_yuyueqingkuangchaxun
    -
  • - -
  • - -
    clickhouse-云数据库ClickHouse
    -
    #icon-clickhouse-yunshujukuClickHouse
    -
  • - -
  • - -
    rds_mariadb
    -
    #icon-rds_mariadb
    -
  • - -
  • - -
    减少减去减号
    -
    #icon-jianshaojianqujianhao
    -
  • - -
  • - -
    sqlserver
    -
    #icon-sqlserver
    -
  • - -
  • - -
    sqlite
    -
    #icon-sqlite
    -
  • - -
  • - -
    缺省页_暂无数据
    -
    #icon-queshengye_zanwushuju
    -
  • - -
  • - -
    未完成
    -
    #icon-weiwancheng
    -
  • - -
  • - -
    完成-01
    -
    #icon-wancheng-
    -
  • - -
  • - -
    成功
    -
    #icon-chenggong1
    -
  • - -
  • - -
    机器人
    -
    #icon-jiqiren
    -
  • - -
  • - -
    换一换
    -
    #icon-huanyihuan
    -
  • - -
  • - -
    icon_infomation
    -
    #icon-icon_infomation
    -
  • - -
  • - -
    key
    -
    #icon-key1
    -
  • - -
  • - -
    mysql
    -
    #icon-mysql
    -
  • - -
  • - -
    oracle
    -
    #icon-oracle
    -
  • - -
  • - -
    postgresql
    -
    #icon-postgresql
    -
  • - -
  • - -
    h2
    -
    #icon-h2
    -
  • - -
  • - -
    cc-schema
    -
    #icon-cc-schema
    -
  • - -
  • - -
    新建表格
    -
    #icon-xinjianbiaoge
    -
  • - -
  • - -
    export
    -
    #icon-export
    -
  • - -
  • - -
    角色管理
    -
    #icon-jiaoseguanli
    -
  • - -
  • - -
    console
    -
    #icon-console
    -
  • - -
  • - -
    24gf-folderMinus
    -
    #icon-24gf-folderMinus
    -
  • - -
  • - -
    查看
    -
    #icon-chakan
    -
  • - -
  • - -
    复制_o
    -
    #icon-fuzhi_o
    -
  • - -
  • - -
    执行
    -
    #icon-zhihang
    -
  • - -
  • - -
    m-格式化文字
    -
    #icon-m-geshihuawenzi
    -
  • - -
  • - -
    github-fill
    -
    #icon-github-fill
    -
  • - -
  • - -
    保存
    -
    #icon-baocun2
    -
  • - -
  • - -
    箭头_向左两次_o
    -
    #icon-jiantou_xiangzuoliangci_o
    -
  • - -
  • - -
    新建窗口
    -
    #icon-xinjianchuangkou
    -
  • - -
  • - -
    loading
    -
    #icon-loading2
    -
  • - -
  • - -
    链接克隆
    -
    #icon-lianjiekelong
    -
  • - -
  • - -
    SQL升级文件
    -
    #icon-SQLshengjiwenjian
    -
  • - -
  • - -
    sql
    -
    #icon-sql
    -
  • - -
  • - -
    连接流
    -
    #icon-lianjieliu
    -
  • - -
  • - -
    跳转/退出
    -
    #icon-tiaozhuan
    -
  • - -
  • - -
    key
    -
    #icon-key
    -
  • - -
  • - -
    播放记录
    -
    #icon-bofangjilu
    -
  • - -
  • - -
    成功
    -
    #icon-chenggong
    -
  • - -
  • - -
    失败
    -
    #icon-shibai
    -
  • - -
  • - -
    收回 上下
    -
    #icon-shouhuishangxia
    -
  • - -
  • - -
    展开 上下
    -
    #icon-zhankaishangxia
    -
  • - -
  • - -
    数据库
    -
    #icon-shujuku
    -
  • - -
  • - -
    保存
    -
    #icon-baocun
    -
  • - -
  • - -
    查询
    -
    #icon-chaxun
    -
  • - -
  • - -
    对勾
    -
    #icon-duigou11
    -
  • - -
  • - -
    check
    -
    #icon-check1
    -
  • - -
  • - -
    概览
    -
    #icon-gailan
    -
  • - -
  • - -
    概览
    -
    #icon-huaban2
    -
  • - -
  • - -
    编辑
    -
    #icon-bianji
    -
  • - -
  • - -
    刷新
    -
    #icon-shuaxin1
    -
  • - -
  • - -
    菜单/列表
    -
    #icon-caidan
    -
  • - -
  • - -
    表格
    -
    #icon-biaoge
    -
  • - -
  • - -
    展开
    -
    #icon-zhankai
    -
  • - -
  • - -
    收起
    -
    #icon-shouqi
    -
  • - -
  • - -
    主题_o
    -
    #icon-zhuti_o
    -
  • - -
  • - -
    断开连接
    -
    #icon-duankailianjie
    -
  • - -
  • - -
    修改
    -
    #icon-xiugai
    -
  • - -
  • - -
    删除
    -
    #icon-delete
    -
  • - -
  • - -
    更多
    -
    #icon-gengduo1
    -
  • - -
  • - -
    减少
    -
    #icon-jianshao
    -
  • - -
  • - -
    -
    #icon-jia
    -
  • - -
  • - -
    加号
    -
    #icon-hao
    -
  • - -
  • - -
    arrow drop down
    -
    #icon-right
    -
  • - -
  • - -
    search
    -
    #icon-search1
    -
  • - -
  • - -
    download
    -
    #icon-download1
    -
  • - -
  • - -
    向右箭头
    -
    #icon-xiangyoujiantou
    -
  • - -
  • - -
    删除线型
    -
    #icon-shanchuxianxing
    -
  • - -
  • - -
    cross
    -
    #icon-cross-copy
    -
  • - -
  • - -
    刷新
    -
    #icon-shuaxin
    -
  • - -
  • - -
    提醒
    -
    #icon-tixing
    -
  • - -
  • - -
    138设置、系统设置、功能设置、属性
    -
    #icon-shezhixitongshezhigongnengshezhishuxing
    -
  • - -
  • - -
    执行sql脚本
    -
    #icon-zhihangsqljiaoben
    -
  • - -
  • - -
    虚拟数据库管理
    -
    #icon-xunishujukuguanli
    -
  • - -
-
-

Symbol 引用

-
- -

这是一种全新的使用方式,应该说这才是未来的主流,也是平台目前推荐的用法。相关介绍可以参考这篇文章 - 这种用法其实是做了一个 SVG 的集合,与另外两种相比具有如下特点:

-
    -
  • 支持多色图标了,不再受单色限制。
  • -
  • 通过一些技巧,支持像字体那样,通过 font-size, color 来调整样式。
  • -
  • 兼容性较差,支持 IE9+,及现代浏览器。
  • -
  • 浏览器渲染 SVG 的性能一般,还不如 png。
  • -
-

使用步骤如下:

-

第一步:引入项目下面生成的 symbol 代码:

-
<script src="./iconfont.js"></script>
-
-

第二步:加入通用 CSS 代码(引入一次就行):

-
<style>
-.icon {
-  width: 1em;
-  height: 1em;
-  vertical-align: -0.15em;
-  fill: currentColor;
-  overflow: hidden;
-}
-</style>
-
-

第三步:挑选相应图标并获取类名,应用于页面:

-
<svg class="icon" aria-hidden="true">
-  <use xlink:href="#icon-xxx"></use>
-</svg>
-
-
-
- -
-
- - - diff --git a/chat2db-client/src/assets/font/iconfont-he.ttf b/chat2db-client/src/assets/font/iconfont-he.ttf new file mode 100644 index 000000000..6c5de7a91 Binary files /dev/null and b/chat2db-client/src/assets/font/iconfont-he.ttf differ diff --git a/chat2db-client/src/assets/font/iconfont-he.woff b/chat2db-client/src/assets/font/iconfont-he.woff new file mode 100644 index 000000000..7d4155420 Binary files /dev/null and b/chat2db-client/src/assets/font/iconfont-he.woff differ diff --git a/chat2db-client/src/assets/font/iconfont-he.woff2 b/chat2db-client/src/assets/font/iconfont-he.woff2 new file mode 100644 index 000000000..846892de8 Binary files /dev/null and b/chat2db-client/src/assets/font/iconfont-he.woff2 differ diff --git a/chat2db-client/src/assets/font/iconfont.css b/chat2db-client/src/assets/font/iconfont.css index 52ce3cf9e..ce32ae206 100644 --- a/chat2db-client/src/assets/font/iconfont.css +++ b/chat2db-client/src/assets/font/iconfont.css @@ -829,3 +829,10 @@ content: "\e61a"; } +.icon-Phoenix:before { + content: "\e712"; +} + +.icon-wuguan:before { + content: "\ec5f"; +} diff --git a/chat2db-client/src/assets/font/iconfont.js b/chat2db-client/src/assets/font/iconfont.js index 6b3f2a891..cd9f7f677 100644 --- a/chat2db-client/src/assets/font/iconfont.js +++ b/chat2db-client/src/assets/font/iconfont.js @@ -1 +1 @@ -window._iconfont_svg_string_3633546='',function(h){var a=(a=document.getElementsByTagName("script"))[a.length-1],c=a.getAttribute("data-injectcss"),a=a.getAttribute("data-disable-injectsvg");if(!a){var l,t,i,o,v,z=function(a,c){c.parentNode.insertBefore(a,c)};if(c&&!h.__iconfont__svg__cssinject__){h.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(a){console&&console.log(a)}}l=function(){var a,c=document.createElement("div");c.innerHTML=h._iconfont_svg_string_3633546,(c=c.getElementsByTagName("svg")[0])&&(c.setAttribute("aria-hidden","true"),c.style.position="absolute",c.style.width=0,c.style.height=0,c.style.overflow="hidden",c=c,(a=document.body).firstChild?z(c,a.firstChild):a.appendChild(c))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(l,0):(t=function(){document.removeEventListener("DOMContentLoaded",t,!1),l()},document.addEventListener("DOMContentLoaded",t,!1)):document.attachEvent&&(i=l,o=h.document,v=!1,s(),o.onreadystatechange=function(){"complete"==o.readyState&&(o.onreadystatechange=null,p())})}function p(){v||(v=!0,i())}function s(){try{o.documentElement.doScroll("left")}catch(a){return void setTimeout(s,50)}p()}}(window); \ No newline at end of file +window._iconfont_svg_string_3633546='',function(h){var a=(a=document.getElementsByTagName("script"))[a.length-1]; var c=a.getAttribute("data-injectcss"); var a=a.getAttribute("data-disable-injectsvg");if(!a){var l; var t; var i; var o; var v; var z=function(a,c){c.parentNode.insertBefore(a,c)};if(c&&!h.__iconfont__svg__cssinject__){h.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(a){console&&console.log(a)}}l=function(){var a; var c=document.createElement("div");c.innerHTML=h._iconfont_svg_string_3633546,(c=c.getElementsByTagName("svg")[0])&&(c.setAttribute("aria-hidden","true"),c.style.position="absolute",c.style.width=0,c.style.height=0,c.style.overflow="hidden",c=c,(a=document.body).firstChild?z(c,a.firstChild):a.appendChild(c))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(l,0):(t=function(){document.removeEventListener("DOMContentLoaded",t,!1),l()},document.addEventListener("DOMContentLoaded",t,!1)):document.attachEvent&&(i=l,o=h.document,v=!1,s(),o.onreadystatechange=function(){"complete"==o.readyState&&(o.onreadystatechange=null,p())})}function p(){v||(v=!0,i())}function s(){try{o.documentElement.doScroll("left")}catch(a){return void setTimeout(s,50)}p()}}(window); diff --git a/chat2db-client/src/assets/img/databaseImg/phoenixLogo.png b/chat2db-client/src/assets/img/databaseImg/phoenixLogo.png new file mode 100644 index 000000000..50bd4a59d Binary files /dev/null and b/chat2db-client/src/assets/img/databaseImg/phoenixLogo.png differ diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.tsx index c82f2063c..82bc05d02 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.tsx @@ -1,14 +1,15 @@ -import React, { useContext, useEffect, useImperativeHandle, ForwardedRef, forwardRef } from 'react'; -import styles from './index.less'; -import classnames from 'classnames'; -import { Form, Input } from 'antd'; -import { Context } from '../index'; -import { IBaseInfo } from '@/typings'; import { DatabaseTypeCode } from '@/constants'; import i18n from '@/i18n'; +import { IBaseInfo } from '@/typings'; +import { Form, Input } from 'antd'; +import classnames from 'classnames'; +import { ForwardedRef, forwardRef, useContext, useEffect, useImperativeHandle } from 'react'; +import { Context } from '../index'; +import styles from './index.less'; export interface IBaseInfoRef { getBaseInfo: () => IBaseInfo; + setTableComment: (comment: string) => void; } interface IProps { @@ -24,6 +25,7 @@ const BaseInfo = forwardRef((props: IProps, ref: ForwardedRef) => form.setFieldsValue({ name: tableDetails.name, comment: tableDetails.comment, + aiComment: tableDetails.aiComment, charset: tableDetails.charset, engine: tableDetails.engine, incrementValue: tableDetails.incrementValue, @@ -36,6 +38,9 @@ const BaseInfo = forwardRef((props: IProps, ref: ForwardedRef) => useImperativeHandle(ref, () => ({ getBaseInfo, + setTableComment: (comment: string) => { + form.setFieldsValue({ comment:comment,aiComment:comment }); + }, })); return ( diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx index ad6b6718f..55431778b 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx @@ -1,19 +1,19 @@ -import React, { useContext, useEffect, useState, useRef, forwardRef, ForwardedRef, useImperativeHandle } from 'react'; -import styles from './index.less'; -import classnames from 'classnames'; +import CustomSelect from '@/components/CustomSelect'; +import Iconfont from '@/components/Iconfont'; +import { DatabaseTypeCode, EditColumnOperationType, NullableType } from '@/constants'; +import i18n from '@/i18n'; +import { IColumnItemNew, IColumnTypes } from '@/typings'; import { MenuOutlined } from '@ant-design/icons'; import { DndContext, type DragEndEvent } from '@dnd-kit/core'; import { restrictToVerticalAxis } from '@dnd-kit/modifiers'; -import { Table, InputNumber, Input, Form, Select, Checkbox } from 'antd'; -import { v4 as uuidv4 } from 'uuid'; import { arrayMove, SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; +import { Checkbox, Form, Input, InputNumber, Select, Table } from 'antd'; +import classnames from 'classnames'; +import React, { ForwardedRef, forwardRef, useContext, useEffect, useImperativeHandle, useRef, useState } from 'react'; +import { v4 as uuidv4 } from 'uuid'; import { Context } from '../index'; -import { IColumnItemNew, IColumnTypes } from '@/typings'; -import i18n from '@/i18n'; -import { EditColumnOperationType, DatabaseTypeCode, NullableType } from '@/constants'; -import CustomSelect from '@/components/CustomSelect'; -import Iconfont from '@/components/Iconfont'; +import styles from './index.less'; interface RowProps extends React.HTMLAttributes { 'data-row-key': string; @@ -29,6 +29,7 @@ interface IEditingConfig extends IColumnTypes { // 本组件暴露给父组件的方法 export interface IColumnListRef { getColumnListInfo: () => IColumnItemNew[]; + setColumnComment: (columnName: string, comment: string) => void; } const Row = ({ children, ...props }: RowProps) => { @@ -71,6 +72,7 @@ const createInitialData = () => { defaultValue: null, autoIncrement: null, comment: null, + aiComment: null, primaryKey: null, primaryKeyOrder: null, schemaName: null, @@ -109,8 +111,10 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) form.setFieldsValue({ ...record }); setEditingData(record); // 根据当前字段类型,设置编辑配置 + // 需要匹配基础类型名 + const baseColumnType = record.dataType?.toUpperCase().trim(); databaseSupportField.columnTypes.forEach((i) => { - if (i.typeName === record.columnType) { + if (i.typeName === baseColumnType) { setEditingConfig({ ...i, editKey: record.key!, @@ -178,14 +182,14 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) }, { title: i18n('editTable.label.columnType'), - dataIndex: 'columnType', + dataIndex: 'dataType', width: '200px', render: (text: string, record: IColumnItemNew) => { const editable = isEditing(record); return (
{editable ? ( - +
+ {React.Children.map(children, (child) => { + if ((child as React.ReactElement).key === 'sort') { + return React.cloneElement(child as React.ReactElement, { + children: ( + + ), + }); + } + return child; + })} + + ); +}; + + +const ForeignKeyList = forwardRef((props: IProps, ref: ForwardedRef) => { + const { tableDetails } = useContext(Context); + const [dataSource, setDataSource] = useState([createInitialData()]); + const [form] = Form.useForm(); + const [editingData, setEditingData] = useState(null); + const [syncing, setSyncing] = useState(false); + const tableRef = useRef(null); + + const isEditing = (record: IForeignKeyItemNew) => record.key === editingData?.key; + const handleFieldsChange = (field: any) => { + const { value } = field[0]; + const { name: nameList } = field[0]; + const name = nameList[0]; + const newData = dataSource.map((item) => { + if (item.key === editingData?.key) { + // 判断当前数据是新增的数据还是编辑后的数据 + let editStatus = item.editStatus; + if (editStatus !== EditColumnOperationType.Add) { + editStatus = EditColumnOperationType.Modify; + } + const editingDataItem = { + ...item, + [name]: value, + editStatus, + }; + return editingDataItem; + } + return item; + }); + setDataSource(newData); + }; + + const edit = (record: IForeignKeyItemNew) => { + if (record.key) { + form.setFieldsValue({ ...record }); + setEditingData(record); + } + }; + + // 整理服务端返回的数据,构造为前端需要的数据结构 + useEffect(() => { + if (tableDetails) { + const list = + tableDetails?.foreignKeyList?.map((t) => { + return { + ...t, + key: uuidv4(), + }; + }) || []; + setDataSource(list); + } + }, [tableDetails]); + + const columns = [ + { + key: 'sort', + width: '40px', + align: 'center', + fixed: 'left', + }, + { + title: i18n('editTable.label.foreignKeyName'), + dataIndex: 'name', + width: '160px', + fixed: 'left', + render: (text: string, record: IForeignKeyItemNew) => { + const editable = isEditing(record); + return ( +
+ {editable ? ( + + + + ) : ( +
{text}
+ )} +
+ ); + }, + }, + { + title: i18n('editTable.label.column'), + dataIndex: 'column', + width: '160px', + render: (text: string, record: IForeignKeyItemNew) => { + const editable = isEditing(record); + return ( +
+ {editable ? ( + + + + ) : ( +
{text}
+ )} +
+ ); + }, + }, + { + title: i18n('editTable.label.referencedTable'), + dataIndex: 'referencedTable', + width: '160px', + render: (text: string, record: IForeignKeyItemNew) => { + const editable = isEditing(record); + return ( +
+ {editable ? ( + + + + ) : ( +
{text}
+ )} +
+ ); + }, + }, + { + title: i18n('editTable.label.referencedColumn'), + dataIndex: 'referencedColumn', + width: '160px', + render: (text: string, record: IForeignKeyItemNew) => { + const editable = isEditing(record); + return ( +
+ {editable ? ( + + + + ) : ( +
{text}
+ )} +
+ ); + }, + }, + { + title: i18n('editTable.label.updateRule'), + dataIndex: 'updateRule', + width: '160px', + render: (text: number, record: IForeignKeyItemNew) => { + const editable = isEditing(record); + return ( +
+ {editable ? ( + + + + ) : ( +
+ {text === 0 ? 'CASCADE' : + text === 1 ? 'SET NULL' : + text === 2 ? 'NO ACTION' : + text === 3 ? 'RESTRICT' : + text === 4 ? 'SET DEFAULT' : 'UNKNOWN'} +
+ )} +
+ ); + }, + }, + { + title: i18n('editTable.label.deleteRule'), + dataIndex: 'deleteRule', + width: '160px', + render: (text: number, record: IForeignKeyItemNew) => { + const editable = isEditing(record); + return ( +
+ {editable ? ( + + + + ) : ( +
+ {text === 0 ? 'CASCADE' : + text === 1 ? 'SET NULL' : + text === 2 ? 'NO ACTION' : + text === 3 ? 'RESTRICT' : + text === 4 ? 'SET DEFAULT' : 'UNKNOWN'} +
+ )} +
+ ); + }, + }, + { + title: i18n('editTable.label.comment'), + dataIndex: 'comment', + render: (text: string, record: IForeignKeyItemNew) => { + const editable = isEditing(record); + return editable ? ( + + + + ) : ( +
{text}
+ ); + }, + }, + { + title: i18n('editTable.label.sourceType'), + dataIndex: 'sourceType', + width: '100px', + render: (text: string, record: IForeignKeyItemNew) => { + const isVirtual = text === 'VIRTUAL' || record.editable; + return ( + + + {isVirtual ? 'V' : 'R'} + + + ); + }, + }, + { + width: '40px', + render: (text: string, record: IForeignKeyItemNew) => { + return ( +
{ + deleteData(record); + }} + > +
+ +
+
+ ); + }, + }, + ]; + + const onDragEnd = ({ active, over }: DragEndEvent) => { + if (active.id !== over?.id) { + setDataSource((previous) => { + const activeIndex = previous.findIndex((i) => i.key === active.id); + const overIndex = previous.findIndex((i) => i.key === over?.id); + return arrayMove(previous, activeIndex, overIndex); + }); + } + }; + + const addData = () => { + const newData = { + ...createInitialData(), + }; + setDataSource([...dataSource, newData]); + edit(newData); + setTimeout(() => { + tableRef.current?.scrollTo(0, tableRef.current?.scrollHeight + 100); + }, 0); + }; + + const deleteData = (record) => { + let list: any = []; + if (record?.editStatus === EditColumnOperationType.Add) { + list = dataSource.filter((i) => i.key !== record?.key); + } else { + list = dataSource.map((i) => { + if (i.key === record?.key) { + setEditingData(null); + return { + ...i, + editStatus: EditColumnOperationType.Delete, + }; + } + return i; + }); + } + setDataSource(list); + }; + + const handleSync = async () => { + if (!tableDetails) return; + const { dataSourceId, databaseName, schemaName, name: tableName } = tableDetails as any; + if (!dataSourceId || !databaseName || !tableName) { + message.warning(i18n('editTable.message.syncFKWarning')); + return; + } + setSyncing(true); + try { + const result = await sqlService.syncForeignKeys({ + dataSourceId: Number(dataSourceId), + databaseName, + schemaName, + tableName, + }); + message.success(i18n('editTable.message.syncFKSuccess', [result?.added || 0, result?.deleted || 0, result?.unchanged || 0])); + } catch (error) { + message.error(i18n('editTable.message.syncFKError')); + } finally { + setSyncing(false); + } + }; + + useImperativeHandle(ref, () => ({ + getForeignKeyListInfo: () => { + return dataSource.map((i) => { + const data = { + ...i, + }; + delete data.key; + return data; + }); + }, + })); + + return ( +
+
+
+ + i.key!)} strategy={verticalListSortingStrategy}> +
({ + onClick: () => { + edit(record); + }, + })} + pagination={false} + rowKey="key" + columns={columns as any} + scroll={{ x: '100%' }} + dataSource={dataSource.filter((i) => i.editStatus !== EditColumnOperationType.Delete)} + /> + + +
+
+ + {i18n('editTable.button.addForeignKey')} +
+ +
+ {syncing ? : } + {i18n('editTable.button.syncForeignKeys')} +
+
+
+ + + + ); +}); + +export default ForeignKeyList; diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx index 4de4a1c05..99b3a0599 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx @@ -417,7 +417,7 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = setIncludeColModalOpen(false); }} maskClosable={false} - destroyOnClose={true} + destroyOnHidden={true} > diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx index 4c8e7f2ae..2eaf6a963 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx @@ -1,17 +1,20 @@ -import React, { memo, useRef, useState, createContext, useEffect, useMemo } from 'react'; -import { Button, Modal, message } from 'antd'; +import ExecuteSQL from '@/components/ExecuteSQL'; +import LoadingContent from '@/components/Loading/LoadingContent'; +import { DatabaseTypeCode, WorkspaceTabType } from '@/constants'; import i18n from '@/i18n'; +import sqlService, { IModifyTableSqlParams } from '@/service/sql'; +import { IColumnTypes, IEditTableInfo, IWorkspaceTab } from '@/typings'; +import { Button, Modal, message, Spin } from 'antd'; +import classnames from 'classnames'; import lodash from 'lodash'; +import React, { createContext, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import BaseInfo, { IBaseInfoRef } from './BaseInfo'; +import ColumnList, { IColumnListRef } from './ColumnList'; +import ForeignKeyList, { IForeignKeyListRef } from './ForeignKeyList'; import styles from './index.less'; -import classnames from 'classnames'; import IndexList, { IIndexListRef } from './IndexList'; -import ColumnList, { IColumnListRef } from './ColumnList'; -import BaseInfo, { IBaseInfoRef } from './BaseInfo'; -import sqlService, { IModifyTableSqlParams } from '@/service/sql'; -import ExecuteSQL from '@/components/ExecuteSQL'; -import { IEditTableInfo, IWorkspaceTab, IColumnTypes } from '@/typings'; -import { DatabaseTypeCode, WorkspaceTabType } from '@/constants'; -import LoadingContent from '@/components/Loading/LoadingContent'; +import { setCurrentWorkspaceExtend, setPendingAiChat, ITableCommentResult } from '@/pages/main/workspace/store/common'; + interface IProps { dataSourceId: number; databaseName: string; @@ -35,6 +38,7 @@ interface IContext extends IProps { baseInfoRef: React.RefObject; columnListRef: React.RefObject; indexListRef: React.RefObject; + foreignKeyListRef: React.RefObject; databaseSupportField: IDatabaseSupportField; } @@ -72,9 +76,11 @@ export default memo((props: IProps) => { const [tableDetails, setTableDetails] = useState({} as any); const [oldTableDetails, setOldTableDetails] = useState({} as any); const [viewSqlModal, setViewSqlModal] = useState(false); + const [guessLoading, setGuessLoading] = useState(false); const baseInfoRef = useRef(null); const columnListRef = useRef(null); const indexListRef = useRef(null); + const foreignKeyListRef = useRef(null); const [appendValue, setAppendValue] = useState(''); const tabList = useMemo(() => { return [ @@ -96,6 +102,12 @@ export default memo((props: IProps) => { key: 'index', component: , }, + { + index: 3, + title: i18n('editTable.tab.foreignKeyInfo'), + key: 'foreignKey', + component: , + }, ]; }, []); const [currentTab, setCurrentTab] = useState(tabList[0]); @@ -210,6 +222,7 @@ export default memo((props: IProps) => { ...baseInfoRef.current.getBaseInfo(), columnList: columnListRef.current.getColumnListInfo()!, indexList: indexListRef.current.getIndexListInfo()!, + foreignKeyList: foreignKeyListRef.current?.getForeignKeyListInfo(), }; const params: IModifyTableSqlParams = { @@ -231,6 +244,40 @@ export default memo((props: IProps) => { } } + const handleCommentGenerated = useCallback((result: ITableCommentResult) => { + if (result.table_comment) { + baseInfoRef.current?.setTableComment(result.table_comment); + } + if (result.column_comments && result.column_comments.length > 0) { + result.column_comments.forEach((col) => { + columnListRef.current?.setColumnComment(col.column_name, col.comment); + }); + } + message.success('AI 注释已应用到表单,请查看并保存'); + }, []); + + const openAiChatForGuess = useCallback(() => { + if (!tableName) { + message.warning('请先选择一个表'); + return; + } + + setGuessLoading(true); + setPendingAiChat({ + dataSourceId, + databaseName, + schemaName, + tableNames: [tableName], + message: `请为表 ${tableName} 及其所有字段生成合适的中文注释`, + promptType: 'NL_2_COMMENT', + onCommentGenerated: handleCommentGenerated, + }); + + setCurrentWorkspaceExtend('ai'); + setGuessLoading(false); + message.success('已切换到 AI 助手,请在 AI 聊天面板中查看推荐结果'); + }, [dataSourceId, databaseName, schemaName, tableName, handleCommentGenerated]); + const executeSuccessCallBack = () => { setViewSqlModal(false); message.success(i18n('common.text.successfulExecution')); @@ -259,6 +306,7 @@ export default memo((props: IProps) => { baseInfoRef, columnListRef, indexListRef, + foreignKeyListRef, databaseSupportField, databaseType, }} @@ -278,6 +326,13 @@ export default memo((props: IProps) => { ); })} +
+ + + +
- { width="60vw" maskClosable={false} footer={false} - destroyOnClose={true} + destroyOnHidden={true} > { + const { + compareResult, selectedTableDiffs, migrationExecuting, migrationResult, + } = useSchemaDiffStore(); + const [confirmVisible, setConfirmVisible] = useState(false); + + const pendingStatements = useMemo(() => { + if (!compareResult?.tableDiffs) return []; + const stmts: { tableName: string; sql: string }[] = []; + for (const td of compareResult.tableDiffs) { + if (selectedTableDiffs[td.tableName] && td.ddlStatement) { + const ddlStatements = td.ddlStatements?.length ? td.ddlStatements : [td.ddlStatement]; + for (const ddlStatement of ddlStatements) { + if (ddlStatement.trim().length > 0) { + stmts.push({ tableName: td.tableName, sql: ddlStatement.trim() }); + } + } + } + } + return stmts; + }, [compareResult, selectedTableDiffs]); + + const selectedCount = useMemo( + () => Object.values(selectedTableDiffs).filter(Boolean).length, + [selectedTableDiffs], + ); + + const allSelected = useMemo( + () => { + const changed = (compareResult?.tableDiffs || []).filter(td => td.diffType !== 'UNCHANGED'); + return changed.length > 0 && changed.every(td => selectedTableDiffs[td.tableName]); + }, + [compareResult, selectedTableDiffs], + ); + + const handleSelectAll = useCallback(() => { + const changed = (compareResult?.tableDiffs || []).filter(td => td.diffType !== 'UNCHANGED'); + const newSelected: Record = {}; + changed.forEach(td => { newSelected[td.tableName] = !allSelected; }); + setSelectedTableDiffs(newSelected); + }, [compareResult, allSelected]); + + const handleMigrate = useCallback(async () => { + if (!compareResult) return; + const stmts = pendingStatements.map(s => s.sql); + if (stmts.length === 0) { + message.warning('No statements to execute'); + return; + } + + setMigrationExecuting(true); + setMigrationResult(null); + try { + const targetDataSourceId = compareResult.targetKey?.split('.')[0] + ? parseInt(compareResult.targetKey.split('.')[0]) : 0; + const targetDatabaseName = compareResult.targetKey?.split('.')[1] || ''; + const result = await sqlService.migrateSchema({ + targetDataSourceId, + targetDatabaseName, + ddlStatements: stmts, + continueOnError: true, + }); + setMigrationResult(result); + if (result.success) { + message.success(i18n('schemaDiff.migrateSuccess')); + } else { + message.error(i18n('schemaDiff.migrateFail')); + } + } catch (e: any) { + message.error(e?.message || 'Migration failed'); + } finally { + setMigrationExecuting(false); + setConfirmVisible(false); + } + }, [compareResult, pendingStatements]); + + const handleOpenConfirm = useCallback(() => { + setConfirmVisible(true); + }, []); + + return ( +
+
+
+ {i18n('schemaDiff.ddlPreview')} + + {selectedCount} tables selected, {pendingStatements.length} statements + +
+
+ + +
+
+ + {pendingStatements.length > 0 && ( +
+ {pendingStatements.map((stmt, i) => ( +
+ {i + 1} + {stmt.tableName} + {stmt.sql} + {(() => { + const r = migrationResult?.statementResults?.find(sr => sr.sequence === i + 1); + return r ? ( + + {r.success ? 'OK' : 'FAIL'} + + ) : null; + })()} +
+ ))} +
+ )} + + {migrationResult && ( +
+
+ {i18n('schemaDiff.migrateResult')}: + {i18n('schemaDiff.successCount')}: {migrationResult.successCount} + {' | '} + {i18n('schemaDiff.failCount')}: {migrationResult.failCount} +
+ {migrationResult.statementResults.filter(r => !r.success).length > 0 && ( +
+ {migrationResult.statementResults.filter(r => !r.success).map(r => ( +
+ #{r.sequence} {r.sql?.substring(0, 100)}... +
{r.errorMessage}
+
+ ))} +
+ )} +
+ )} + + setConfirmVisible(false)} + confirmLoading={migrationExecuting} + > +

{i18n('schemaDiff.migrateConfirmMessage').replace('{count}', String(pendingStatements.length))}

+
+
+ ); +}); + +export default MigrationPanel; diff --git a/chat2db-client/src/blocks/SchemaDiff/ResultPanel/DetailView/index.less b/chat2db-client/src/blocks/SchemaDiff/ResultPanel/DetailView/index.less new file mode 100644 index 000000000..78d599930 --- /dev/null +++ b/chat2db-client/src/blocks/SchemaDiff/ResultPanel/DetailView/index.less @@ -0,0 +1,52 @@ +.detailView { + padding: 12px; + + .detailHeader { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 12px; + + .tableName { + font-size: 16px; + font-weight: 600; + color: var(--color-text); + } + } + + .ddlBlock { + background: var(--color-fill-tertiary); + border: 1px solid var(--color-border); + border-radius: 4px; + padding: 12px; + font-size: 12px; + overflow-x: auto; + white-space: pre-wrap; + word-break: break-all; + max-height: 400px; + overflow-y: auto; + font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace; + line-height: 1.6; + color: var(--color-text); + } + + .operationCell { + :global { + .ant-tag { + margin-inline-end: 0; + } + + .ant-btn-sm { + padding: 0 2px; + } + } + } + + .empty { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + color: var(--color-text-tertiary); + } +} diff --git a/chat2db-client/src/blocks/SchemaDiff/ResultPanel/DetailView/index.tsx b/chat2db-client/src/blocks/SchemaDiff/ResultPanel/DetailView/index.tsx new file mode 100644 index 000000000..ea71beaa3 --- /dev/null +++ b/chat2db-client/src/blocks/SchemaDiff/ResultPanel/DetailView/index.tsx @@ -0,0 +1,266 @@ +import React, { memo, useMemo, useState } from 'react'; +import { Button, Modal, Space, Table, Tabs, Tag } from 'antd'; + +import { i18n } from '@/i18n'; +import { IColumnDiff, IFieldDiff, IForeignKeyDiff, IIndexDiff, ITableDiff } from '@/typings/schemaDiff'; +import styles from './index.less'; + +interface DetailViewProps { + tableDiff: ITableDiff; +} + +const changeTypeColor: Record = { + ADD: 'success', + MODIFY: 'warning', + DELETE: 'error', +}; + +const changeTypeLabel: Record = { + ADD: i18n('schemaDiff.added'), + MODIFY: i18n('schemaDiff.modified'), + DELETE: i18n('schemaDiff.removed'), +}; + +interface IDiffRow { + key: string; + changeType: string; + name?: string; + changedFields?: IFieldDiff[]; + [key: string]: any; +} + +const formatDiffValue = (value?: string) => (value === undefined || value === null ? '-' : value); + +const diffColumns = [ + { title: i18n('schemaDiff.diffField'), dataIndex: 'fieldName', key: 'fieldName', width: 160 }, + { + title: i18n('schemaDiff.sourceValue'), + dataIndex: 'sourceValue', + key: 'sourceValue', + ellipsis: true, + render: formatDiffValue, + }, + { + title: i18n('schemaDiff.targetValue'), + dataIndex: 'targetValue', + key: 'targetValue', + ellipsis: true, + render: formatDiffValue, + }, +]; + +const buildOperationColumn = (onViewDiff: (row: IDiffRow) => void) => ({ + title: i18n('schemaDiff.operation'), + dataIndex: 'changeType', + key: 'changeType', + width: 118, + render: (v: string, row: IDiffRow) => ( + + {changeTypeLabel[v]} + {row.changedFields?.length ? ( + + ) : null} + + ), +}); + +const buildColumnColumns = (onViewDiff: (row: IDiffRow) => void) => [ + buildOperationColumn(onViewDiff), + { title: i18n('schemaDiff.columnName'), dataIndex: 'name', key: 'name', width: 140 }, + { title: i18n('schemaDiff.columnType'), dataIndex: 'columnType', key: 'columnType', width: 120 }, + { title: 'Size', dataIndex: 'size', key: 'size', width: 60 }, + { + title: i18n('schemaDiff.nullable'), + dataIndex: 'nullable', + key: 'nullable', + width: 60, + render: (v: any) => (v ? 'YES' : 'NO'), + }, + { title: i18n('schemaDiff.defaultValue'), dataIndex: 'defaultValue', key: 'defaultValue', width: 100 }, + { title: i18n('schemaDiff.comment'), dataIndex: 'comment', key: 'comment', ellipsis: true }, +]; + +const buildIndexColumns = (onViewDiff: (row: IDiffRow) => void) => [ + buildOperationColumn(onViewDiff), + { title: i18n('schemaDiff.indexName'), dataIndex: 'name', key: 'name', width: 140 }, + { title: i18n('schemaDiff.indexType'), dataIndex: 'indexType', key: 'indexType', width: 100 }, + { + title: i18n('schemaDiff.unique'), + dataIndex: 'unique', + key: 'unique', + width: 60, + render: (v: any) => (v ? 'YES' : 'NO'), + }, +]; + +const buildFkColumns = (onViewDiff: (row: IDiffRow) => void) => [ + buildOperationColumn(onViewDiff), + { title: i18n('schemaDiff.foreignKeyName'), dataIndex: 'name', key: 'name', width: 140 }, + { title: i18n('schemaDiff.referencedTable'), dataIndex: 'refTable', key: 'refTable', width: 120 }, + { title: i18n('schemaDiff.referencedColumn'), dataIndex: 'refColumn', key: 'refColumn', width: 120 }, +]; + +function buildColumnRows(diffs: IColumnDiff[]): IDiffRow[] { + return (diffs || []).map((d) => { + const col = d.targetColumn || d.sourceColumn || {}; + return { + key: `${d.changeType}-${col.name}`, + changeType: d.changeType, + name: col.name, + columnType: col.dataType || col.columnType || '-', + size: col.columnSize, + nullable: col.nullable, + defaultValue: col.defaultValue, + comment: col.comment, + changedFields: d.changedFields, + }; + }); +} + +function buildIndexRows(diffs: IIndexDiff[]): IDiffRow[] { + return (diffs || []).map((d) => { + const idx = d.targetIndex || d.sourceIndex || {}; + return { + key: `${d.changeType}-${idx.name}`, + changeType: d.changeType, + name: idx.name, + indexType: idx.type, + unique: idx.unique, + changedFields: d.changedFields, + }; + }); +} + +function buildFkRows(diffs: IForeignKeyDiff[]): IDiffRow[] { + return (diffs || []).map((d) => { + const fk = d.targetForeignKey || d.sourceForeignKey || {}; + return { + key: `${d.changeType}-${fk.name}`, + changeType: d.changeType, + name: fk.name, + refTable: fk.referencedTable, + refColumn: fk.referencedColumn, + changedFields: d.changedFields, + }; + }); +} + +const DetailView: React.FC = memo(({ tableDiff }) => { + const [currentDiff, setCurrentDiff] = useState(null); + const hasColumns = tableDiff.columnDiffs && tableDiff.columnDiffs.length > 0; + const hasIndexes = tableDiff.indexDiffs && tableDiff.indexDiffs.length > 0; + const hasFKs = tableDiff.foreignKeyDiffs && tableDiff.foreignKeyDiffs.length > 0; + const hasTableOptions = !!tableDiff.tableOptionDiffs?.length; + const hasDdl = tableDiff.ddlStatement; + const columnColumns = useMemo(() => buildColumnColumns(setCurrentDiff), []); + const indexColumns = useMemo(() => buildIndexColumns(setCurrentDiff), []); + const fkColumns = useMemo(() => buildFkColumns(setCurrentDiff), []); + + if (!hasColumns && !hasIndexes && !hasFKs && !hasTableOptions && !hasDdl) { + return
{i18n('schemaDiff.noChanges')}
; + } + + const tabItems = []; + if (hasColumns) { + tabItems.push({ + key: 'columns', + label: `${i18n('schemaDiff.columns')} (${tableDiff.columnDiffs!.length})`, + children: ( +
+ ), + }); + } + if (hasIndexes) { + tabItems.push({ + key: 'indexes', + label: `${i18n('schemaDiff.indexes')} (${tableDiff.indexDiffs!.length})`, + children: ( +
+ ), + }); + } + if (hasFKs) { + tabItems.push({ + key: 'foreignKeys', + label: `${i18n('schemaDiff.foreignKeys')} (${tableDiff.foreignKeyDiffs!.length})`, + children: ( +
+ ), + }); + } + if (hasTableOptions) { + tabItems.push({ + key: 'tableOptions', + label: `${i18n('schemaDiff.tableOptions')} (${tableDiff.tableOptionDiffs!.length})`, + children: ( +
+ ), + }); + } + if (hasDdl) { + tabItems.push({ + key: 'ddl', + label: i18n('schemaDiff.ddlPreview'), + children: ( +
{tableDiff.ddlStatement}
+ ), + }); + } + + return ( +
+
+ {tableDiff.tableName} + + {i18n(tableDiff.diffType === 'MODIFIED' ? 'schemaDiff.modified' : tableDiff.diffType === 'ADDED' ? 'schemaDiff.added' : 'schemaDiff.removed')} + +
+ + setCurrentDiff(null)} + > +
+ + + ); +}); + +export default DetailView; diff --git a/chat2db-client/src/blocks/SchemaDiff/ResultPanel/index.less b/chat2db-client/src/blocks/SchemaDiff/ResultPanel/index.less new file mode 100644 index 000000000..c92f5a38e --- /dev/null +++ b/chat2db-client/src/blocks/SchemaDiff/ResultPanel/index.less @@ -0,0 +1,110 @@ +.resultPanel { + display: flex; + flex: 1; + gap: 12px; + min-height: 200px; + + .tableList { + width: 320px; + min-width: 260px; + border: 1px solid var(--color-border); + border-radius: 6px; + overflow-y: auto; + background: var(--color-bg-subtle); + + .listHeader { + padding: 8px 12px; + font-weight: 600; + font-size: 13px; + border-bottom: 1px solid var(--color-border); + background: var(--color-bg-container); + position: sticky; + top: 0; + z-index: 1; + + .count { + color: var(--color-text-tertiary); + font-weight: 400; + margin-left: 4px; + } + } + + .tableItem { + padding: 8px 12px; + cursor: pointer; + border-bottom: 1px solid var(--color-border); + transition: background 0.2s; + + &:hover { + background: var(--color-fill-tertiary); + } + + &.active { + background: var(--color-fill-secondary); + } + + .tableItemHeader { + display: flex; + align-items: center; + gap: 6px; + + input[type="checkbox"] { + margin: 0; + } + + .diffBadge { + font-size: 11px; + padding: 0 4px; + border-radius: 3px; + font-weight: 500; + + &.added { background: var(--color-success-bg); color: var(--color-success); } + &.removed { background: var(--color-error-bg); color: var(--color-error); } + &.modified { background: var(--color-warning-bg); color: var(--color-warning); } + &.unchanged { background: var(--color-fill-tertiary); color: var(--color-text-tertiary); } + } + + .tableItemName { + font-weight: 500; + font-size: 13px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + color: var(--color-text); + } + } + + .tableItemMeta { + display: flex; + gap: 8px; + font-size: 11px; + color: var(--color-text-tertiary); + margin-top: 2px; + padding-left: 38px; + } + } + + .noData { + padding: 40px; + text-align: center; + color: var(--color-text-tertiary); + } + } + + .detailArea { + flex: 1; + border: 1px solid var(--color-border); + border-radius: 6px; + overflow-y: auto; + background: var(--color-bg-container); + + .noSelection { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + color: var(--color-text-tertiary); + font-size: 14px; + } + } +} diff --git a/chat2db-client/src/blocks/SchemaDiff/ResultPanel/index.tsx b/chat2db-client/src/blocks/SchemaDiff/ResultPanel/index.tsx new file mode 100644 index 000000000..12d4330e1 --- /dev/null +++ b/chat2db-client/src/blocks/SchemaDiff/ResultPanel/index.tsx @@ -0,0 +1,100 @@ +import React, { memo, useMemo } from 'react'; +import classnames from 'classnames'; + +import { i18n } from '@/i18n'; + + +import { useSchemaDiffStore, setSelectedTableDiffs, setDetailViewTableName } from '../store'; +import DetailView from './DetailView'; +import styles from './index.less'; + +const diffTypeLabel: Record = { + ADDED: 'schemaDiff.added', + REMOVED: 'schemaDiff.removed', + MODIFIED: 'schemaDiff.modified', + UNCHANGED: 'schemaDiff.unchanged', +}; + +const diffTypeClass: Record = { + ADDED: styles.added, + REMOVED: styles.removed, + MODIFIED: styles.modified, + UNCHANGED: styles.unchanged, +}; + +const ResultPanel: React.FC = memo(() => { + const { compareResult, selectedTableDiffs, detailViewTableName } = useSchemaDiffStore(); + + const tableDiffs = useMemo(() => compareResult?.tableDiffs || [], [compareResult]); + + const visibleDiffs = useMemo( + () => tableDiffs.filter((td) => td.diffType !== 'UNCHANGED'), + [tableDiffs], + ); + + const selectedTable = useMemo( + () => visibleDiffs.find((td) => td.tableName === detailViewTableName) || null, + [visibleDiffs, detailViewTableName], + ); + + const handleToggleSelect = (tableName: string) => { + setSelectedTableDiffs({ + ...selectedTableDiffs, + [tableName]: !selectedTableDiffs[tableName], + }); + }; + + return ( +
+
+
+ {i18n('schemaDiff.table')} + ({visibleDiffs.length}) +
+ {visibleDiffs.map((td) => { + const ddlLength = td.ddlStatements?.length || (td.ddlStatement ? 1 : 0); + return ( +
setDetailViewTableName(td.tableName)} + > +
+ e.stopPropagation()} + onChange={() => handleToggleSelect(td.tableName)} + /> + + {i18n(diffTypeLabel[td.diffType])} + + {td.tableName} +
+
+ {td.columnDiffs?.length ? Col: {td.columnDiffs.length} : null} + {td.indexDiffs?.length ? Idx: {td.indexDiffs.length} : null} + {td.foreignKeyDiffs?.length ? FK: {td.foreignKeyDiffs.length} : null} + {ddlLength > 0 ? DDL: {ddlLength} stmts : null} +
+
+ ); + })} + {visibleDiffs.length === 0 && ( +
{i18n('schemaDiff.noChanges')}
+ )} +
+
+ {selectedTable ? : ( +
+ {i18n('schemaDiff.table')} +
+ )} +
+
+ ); +}); + +export default ResultPanel; diff --git a/chat2db-client/src/blocks/SchemaDiff/index.less b/chat2db-client/src/blocks/SchemaDiff/index.less new file mode 100644 index 000000000..3e58dc174 --- /dev/null +++ b/chat2db-client/src/blocks/SchemaDiff/index.less @@ -0,0 +1,91 @@ +.schemaDiffPanel { + display: flex; + flex-direction: column; + height: 100%; + padding: 16px; + overflow-y: auto; + background: var(--color-bg-container); + + .header { + display: flex; + align-items: flex-start; + gap: 16px; + margin-bottom: 12px; + + .sourceSelector, + .targetSelector { + display: flex; + align-items: center; + flex: 1; + gap: 6px; + + .selectorLabel { + font-weight: 600; + font-size: 13px; + white-space: nowrap; + min-width: 32px; + color: var(--color-text); + } + } + + .selectorArrow { + font-size: 20px; + color: var(--color-text-tertiary); + line-height: 30px; + padding-top: 20px; + } + + .compareActions { + display: flex; + align-items: flex-end; + padding-top: 16px; + } + } + + .options { + display: flex; + gap: 16px; + padding: 8px 0; + margin-bottom: 8px; + border-bottom: 1px solid var(--color-border); + + .optionCheckbox { + display: flex; + align-items: center; + gap: 4px; + font-size: 12px; + cursor: pointer; + color: var(--color-text-secondary); + + input { + margin: 0; + } + } + } + + .loadingContainer { + display: flex; + justify-content: center; + align-items: center; + padding: 60px 0; + } + + .summaryBar { + display: flex; + gap: 20px; + padding: 10px 14px; + background: var(--color-bg-subtle); + border: 1px solid var(--color-border); + border-radius: 6px; + margin-bottom: 12px; + font-size: 13px; + + .summaryItem { + &.added strong { color: var(--color-success); } + &.removed strong { color: var(--color-error); } + &.modified strong { color: var(--color-warning); } + &.unchanged strong { color: var(--color-text-tertiary); } + &.excluded strong { color: var(--color-text-tertiary); } + } + } +} diff --git a/chat2db-client/src/blocks/SchemaDiff/index.tsx b/chat2db-client/src/blocks/SchemaDiff/index.tsx new file mode 100644 index 000000000..3431b41b3 --- /dev/null +++ b/chat2db-client/src/blocks/SchemaDiff/index.tsx @@ -0,0 +1,328 @@ +import React, { memo, useCallback, useEffect, useState } from 'react'; +import { Button, Select, Spin, message, Modal, Checkbox } from 'antd'; +import classnames from 'classnames'; + +import { i18n, i18nElement } from '@/i18n'; +import connectionService from '@/service/connection'; +import sqlService from '@/service/sql'; +import { useConnectionStore, getConnectionList } from '@/pages/main/store/connection'; +import { IConnectionListItem } from '@/typings/connection'; +import { IDatabaseItem, ISchemaItem } from '@/typings'; + +import { useSchemaDiffStore, setSourceDataSource, setTargetDataSource, setSourceDatabase, setTargetDatabase, setSourceSchema, setTargetSchema, setCompareOption, setCompareResult, setComparing, setSelectedTableDiffs, setDetailViewTableName } from './store'; +import ResultPanel from './ResultPanel'; +import MigrationPanel from './MigrationPanel'; +import styles from './index.less'; + +/** + * SchemaDiffPanel - Database schema comparison component. + * + * Allows users to: + * 1. Select source and target data sources, databases, and schemas + * 2. Configure comparison options (columns, indexes, foreign keys, case sensitivity) + * 3. View comparison results with diff details and DDL statements + * 4. Execute migration to apply changes to the target database + */ +const SchemaDiffPanel: React.FC = memo(() => { + // Store state + const { + sourceDataSource, targetDataSource, + sourceDatabase, targetDatabase, + sourceSchema, targetSchema, + compareOption, compareResult, + comparing, selectedTableDiffs, + } = useSchemaDiffStore(); + + const connectionList = useConnectionStore((s) => s.connectionList); + + // Local state for database/schema lists and loading indicators + const [sourceDatabases, setSourceDatabases] = useState([]); + const [targetDatabases, setTargetDatabases] = useState([]); + const [sourceSchemas, setSourceSchemas] = useState([]); + const [targetSchemas, setTargetSchemas] = useState([]); + const [sourceDbLoading, setSourceDbLoading] = useState(false); + const [targetDbLoading, setTargetDbLoading] = useState(false); + const [sourceSchemaLoading, setSourceSchemaLoading] = useState(false); + const [targetSchemaLoading, setTargetSchemaLoading] = useState(false); + + // Load connection list on mount + useEffect(() => { + if (!connectionList) { + getConnectionList(); + } + }, [connectionList]); + + // Load databases when source data source changes + useEffect(() => { + if (sourceDataSource?.id) { + setSourceDbLoading(true); + setSourceDatabases([]); + connectionService.getDatabaseList({ dataSourceId: sourceDataSource.id }) + .then((res) => { + setSourceDatabases(Array.isArray(res) ? res : []); + }) + .catch(() => { + message.error(i18n('schemaDiff.loadDatabaseFail')); + }) + .finally(() => { + setSourceDbLoading(false); + }); + } else { + setSourceDatabases([]); + } + }, [sourceDataSource?.id]); + + useEffect(() => { + if (targetDataSource?.id) { + setTargetDbLoading(true); + setTargetDatabases([]); + connectionService.getDatabaseList({ dataSourceId: targetDataSource.id }) + .then((res) => { + setTargetDatabases(Array.isArray(res) ? res : []); + }) + .catch(() => { + message.error(i18n('schemaDiff.loadDatabaseFail')); + }) + .finally(() => { + setTargetDbLoading(false); + }); + } else { + setTargetDatabases([]); + } + }, [targetDataSource?.id]); + + useEffect(() => { + if (sourceDataSource?.id && sourceDatabase) { + setSourceSchemaLoading(true); + setSourceSchemas([]); + connectionService.getSchemaList({ dataSourceId: sourceDataSource.id, databaseName: sourceDatabase }) + .then((res) => { + setSourceSchemas(Array.isArray(res) ? res : []); + }) + .catch(() => { + message.error(i18n('schemaDiff.loadSchemaFail')); + }) + .finally(() => { + setSourceSchemaLoading(false); + }); + } else { + setSourceSchemas([]); + } + }, [sourceDataSource?.id, sourceDatabase]); + + useEffect(() => { + if (targetDataSource?.id && targetDatabase) { + setTargetSchemaLoading(true); + setTargetSchemas([]); + connectionService.getSchemaList({ dataSourceId: targetDataSource.id, databaseName: targetDatabase }) + .then((res) => { + setTargetSchemas(Array.isArray(res) ? res : []); + }) + .catch(() => { + message.error(i18n('schemaDiff.loadSchemaFail')); + }) + .finally(() => { + setTargetSchemaLoading(false); + }); + } else { + setTargetSchemas([]); + } + }, [targetDataSource?.id, targetDatabase]); + + // Execute schema comparison + const handleCompare = useCallback(async () => { + if (!sourceDataSource || !targetDataSource || !sourceDatabase || !targetDatabase) { + message.warning(i18n('schemaDiff.selectSource')); + return; + } + setComparing(true); + try { + const result = await sqlService.compareSchema({ + sourceDataSourceId: sourceDataSource.id, + sourceDatabaseName: sourceDatabase, + sourceSchemaName: sourceSchema || undefined, + targetDataSourceId: targetDataSource.id, + targetDatabaseName: targetDatabase, + targetSchemaName: targetSchema || undefined, + compareOption, + }); + setCompareResult(result); + if (result?.tableDiffs) { + const selected: Record = {}; + result.tableDiffs.forEach((td) => { + if (td.diffType !== 'UNCHANGED') { + selected[td.tableName] = true; + } + }); + setSelectedTableDiffs(selected); + } + } catch (e: any) { + message.error(e?.message || 'Compare failed'); + } finally { + setComparing(false); + } + }, [sourceDataSource, targetDataSource, sourceDatabase, targetDatabase, sourceSchema, targetSchema, compareOption]); + + // Calculate total number of changes for display + const totalChanges = compareResult + ? (compareResult.summary?.tablesOnlyInSource || 0) + + (compareResult.summary?.tablesOnlyInTarget || 0) + + (compareResult.summary?.modifiedTables || 0) + : 0; + + // Render: header with selectors, options, loading spinner, and results + return ( +
+
+
+
{i18n('schemaDiff.source')}
+ setSourceDatabase(v || '')} + style={{ width: 160, marginLeft: 8 }} + loading={sourceDbLoading} + options={(sourceDatabases || []).map((db: IDatabaseItem) => ({ label: db.name, value: db.name }))} + /> + {sourceSchemas.length > 0 && ( + { + const ds = connectionList?.find((c: IConnectionListItem) => c.id === id) || null; + setTargetDataSource(ds ? { id: ds.id!, alias: ds.alias || '', dbType: ds.type || '' } : null); + }} + style={{ width: 200 }} + optionFilterProp="label" + options={(connectionList || []).map((c: IConnectionListItem) => ({ + label: c.alias, + value: c.id, + }))} + /> + setTargetSchema(v || '')} + style={{ width: 140, marginLeft: 8 }} + loading={targetSchemaLoading} + options={(targetSchemas || []).map((s: ISchemaItem) => ({ label: s.name, value: s.name }))} + allowClear + /> + )} +
+
+ +
+
+ +
+ setCompareOption({ ...compareOption, compareColumn: e.target.checked })}> + {i18n('schemaDiff.compareColumn')} + + setCompareOption({ ...compareOption, compareIndex: e.target.checked })}> + {i18n('schemaDiff.compareIndex')} + + setCompareOption({ ...compareOption, compareForeignKey: e.target.checked })}> + {i18n('schemaDiff.compareForeignKey')} + + setCompareOption({ ...compareOption, compareTableOption: e.target.checked })}> + {i18n('schemaDiff.compareTableOption')} + + setCompareOption({ ...compareOption, excludeDeprecated: e.target.checked })}> + {i18n('schemaDiff.excludeDeprecated')} + + setCompareOption({ ...compareOption, caseSensitive: e.target.checked })}> + {i18n('schemaDiff.caseSensitive')} + + setCompareOption({ ...compareOption, ignoreCharsetAlias: e.target.checked })}> + {i18n('schemaDiff.ignoreCharsetAlias')} + + setCompareOption({ ...compareOption, ignoreIntegerDisplayWidth: e.target.checked })}> + {i18n('schemaDiff.ignoreIntegerDisplayWidth')} + + setCompareOption({ ...compareOption, ignoreAutoIncrement: e.target.checked })}> + {i18n('schemaDiff.ignoreAutoIncrement')} + +
+ + {comparing && ( +
+ +
+ )} + + {compareResult && ( + <> +
+ + {i18n('schemaDiff.totalTables')}: {compareResult.summary?.totalTables || 0} + + + +{i18n('schemaDiff.tablesAdded')}: {compareResult.summary?.tablesOnlyInSource || 0} + + + -{i18n('schemaDiff.tablesRemoved')}: {compareResult.summary?.tablesOnlyInTarget || 0} + + + ~{i18n('schemaDiff.tablesModified')}: {compareResult.summary?.modifiedTables || 0} + + + {i18n('schemaDiff.tablesUnchanged')}: {compareResult.summary?.unchangedTables || 0} + + {compareResult.summary?.excludedDeprecatedTables > 0 && ( + + {i18n('schemaDiff.excluded')}: {compareResult.summary.excludedDeprecatedTables} + + )} +
+ + + + + )} +
+ ); +}); + +export default SchemaDiffPanel; diff --git a/chat2db-client/src/blocks/SchemaDiff/store.ts b/chat2db-client/src/blocks/SchemaDiff/store.ts new file mode 100644 index 000000000..eb9bd6595 --- /dev/null +++ b/chat2db-client/src/blocks/SchemaDiff/store.ts @@ -0,0 +1,112 @@ +import { createWithEqualityFn } from 'zustand/traditional'; +import { devtools } from 'zustand/middleware'; +import { shallow } from 'zustand/shallow'; + +import { + ISchemaDiffResult, + ITableDiff, + ICompareOption, + IMigrateResult, +} from '@/typings/schemaDiff'; + +export interface IConnectionOption { + id: number; + alias: string; + dbType: string; +} + +export interface ISchemaDiffStore { + sourceDataSource: IConnectionOption | null; + targetDataSource: IConnectionOption | null; + sourceDatabase: string; + targetDatabase: string; + sourceSchema: string; + targetSchema: string; + compareOption: ICompareOption; + compareResult: ISchemaDiffResult | null; + selectedTableDiffs: Record; + selectedStatementIndexes: Record; + detailViewTableName: string | null; + comparing: boolean; + migrationExecuting: boolean; + migrationResult: IMigrateResult | null; +} + +const defaultCompareOption: ICompareOption = { + compareColumn: true, + compareIndex: true, + compareForeignKey: true, + compareTableOption: true, + caseSensitive: false, + excludeDeprecated: true, + ignoreCharsetAlias: true, + ignoreIntegerDisplayWidth: true, + ignoreAutoIncrement: true, +}; + +export const initSchemaDiffStore = { + sourceDataSource: null, + targetDataSource: null, + sourceDatabase: '', + targetDatabase: '', + sourceSchema: '', + targetSchema: '', + compareOption: defaultCompareOption, + compareResult: null, + selectedTableDiffs: {}, + selectedStatementIndexes: {}, + detailViewTableName: null, + comparing: false, + migrationExecuting: false, + migrationResult: null, +}; + +export const useSchemaDiffStore = createWithEqualityFn( + devtools(() => initSchemaDiffStore), + shallow, +); + +export const setSourceDataSource = (sourceDataSource: IConnectionOption | null) => + useSchemaDiffStore.setState({ sourceDataSource, sourceDatabase: '', sourceSchema: '', compareResult: null }); + +export const setTargetDataSource = (targetDataSource: IConnectionOption | null) => + useSchemaDiffStore.setState({ targetDataSource, targetDatabase: '', targetSchema: '', compareResult: null }); + +export const setSourceDatabase = (sourceDatabase: string) => + useSchemaDiffStore.setState({ sourceDatabase, sourceSchema: '', compareResult: null }); + +export const setTargetDatabase = (targetDatabase: string) => + useSchemaDiffStore.setState({ targetDatabase, targetSchema: '', compareResult: null }); + +export const setSourceSchema = (sourceSchema: string) => + useSchemaDiffStore.setState({ sourceSchema, compareResult: null }); + +export const setTargetSchema = (targetSchema: string) => + useSchemaDiffStore.setState({ targetSchema, compareResult: null }); + +export const setCompareOption = (compareOption: ICompareOption) => + useSchemaDiffStore.setState({ compareOption, compareResult: null }); + +export const setCompareResult = (compareResult: ISchemaDiffResult | null) => + useSchemaDiffStore.setState({ compareResult, selectedTableDiffs: {}, selectedStatementIndexes: {}, detailViewTableName: null }); + +export const setComparing = (comparing: boolean) => + useSchemaDiffStore.setState({ comparing }); + +export const setSelectedTableDiffs = (selectedTableDiffs: Record) => + useSchemaDiffStore.setState({ selectedTableDiffs }); + +export const setSelectedStatementIndexes = (selectedStatementIndexes: Record) => + useSchemaDiffStore.setState({ selectedStatementIndexes }); + +export const setDetailViewTableName = (detailViewTableName: string | null) => + useSchemaDiffStore.setState({ detailViewTableName }); + +export const setMigrationExecuting = (migrationExecuting: boolean) => + useSchemaDiffStore.setState({ migrationExecuting }); + +export const setMigrationResult = (migrationResult: IMigrateResult | null) => + useSchemaDiffStore.setState({ migrationResult }); + +export const resetStore = () => + useSchemaDiffStore.setState({ ...initSchemaDiffStore }); diff --git a/chat2db-client/src/blocks/Setting/AiSetting/aiTypeConfig.ts b/chat2db-client/src/blocks/Setting/AiSetting/aiTypeConfig.ts index e2fbef8bc..9ed7bdaf8 100644 --- a/chat2db-client/src/blocks/Setting/AiSetting/aiTypeConfig.ts +++ b/chat2db-client/src/blocks/Setting/AiSetting/aiTypeConfig.ts @@ -1,63 +1,63 @@ import i18n from '@/i18n'; -import { IAiConfig } from '@/typings'; import { AIType } from '@/typings/ai'; export type IAiConfigBooleans = { - [K in keyof IAiConfig]?: boolean | string; + [key: string]: boolean | string | number; }; const AITypeName = { - [AIType.CHAT2DBAI]: 'Chat2DB', - [AIType.ZHIPUAI]: i18n('setting.tab.aiType.zhipu'), - [AIType.BAICHUANAI]: i18n('setting.tab.aiType.baichuan'), - [AIType.WENXINAI]: i18n('setting.tab.aiType.wenxin'), - [AIType.TONGYIQIANWENAI]: i18n('setting.tab.aiType.tongyiqianwen'), - [AIType.OPENAI]: 'Open AI', - [AIType.AZUREAI]: 'Azure AI', - [AIType.RESTAI]: i18n('setting.tab.custom'), + [AIType.ANTHROPIC]: 'Anthropic', + [AIType.OPENAI]: 'OpenAI', }; const AIFormConfig: Record = { - [AIType.CHAT2DBAI]: { + [AIType.ANTHROPIC]: { apiKey: true, - }, - [AIType.ZHIPUAI]: { - apiKey: true, - apiHost: 'https://open.bigmodel.cn/api/paas/v3/model-api/', - model: 'chatglm_turbo', - }, - [AIType.BAICHUANAI]: { - apiKey: true, - secretKey: true, - apiHost: 'https://api.baichuan-ai.com/v1/stream/chat/', - model: 'Baichuan2-53B', - }, - [AIType.WENXINAI]: { - apiKey: true, - apiHost: true, - }, - [AIType.TONGYIQIANWENAI]: { - apiKey: true, - apiHost: true, - model: true, + apiHost: 'https://api.anthropic.com', + model: 'claude-3-5-sonnet-20241022', + temperature: 0.7, + maxTokens: 4096, + topP: 0.999, + topK: 5, + stopSequences: '', + betaVersion: 'tools-2024-05-16', }, [AIType.OPENAI]: { apiKey: true, apiHost: 'https://api.openai.com/', - httpProxyHost: true, - httpProxyPort: true, - // model: 'gpt-3.5-turbo', + httpProxyHost: '', + httpProxyPort: '', + model: 'gpt-4o-mini', + temperature: 0.7, + maxTokens: 4096, + topP: 1, + n: 1, + stop: '', + presencePenalty: 0, + frequencyPenalty: 0, + logitBias: '', + user: '', + organizationId: '', + projectId: '', }, - [AIType.AZUREAI]: { - apiKey: true, - apiHost: true, - model: true, +}; + +// 快速模型配置(用于选表等简单任务) +const FastAIFormConfig: Record = { + [AIType.ANTHROPIC]: { + apiKey: '', + model: 'claude-3-haiku-20240307', + temperature: 0.5, + maxTokens: 1024, }, - [AIType.RESTAI]: { - apiKey: true, - apiHost: true, - model: true, + [AIType.OPENAI]: { + apiKey: '', + apiHost: 'https://api.openai.com/', + model: 'gpt-4o-mini', + temperature: 0.5, + maxTokens: 1024, }, }; -export { AIFormConfig, AITypeName }; +export { AIFormConfig, AITypeName, FastAIFormConfig }; + diff --git a/chat2db-client/src/blocks/Setting/AiSetting/index.tsx b/chat2db-client/src/blocks/Setting/AiSetting/index.tsx index fd84059df..ac2ff5b8f 100644 --- a/chat2db-client/src/blocks/Setting/AiSetting/index.tsx +++ b/chat2db-client/src/blocks/Setting/AiSetting/index.tsx @@ -1,115 +1,247 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; +import { Alert, Button, Form, Input, Modal, Select, Space, Table, message } from 'antd'; import configService from '@/service/config'; import { AIType } from '@/typings/ai'; -import { Alert, Button, Form, Input, Radio, RadioChangeEvent } from 'antd'; +import { IDefaultModelConfig, IModelItem, IModelServiceConfig } from '@/typings/setting'; import i18n from '@/i18n'; -import { IAiConfig } from '@/typings/setting'; import { IRole } from '@/typings/user'; -import { AIFormConfig, AITypeName } from './aiTypeConfig'; +import { useUserStore } from '@/store/user'; import styles from './index.less'; -import { useUserStore } from '@/store/user' interface IProps { - handleApplyAiConfig: (aiConfig: IAiConfig) => void; - aiConfig: IAiConfig; + mode: 'service' | 'default'; } -function capitalizeFirstLetter(string) { - return string.charAt(0).toUpperCase() + string.slice(1); -} +const providerOptions = [ + { label: 'OpenAI', value: AIType.OPENAI }, + { label: 'Anthropic', value: AIType.ANTHROPIC }, +]; -// openAI 的设置项 export default function SettingAI(props: IProps) { - const [aiConfig, setAiConfig] = useState(); - const { userInfo } = useUserStore(state => { - return { - userInfo: state.curUser - } - }) + const [modelServices, setModelServices] = useState([]); + const [defaultModelConfig, setDefaultModelConfig] = useState({ defaultModelId: '', fastModelId: '' }); + const [editingService, setEditingService] = useState(null); + const [serviceModalOpen, setServiceModalOpen] = useState(false); + const [testing, setTesting] = useState(false); + const [serviceForm] = Form.useForm(); + const { userInfo } = useUserStore((state) => ({ userInfo: state.curUser })); useEffect(() => { - setAiConfig(props.aiConfig); - }, [props.aiConfig]); + loadData(); + }, []); - if (!aiConfig) { - return ; - } + const modelOptions = useMemo(() => { + return modelServices.flatMap((service) => + (service.modelList || []).map((model) => ({ + label: `${model.name} (${service.name})`, + value: model.id || `${service.id || service.name}:${model.model}`, + })), + ); + }, [modelServices]); + + const loadData = async () => { + const [serviceList, modelConfig] = await Promise.all([ + configService.getModelServiceList(), + configService.getDefaultModelConfig(), + ]); + setModelServices(serviceList || []); + setDefaultModelConfig(modelConfig || { defaultModelId: '', fastModelId: '' }); + }; if (userInfo?.roleCode && userInfo?.roleCode === IRole.USER) { - // 如果是用户,不能配置ai return ; } - const handleAiTypeChange = async (e: RadioChangeEvent) => { - const aiSqlSource = e.target.value; + const openCreate = () => { + setEditingService(null); + serviceForm.setFieldsValue({ + provider: AIType.OPENAI, + modelList: [{ name: '', model: '' }], + } as IModelServiceConfig); + setServiceModalOpen(true); + }; - // 查询对应ai类型的配置 - const res = await configService.getAiSystemConfig({ - aiSqlSource, + const openEdit = (record: IModelServiceConfig) => { + setEditingService(record); + serviceForm.setFieldsValue({ + ...record, + modelList: record.modelList?.length ? record.modelList : [{ name: '', model: '' }], }); - setAiConfig(res); + setServiceModalOpen(true); }; - /** 应用Ai配置 */ - const handleApplyAiConfig = () => { - const newAiConfig = { ...aiConfig }; - if (newAiConfig.apiHost && !newAiConfig.apiHost?.endsWith('/')) { - newAiConfig.apiHost = newAiConfig.apiHost + '/'; - } - if (aiConfig?.aiSqlSource === AIType.CHAT2DBAI) { - newAiConfig.apiHost = `${window._appGatewayParams.baseUrl || 'http://test.sqlgpt.cn/gateway'}${'/model/'}`; + const handleDelete = async (id?: string) => { + if (!id) { + return; } + await configService.deleteModelService({ id }); + await loadData(); + }; + + const handleSaveService = async () => { + const values = await serviceForm.validateFields(); + await configService.upsertModelService({ + ...values, + id: editingService?.id, + modelList: (values.modelList || []).filter((model) => model?.name && model?.model), + }); + message.success('模型服务保存成功'); + setServiceModalOpen(false); + await loadData(); + }; - if (props.handleApplyAiConfig) { - props.handleApplyAiConfig(newAiConfig); + const handleTestService = async () => { + const values = await serviceForm.validateFields(); + setTesting(true); + try { + await configService.testModelService({ + ...values, + id: editingService?.id, + modelList: (values.modelList || []).filter((model) => model?.name && model?.model), + }); + message.success('模型服务连接测试成功'); + } catch (e: any) { + message.error(typeof e === 'string' ? e : e?.message || '模型服务连接测试失败'); + } finally { + setTesting(false); } }; - return ( - <> -
-
{i18n('setting.title.aiSource')}:
- - {Object.keys(AIType).map((key) => ( - - {AITypeName[key]} - - ))} - -
+ const handleSaveDefaultModel = async () => { + await configService.setDefaultModelConfig(defaultModelConfig); + message.success('默认模型配置保存成功'); + await loadData(); + }; -
- {Object.keys(AIFormConfig[aiConfig?.aiSqlSource]).map((key: string) => ( - - { - setAiConfig({ ...aiConfig, [key]: e.target.value }); + if (props.mode === 'default') { + return ( + <> + + + { + setDefaultModelConfig({ ...defaultModelConfig, fastModelId: value || '' }); }} /> - ))} - - - {aiConfig.aiSqlSource === AIType.RESTAI && ( -
{`Tips: ${i18n( - 'setting.tab.aiType.custom.tips', - )}`}
- )} -
- +
+ + ); + } + + return ( + <> +
+
- {/* {aiConfig?.aiSqlSource === AIType.CHAT2DBAI && !aiConfig.apiKey && } */} +
record.id || record.name} + dataSource={modelServices} + pagination={false} + columns={[ + { title: '服务名称', dataIndex: 'name' }, + { title: '厂商', dataIndex: 'provider' }, + { + title: '模型', + dataIndex: 'modelList', + render: (models: IModelItem[]) => (models || []).map((item) => item.name).join(', '), + }, + { + title: '操作', + render: (_, record: IModelServiceConfig) => ( + + + + + ), + }, + ]} + /> + + setServiceModalOpen(false)} + onOk={handleSaveService} + okText="保存" + cancelText="取消" + footer={( + + + + + + )} + destroyOnClose + > +
+ + + + + + + + + + + {(fields, { add, remove }) => ( + <> + {fields.map((field) => ( + + + + + + + + + + ))} + + + )} + + +
); } + diff --git a/chat2db-client/src/blocks/Setting/ProxySetting/index.tsx b/chat2db-client/src/blocks/Setting/ProxySetting/index.tsx index e25974079..9242ee151 100644 --- a/chat2db-client/src/blocks/Setting/ProxySetting/index.tsx +++ b/chat2db-client/src/blocks/Setting/ProxySetting/index.tsx @@ -20,7 +20,8 @@ export default function ProxyBody() { outSideService.dynamicUrl(`${apiPrefix}/api/system/get-version-a`).then((res: any) => { localStorage.setItem('_BaseURL', apiPrefix); location.reload(); - }).catch((err: any) => { + }) +.catch((err: any) => { message.error(i18n('setting.message.urlTestError')) }); // try { diff --git a/chat2db-client/src/blocks/Setting/index.tsx b/chat2db-client/src/blocks/Setting/index.tsx index cc2bb7bb3..81a5582b9 100644 --- a/chat2db-client/src/blocks/Setting/index.tsx +++ b/chat2db-client/src/blocks/Setting/index.tsx @@ -12,7 +12,6 @@ import { ILatestVersion } from '@/service/config'; import UpdateDetection, { IUpdateDetectionRef, UpdatedStatusEnum } from '@/blocks/Setting/UpdateDetection'; // ---- store ----- -import { useSettingStore, getAiSystemConfig, setAiSystemConfig } from '@/store/setting'; interface IProps { className?: string; @@ -32,7 +31,6 @@ function Setting(props: IProps) { const [currentMenu, setCurrentMenu] = useState(defaultMenu); const [updateDetectionData, setUpdateDetectionData] = useState(null); const updateDetectionRef = React.useRef(null); - const aiConfig = useSettingStore((state) => state.aiConfig); useEffect(() => { if (defaultArouse) { @@ -40,17 +38,6 @@ function Setting(props: IProps) { } }, []); - useEffect(() => { - if (isModalVisible && !noLogin) { - getAiSystemConfig(); - } - }, [isModalVisible]); - - useEffect(() => { - if (!noLogin) { - getAiSystemConfig(); - } - }, []); const showModal = (_currentMenu: number = 0) => { setCurrentMenu(_currentMenu); @@ -77,10 +64,16 @@ function Setting(props: IProps) { code: 'basic', }, { - label: i18n('setting.nav.customAi'), + label: '模型服务', icon: '\ue646', - body: , - code: 'ai', + body: , + code: 'model-service', + }, + { + label: '默认模型', + icon: '\ue63d', + body: , + code: 'default-model', }, { label: i18n('setting.nav.proxy'), diff --git a/chat2db-client/src/blocks/Tree/components/TableRelationModal/index.less b/chat2db-client/src/blocks/Tree/components/TableRelationModal/index.less new file mode 100644 index 000000000..c2d72cca2 --- /dev/null +++ b/chat2db-client/src/blocks/Tree/components/TableRelationModal/index.less @@ -0,0 +1,117 @@ +.tableRelationModal { + .toolbar { + display: flex; + gap: 12px; + align-items: center; + justify-content: space-between; + margin-bottom: 12px; + } + + .search { + width: 320px; + } + + .form { + padding: 12px; + margin-bottom: 12px; + background: var(--color-bg-elevated); + border: 1px solid var(--color-border); + border-radius: 6px; + } + + .formGrid { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 12px; + } +} + +:global { + .ant-modal.tableRelationModalRoot { + .ant-modal-content { + background-color: var(--color-bg-elevated) !important; + } + + .ant-modal-header { + background-color: var(--color-bg-elevated) !important; + } + + .ant-modal-title { + color: var(--color-text) !important; + } + + .ant-modal-close-x { + color: var(--color-text-tertiary) !important; + } + + .ant-table { + color: var(--color-text); + background-color: transparent; + } + + .ant-table-thead > tr > th { + color: var(--color-text); + background-color: var(--color-bg-container); + border-bottom-color: var(--color-border); + } + + .ant-table-tbody > tr > td { + color: var(--color-text); + border-bottom-color: var(--color-border); + } + + .ant-table-tbody > tr:hover > td { + background-color: var(--color-fill-tertiary); + } + + .ant-table-wrapper { + background-color: transparent; + } + + .ant-table-cell { + color: var(--color-text); + } + + .ant-btn-text:not(:disabled):not(.ant-btn-disabled) { + color: var(--color-text); + } + + .ant-btn-text:not(:disabled):not(.ant-btn-disabled):hover { + color: var(--color-primary); + background-color: var(--color-fill-tertiary); + } + + .ant-btn-dangerous.ant-btn-text:not(:disabled):not(.ant-btn-disabled) { + color: var(--color-error); + } + + .ant-tag-blue { + background-color: var(--color-info-bg); + border-color: var(--color-info-border); + color: var(--color-info); + } + + .ant-tag-green { + background-color: var(--color-success-bg); + border-color: var(--color-success-border); + color: var(--color-success); + } + } +} + +.ddlPreview { + max-height: 260px; + padding: 12px; + overflow: auto; + background: var(--color-bg-layout); + border-radius: 6px; +} + +.importInvalidPreview { + max-height: 220px; + padding: 12px; + margin-top: 12px; + overflow: auto; + background: var(--color-bg-layout); + border-radius: 6px; +} diff --git a/chat2db-client/src/blocks/Tree/components/TableRelationModal/index.tsx b/chat2db-client/src/blocks/Tree/components/TableRelationModal/index.tsx new file mode 100644 index 000000000..e24e6abfb --- /dev/null +++ b/chat2db-client/src/blocks/Tree/components/TableRelationModal/index.tsx @@ -0,0 +1,666 @@ +import Iconfont from '@/components/Iconfont'; +import { TreeNodeType } from '@/constants'; +import i18n from '@/i18n'; +import sqlService, { IForeignKeyVO } from '@/service/sql'; +import { ITreeNode } from '@/typings'; +import { DownloadOutlined, EditOutlined, UploadOutlined } from '@ant-design/icons'; +import { Button, Form, Input, message, Modal, Select, Space, Table, Tag, Upload } from 'antd'; +import React, { memo, useCallback, useEffect, useMemo, useState } from 'react'; +import styles from './index.less'; + +interface IProps { + treeNodeData: ITreeNode; +} + +interface IAddRelationForm { + tableName: string; + columnName: string; + referencedTable: string; + referencedColumnName: string; + comment?: string; +} + +interface IVirtualFkImportItem { + tableName?: string; + columnName?: string; + referencedTable?: string; + referencedColumnName?: string; + comment?: string; +} + +const buildVirtualFkKey = (item: IVirtualFkImportItem) => + [item.tableName, item.columnName].map((value) => `${value || ''}`.trim().toLowerCase()).join('\u0001'); + +const getNormalizedName = (value?: string) => `${value || ''}`.trim().toLowerCase(); + +const TableRelationModal = memo((props: IProps) => { + const { treeNodeData } = props; + const { dataSourceId, databaseName, schemaName } = treeNodeData.extraParams || {}; + const isForeignKeyNode = + treeNodeData.treeNodeType === TreeNodeType.KEY || treeNodeData.treeNodeType === TreeNodeType.V_KEY; + const tableName = + treeNodeData.treeNodeType === TreeNodeType.TABLE ? treeNodeData.name : treeNodeData.extraParams?.tableName; + const keyName = isForeignKeyNode ? treeNodeData.name : undefined; + + const [form] = Form.useForm(); + const [loading, setLoading] = useState(false); + const [syncing, setSyncing] = useState(false); + const [submitting, setSubmitting] = useState(false); + const [importing, setImporting] = useState(false); + const [deletingAllVirtual, setDeletingAllVirtual] = useState(false); + const [showForm, setShowForm] = useState(false); + const [editingRecord, setEditingRecord] = useState(null); + const [searchKeyword, setSearchKeyword] = useState(''); + const [foreignKeys, setForeignKeys] = useState([]); + const [tables, setTables] = useState>([]); + const [fieldMap, setFieldMap] = useState>>({}); + + const sortedForeignKeys = useMemo(() => { + if (!foreignKeys.length) return []; + const refCount: Record = {}; + foreignKeys.forEach((fk) => { + refCount[fk.referencedTable] = (refCount[fk.referencedTable] || 0) + 1; + }); + return [...foreignKeys].sort((a, b) => { + const countA = refCount[a.referencedTable] || 0; + const countB = refCount[b.referencedTable] || 0; + if (countB !== countA) return countB - countA; + return a.referencedTable.localeCompare(b.referencedTable); + }); + }, [foreignKeys]); + + const filteredForeignKeys = useMemo(() => { + const keyword = searchKeyword.trim().toLowerCase(); + if (!keyword) return sortedForeignKeys; + + return sortedForeignKeys.filter((fk) => + [ + fk.referencedTable, + fk.referencedColumnName, + fk.tableName, + fk.columnName, + fk.sourceType, + fk.sourceType === 'VIRTUAL' ? i18n('editTable.tooltip.virtualFK') : i18n('editTable.tooltip.realFK'), + fk.comment, + ].some((value) => `${value || ''}`.toLowerCase().includes(keyword)), + ); + }, [searchKeyword, sortedForeignKeys]); + + const tableOptions = useMemo(() => tables.map((item) => ({ label: item.name, value: item.name })), [tables]); + + const virtualForeignKeys = useMemo(() => foreignKeys.filter((item) => item.sourceType === 'VIRTUAL'), [foreignKeys]); + + const filteredVirtualForeignKeys = useMemo( + () => filteredForeignKeys.filter((item) => item.sourceType === 'VIRTUAL'), + [filteredForeignKeys], + ); + + const getFields = useCallback( + async (selectedTableName?: string) => { + if (!selectedTableName || fieldMap[selectedTableName]) { + return; + } + const fields = await sqlService.getColumnList({ + dataSourceId: dataSourceId!, + databaseName: databaseName!, + schemaName, + tableName: selectedTableName, + }); + setFieldMap((prev) => ({ + ...prev, + [selectedTableName]: (fields || []).map((item) => ({ name: item.name })), + })); + }, + [dataSourceId, databaseName, fieldMap, schemaName], + ); + + const fetchForeignKeys = useCallback(async () => { + if (!dataSourceId || !databaseName) return; + setLoading(true); + try { + const list = await sqlService.getForeignKeyList({ + dataSourceId: dataSourceId!, + databaseName: databaseName!, + schemaName, + }); + const tableForeignKeys = tableName + ? (list || []).filter((item) => item.tableName === tableName || item.referencedTable === tableName) + : list || []; + setForeignKeys(keyName ? tableForeignKeys.filter((item) => item.name === keyName) : tableForeignKeys); + } catch { + message.error(i18n('workspace.tableRelation.loadError')); + } finally { + setLoading(false); + } + }, [dataSourceId, databaseName, schemaName, tableName, keyName]); + + const fetchTables = useCallback(async () => { + if (!dataSourceId || !databaseName) return; + try { + const list = await sqlService.getAllTableList({ + dataSourceId: dataSourceId!, + databaseName, + schemaName, + }); + setTables(list || []); + } catch { + setTables([]); + } + }, [dataSourceId, databaseName, schemaName]); + + useEffect(() => { + fetchForeignKeys(); + fetchTables(); + }, [fetchForeignKeys, fetchTables]); + + const handleSync = async () => { + if (!dataSourceId || !databaseName) return; + setSyncing(true); + try { + const result = await sqlService.syncForeignKeys({ + dataSourceId: dataSourceId!, + databaseName, + schemaName, + }); + message.success( + i18n('editTable.message.syncFKSuccess', result?.added || 0, result?.deleted || 0, result?.unchanged || 0), + ); + fetchForeignKeys(); + } catch { + message.error(i18n('editTable.message.syncFKError')); + } finally { + setSyncing(false); + } + }; + + const handleDelete = (record: IForeignKeyVO) => { + if (!record.id) { + message.error(i18n('workspace.tableRelation.missingId')); + return; + } + Modal.confirm({ + title: i18n('workspace.tableRelation.deleteConfirmTitle'), + content: + record.sourceType === 'REAL' + ? i18n('workspace.tableRelation.deleteRealConfirmContent') + : i18n('workspace.tableRelation.deleteVirtualConfirmContent'), + okText: i18n('common.button.confirm'), + cancelText: i18n('common.button.cancel'), + onOk: async () => { + const result = await sqlService.deleteForeignKey({ id: record.id!, sourceType: record.sourceType }); + if (result?.executedDDL) { + Modal.info({ + title: i18n('workspace.tableRelation.realDeleteDDLTitle'), + content:
{result.executedDDL}
, + width: 640, + }); + } + message.success(i18n('workspace.tableRelation.deleteSuccess')); + fetchForeignKeys(); + }, + }); + }; + + const handleDeleteAllVirtual = () => { + if (!virtualForeignKeys.length) { + message.warning(i18n('workspace.tableRelation.noVirtualFk')); + return; + } + + Modal.confirm({ + title: i18n('workspace.tableRelation.deleteAllVirtualConfirmTitle'), + content: i18n('workspace.tableRelation.deleteAllVirtualConfirmContent', virtualForeignKeys.length), + okText: i18n('common.button.confirm'), + okButtonProps: { danger: true }, + cancelText: i18n('common.button.cancel'), + onOk: async () => { + setDeletingAllVirtual(true); + try { + await Promise.all( + virtualForeignKeys + .filter((item) => item.id) + .map((item) => sqlService.deleteForeignKey({ id: item.id!, sourceType: 'VIRTUAL' })), + ); + message.success(i18n('workspace.tableRelation.deleteAllVirtualSuccess')); + fetchForeignKeys(); + } catch { + message.error(i18n('workspace.tableRelation.deleteAllVirtualError')); + } finally { + setDeletingAllVirtual(false); + } + }, + }); + }; + + const openCreateForm = () => { + setEditingRecord(null); + form.resetFields(); + form.setFieldsValue(tableName ? { tableName } : {}); + if (tableName) { + getFields(tableName); + } + setShowForm(true); + }; + + const openEditForm = (record: IForeignKeyVO) => { + setEditingRecord(record); + form.setFieldsValue({ + tableName: record.tableName, + columnName: record.columnName, + referencedTable: record.referencedTable, + referencedColumnName: record.referencedColumnName, + comment: record.comment, + }); + getFields(record.tableName); + getFields(record.referencedTable); + setShowForm(true); + }; + + const closeForm = () => { + setShowForm(false); + setEditingRecord(null); + form.resetFields(); + }; + + const handleSubmit = async () => { + const values = await form.validateFields(); + setSubmitting(true); + try { + if (editingRecord?.id) { + await sqlService.updateVirtualForeignKey({ + id: editingRecord.id, + dataSourceId: dataSourceId!, + databaseName, + schemaName, + ...values, + }); + message.success(i18n('workspace.tableRelation.updateSuccess')); + } else { + await sqlService.createVirtualForeignKey({ + dataSourceId: dataSourceId!, + databaseName, + schemaName, + ...values, + }); + message.success(i18n('workspace.tableRelation.createSuccess')); + } + closeForm(); + fetchForeignKeys(); + } catch { + message.error( + editingRecord ? i18n('workspace.tableRelation.updateError') : i18n('workspace.tableRelation.createError'), + ); + } finally { + setSubmitting(false); + } + }; + + const normalizeImportData = (data: unknown): IVirtualFkImportItem[] => { + const rawList = Array.isArray(data) + ? data + : Array.isArray((data as { virtualForeignKeys?: unknown[] })?.virtualForeignKeys) + ? (data as { virtualForeignKeys: unknown[] }).virtualForeignKeys + : []; + + const itemMap = new Map(); + rawList + .map((item) => item as IVirtualFkImportItem) + .filter((item) => item.tableName && item.columnName && item.referencedTable && item.referencedColumnName) + .forEach((item) => { + itemMap.set(buildVirtualFkKey(item), item); + }); + return Array.from(itemMap.values()); + }; + + const validateAndNormalizeImportList = async (importList: IVirtualFkImportItem[]) => { + const tableList = await sqlService.getAllTableList({ + dataSourceId: dataSourceId!, + databaseName, + schemaName, + }); + const tableNameMap = new Map((tableList || []).map((item) => [getNormalizedName(item.name), item.name])); + const involvedTableNames = Array.from( + new Set( + importList + .flatMap((item) => [item.tableName, item.referencedTable]) + .map(getNormalizedName) + .filter(Boolean), + ), + ); + const columnNameMap: Record> = {}; + + await Promise.all( + involvedTableNames + .filter((normalizedTableName) => tableNameMap.has(normalizedTableName)) + .map(async (normalizedTableName) => { + const actualTableName = tableNameMap.get(normalizedTableName)!; + const columns = await sqlService.getColumnList({ + dataSourceId: dataSourceId!, + databaseName, + schemaName, + tableName: actualTableName, + }); + columnNameMap[normalizedTableName] = new Map( + (columns || []).map((column) => [getNormalizedName(column.name), column.name]), + ); + }), + ); + + const invalidItems: string[] = []; + const normalizedList: IVirtualFkImportItem[] = []; + importList.forEach((item) => { + const sourceTableKey = getNormalizedName(item.tableName); + const sourceColumnKey = getNormalizedName(item.columnName); + const referencedTableKey = getNormalizedName(item.referencedTable); + const referencedColumnKey = getNormalizedName(item.referencedColumnName); + const sourceTable = tableNameMap.get(sourceTableKey); + const referencedTable = tableNameMap.get(referencedTableKey); + const sourceColumn = columnNameMap[sourceTableKey]?.get(sourceColumnKey); + const referencedColumn = columnNameMap[referencedTableKey]?.get(referencedColumnKey); + const relationText = [ + `${item.tableName}.${item.columnName}`, + `${item.referencedTable}.${item.referencedColumnName}`, + ].join(' -> '); + + if (!sourceTable || !referencedTable || !sourceColumn || !referencedColumn) { + invalidItems.push(relationText); + return; + } + + normalizedList.push({ + tableName: sourceTable, + columnName: sourceColumn, + referencedTable, + referencedColumnName: referencedColumn, + comment: item.comment, + }); + }); + + return { invalidItems, normalizedList }; + }; + + const handleImport = async (file: File) => { + if (!dataSourceId || !databaseName) return false; + setImporting(true); + try { + const data = JSON.parse(await file.text()); + const importList = normalizeImportData(data); + if (!importList.length) { + message.error(i18n('workspace.tableRelation.importEmpty')); + return false; + } + + const { invalidItems, normalizedList } = await validateAndNormalizeImportList(importList); + if (invalidItems.length) { + Modal.error({ + title: i18n('workspace.tableRelation.importInvalidTitle'), + content: ( +
+
{i18n('workspace.tableRelation.importInvalidContent', invalidItems.length)}
+
{invalidItems.slice(0, 10).join('\n')}
+
+ ), + width: 640, + }); + return false; + } + + const currentVirtualFKs = await sqlService.getForeignKeyList({ + dataSourceId: dataSourceId!, + databaseName, + schemaName, + }); + const currentMap = new Map( + (currentVirtualFKs || []) + .filter((item) => item.sourceType === 'VIRTUAL' && item.id) + .map((item) => [buildVirtualFkKey(item), item]), + ); + + let created = 0; + let updated = 0; + for (const item of normalizedList) { + const existing = currentMap.get(buildVirtualFkKey(item)); + if (existing?.id) { + await sqlService.updateVirtualForeignKey({ + id: existing.id, + dataSourceId: dataSourceId!, + databaseName, + schemaName, + tableName: item.tableName, + columnName: item.columnName, + referencedTable: item.referencedTable, + referencedColumnName: item.referencedColumnName, + comment: item.comment, + }); + updated++; + } else { + await sqlService.createVirtualForeignKey({ + dataSourceId: dataSourceId!, + databaseName, + schemaName, + tableName: item.tableName!, + columnName: item.columnName!, + referencedTable: item.referencedTable!, + referencedColumnName: item.referencedColumnName!, + comment: item.comment, + }); + created++; + } + } + + message.success(i18n('workspace.tableRelation.importSuccess', created, updated)); + fetchForeignKeys(); + } catch { + message.error(i18n('workspace.tableRelation.importError')); + } finally { + setImporting(false); + } + return false; + }; + + const handleExport = () => { + if (!filteredVirtualForeignKeys.length) { + message.warning(i18n('workspace.tableRelation.exportEmpty')); + return; + } + + const content = JSON.stringify( + { + version: 1, + databaseName, + schemaName, + virtualForeignKeys: filteredVirtualForeignKeys.map((item) => ({ + tableName: item.tableName, + columnName: item.columnName, + referencedTable: item.referencedTable, + referencedColumnName: item.referencedColumnName, + comment: item.comment, + })), + }, + null, + 2, + ); + const blobUrl = URL.createObjectURL(new Blob([content], { type: 'application/json;charset=utf-8' })); + const link = document.createElement('a'); + link.href = blobUrl; + link.download = `virtual-foreign-keys-${databaseName || 'database'}.json`; + link.click(); + URL.revokeObjectURL(blobUrl); + message.success(i18n('workspace.tableRelation.exportSuccess')); + }; + + const columns = [ + { + title: i18n('workspace.tableRelation.masterTable'), + dataIndex: 'referencedTable', + width: 160, + }, + { + title: i18n('workspace.tableRelation.uniqueColumn'), + dataIndex: 'referencedColumnName', + width: 160, + }, + { + title: i18n('workspace.tableRelation.childTable'), + dataIndex: 'tableName', + width: 160, + }, + { + title: i18n('workspace.tableRelation.relationColumn'), + dataIndex: 'columnName', + width: 160, + }, + { + title: i18n('editTable.label.sourceType'), + dataIndex: 'sourceType', + width: 100, + render: (sourceType: IForeignKeyVO['sourceType']) => ( + + {sourceType === 'VIRTUAL' ? i18n('editTable.tooltip.virtualFK') : i18n('editTable.tooltip.realFK')} + + ), + }, + { + title: i18n('editTable.label.comment'), + dataIndex: 'comment', + ellipsis: true, + }, + { + title: i18n('workspace.tableRelation.operation'), + width: 100, + render: (_: unknown, record: IForeignKeyVO) => ( + + {record.sourceType === 'VIRTUAL' ? ( + + + + + + + + + {showForm ? ( +
{ + if (changedValues.tableName) { + form.setFieldValue('columnName', undefined); + getFields(changedValues.tableName); + } + if (changedValues.referencedTable) { + form.setFieldValue('referencedColumnName', undefined); + getFields(changedValues.referencedTable); + } + }} + > +
+ + getFields(form.getFieldValue('tableName'))} + /> + + + getFields(form.getFieldValue('referencedTable'))} + /> + +
+ + + + + + + + + ) : null} +
`${record.sourceType}-${record.id || record.name}`} + loading={loading} + columns={columns as any} + dataSource={filteredForeignKeys} + pagination={false} + scroll={{ x: 900, y: 420 }} + /> + + ); +}); + +export const openTableRelationModal = (treeNodeData: ITreeNode) => { + Modal.info({ + title: i18n('workspace.menu.viewTableRelation'), + icon: null, + width: 980, + className: 'tableRelationModalRoot', + content: , + okText: i18n('common.button.close'), + maskClosable: true, + }); +}; + +export default TableRelationModal; diff --git a/chat2db-client/src/blocks/Tree/functions/deleteDatabase.tsx b/chat2db-client/src/blocks/Tree/functions/deleteDatabase.tsx new file mode 100644 index 000000000..a64929a0e --- /dev/null +++ b/chat2db-client/src/blocks/Tree/functions/deleteDatabase.tsx @@ -0,0 +1,81 @@ +import React, { useState } from 'react'; +import mysqlService from '@/service/sql'; +import { Button, Checkbox, message } from 'antd'; +import { openModal } from '@/store/common/components'; +import styles from './deleteTable.less'; +import i18n from '@/i18n'; + +export const deleteDatabase = (treeNodeData, loadData, refreshRootData?: (refresh?: boolean) => void) => { + openModal({ + width: '450px', + content: ( + + ), + }); +}; + +export const DeleteDatabaseModalContent = (params: { + treeNodeData: any; + loadData: any; + refreshRootData?: (refresh?: boolean) => void; +}) => { + const { treeNodeData, loadData, refreshRootData } = params; + const [userChecked, setUserChecked] = useState(false); + + const onOk = () => { + const p: any = { + dataSourceId: treeNodeData.extraParams.dataSourceId, + databaseName: treeNodeData.name, + }; + mysqlService + .deleteDatabase(p) + .then(() => { + if (treeNodeData.parentNode) { + loadData({ + refresh: true, + treeNodeData: treeNodeData.parentNode, + }); + } else { + refreshRootData?.(true); + } + openModal(false); + }) + .catch((error) => { + console.error('Error deleting database:', error); + message.error(i18n('workspace.tree.delete.database.error') || 'Failed to delete database'); + }); + }; + + return ( +
+
{i18n('workspace.tree.delete.database.tip', `"${treeNodeData.name}"`)}
+
+ { + setUserChecked(e.target.checked); + }} + > + {i18n('workspace.tree.delete.tip')} + +
+
+ + +
+
+ ); +}; diff --git a/chat2db-client/src/blocks/Tree/functions/deprecatedTable.ts b/chat2db-client/src/blocks/Tree/functions/deprecatedTable.ts new file mode 100644 index 000000000..10c1cc145 --- /dev/null +++ b/chat2db-client/src/blocks/Tree/functions/deprecatedTable.ts @@ -0,0 +1,23 @@ +import mysqlService from '@/service/sql'; + +export const deprecatedTable = ({ treeNodeData, loadData }: { treeNodeData: any; loadData: any }) => { + mysqlService.deprecatedTable({ + dataSourceId: treeNodeData.extraParams.dataSourceId, + databaseName: treeNodeData.extraParams.databaseName, + schemaName: treeNodeData.extraParams.schemaName, + tableName: treeNodeData.name, + }).then(() => { + loadData(); + }); +}; + +export const restoreDeprecatedTable = ({ treeNodeData, loadData }: { treeNodeData: any; loadData: any }) => { + mysqlService.restoreDeprecatedTable({ + dataSourceId: treeNodeData.extraParams.dataSourceId, + databaseName: treeNodeData.extraParams.databaseName, + schemaName: treeNodeData.extraParams.schemaName, + tableName: treeNodeData.name, + }).then(() => { + loadData(); + }); +}; diff --git a/chat2db-client/src/blocks/Tree/functions/openAsyncSql.ts b/chat2db-client/src/blocks/Tree/functions/openAsyncSql.ts index 98cf337ef..b222d6c87 100644 --- a/chat2db-client/src/blocks/Tree/functions/openAsyncSql.ts +++ b/chat2db-client/src/blocks/Tree/functions/openAsyncSql.ts @@ -16,7 +16,7 @@ export const openView = (props:{ databaseName: treeNodeData.extraParams?.databaseName, schemaName: treeNodeData.extraParams?.schemaName, loadSQL: ()=>{ - return new Promise((resolve) => { + return new Promise((resolve, reject) => { sqlService .getViewDetail({ dataSourceId: treeNodeData.extraParams!.dataSourceId!, @@ -26,8 +26,16 @@ export const openView = (props:{ tableName: treeNodeData.name } as any) .then((res) => { - // 更新ddl - resolve(res.ddl); + if (res && res.ddl) { + resolve(res.ddl); + } else { + console.warn('[openView] ddl is empty, response:', res); + resolve('-- View DDL not available or empty'); + } + }) + .catch((err) => { + console.error('[openView] Failed to get view detail:', err); + reject(err); }); }); } @@ -35,6 +43,7 @@ export const openView = (props:{ } export const openFunction = (props:{ + addWorkspaceTab: any; treeNodeData: any; }) => { const { treeNodeData } = props; @@ -47,7 +56,7 @@ export const openFunction = (props:{ databaseName: treeNodeData.extraParams?.databaseName, schemaName: treeNodeData.extraParams?.schemaName, loadSQL: ()=>{ - return new Promise((resolve) => { + return new Promise((resolve, reject) => { sqlService .getFunctionDetail({ dataSourceId: treeNodeData.extraParams!.dataSourceId!, @@ -57,8 +66,16 @@ export const openFunction = (props:{ functionName: treeNodeData.name } as any) .then((res) => { - // 更新ddl - resolve(res.functionBody); + if (res && res.functionBody) { + resolve(res.functionBody); + } else { + console.warn('[openFunction] functionBody is empty, response:', res); + resolve('-- Function body not available or empty'); + } + }) + .catch((err) => { + console.error('[openFunction] Failed to get function detail:', err); + reject(err); }); }); } @@ -66,6 +83,7 @@ export const openFunction = (props:{ } export const openProcedure = (props:{ + addWorkspaceTab: any; treeNodeData: any; }) => { const { treeNodeData } = props; @@ -78,7 +96,7 @@ export const openProcedure = (props:{ databaseName: treeNodeData.extraParams?.databaseName, schemaName: treeNodeData.extraParams?.schemaName, loadSQL: ()=>{ - return new Promise((resolve) => { + return new Promise((resolve, reject) => { sqlService .getProcedureDetail({ dataSourceId: treeNodeData.extraParams!.dataSourceId!, @@ -88,8 +106,16 @@ export const openProcedure = (props:{ procedureName: treeNodeData.name } as any) .then((res) => { - // 更新ddl - resolve(res.procedureBody); + if (res && res.procedureBody) { + resolve(res.procedureBody); + } else { + console.warn('[openProcedure] procedureBody is empty, response:', res); + resolve('-- Procedure body not available or empty'); + } + }) + .catch((err) => { + console.error('[openProcedure] Failed to get procedure detail:', err); + reject(err); }); }); } @@ -97,6 +123,7 @@ export const openProcedure = (props:{ } export const openTrigger = (props:{ + addWorkspaceTab: any; treeNodeData: any; }) => { const {treeNodeData } = props; @@ -109,7 +136,7 @@ export const openTrigger = (props:{ databaseName: treeNodeData.extraParams?.databaseName, schemaName: treeNodeData.extraParams?.schemaName, loadSQL: ()=>{ - return new Promise((resolve) => { + return new Promise((resolve, reject) => { sqlService .getTriggerDetail({ dataSourceId: treeNodeData.extraParams!.dataSourceId!, @@ -119,8 +146,16 @@ export const openTrigger = (props:{ triggerName: treeNodeData.name } as any) .then((res) => { - // 更新ddl - resolve(res.triggerBody); + if (res && res.triggerBody) { + resolve(res.triggerBody); + } else { + console.warn('[openTrigger] triggerBody is empty, response:', res); + resolve('-- Trigger body not available or empty'); + } + }) + .catch((err) => { + console.error('[openTrigger] Failed to get trigger detail:', err); + reject(err); }); }); } diff --git a/chat2db-client/src/blocks/Tree/functions/truncateTable.tsx b/chat2db-client/src/blocks/Tree/functions/truncateTable.tsx new file mode 100644 index 000000000..ddd88c6ed --- /dev/null +++ b/chat2db-client/src/blocks/Tree/functions/truncateTable.tsx @@ -0,0 +1,72 @@ +import React, { useState } from 'react'; +import mysqlService from '@/service/sql'; +import { Button, Checkbox } from 'antd'; +import { openModal } from '@/store/common/components'; +import styles from './deleteTable.less'; +import i18n from '@/i18n'; + +export const truncateTable = (treeNodeData, loadData) => { + openModal({ + width: '450px', + content: , + }); +}; + +export const TruncateModalContent = (params: { treeNodeData: any; openModal: any; loadData: any }) => { + const { treeNodeData, loadData, openModal } = params; + // 禁用确定按钮 + const [userChecked, setUserChecked] = useState(false); + + const onOk = () => { + const p: any = { + dataSourceId: treeNodeData.extraParams.dataSourceId, + databaseName: treeNodeData.extraParams.databaseName, + schemaName: treeNodeData.extraParams.schemaName, + tableName: treeNodeData.name, + }; + mysqlService.truncateTable(p).then(() => { + // 如果有 parentNode,刷新父节点;否则刷新当前节点 + if (treeNodeData.parentNode?.loadData) { + loadData({ + refresh: true, + treeNodeData: treeNodeData.parentNode + }); + } else { + loadData({ refresh: true }); + } + openModal(false); + }) +.catch((error) => { + console.error('Error truncating table:', error); + }); + }; + + return ( +
+
{i18n('workspace.tree.truncate.table.tip', `"${treeNodeData.name}"`)}
+
+ { + setUserChecked(e.target.checked); + }} + > + {i18n('workspace.tree.truncate.tip')} + +
+
+ + +
+
+ ); +}; diff --git a/chat2db-client/src/blocks/Tree/hooks/useGetRightClickMenu.ts b/chat2db-client/src/blocks/Tree/hooks/useGetRightClickMenu.ts index 9c7112ec0..20ac0d3a7 100644 --- a/chat2db-client/src/blocks/Tree/hooks/useGetRightClickMenu.ts +++ b/chat2db-client/src/blocks/Tree/hooks/useGetRightClickMenu.ts @@ -1,6 +1,8 @@ -import { ITreeNode } from '@/typings'; -import { OperationColumn, WorkspaceTabType, TreeNodeType } from '@/constants'; +import { DatabaseTypeCode, OperationColumn, TreeNodeType, WorkspaceTabType } from '@/constants'; import i18n from '@/i18n'; +import sqlServer from '@/service/sql'; +import { ITreeNode } from '@/typings'; +import { message } from 'antd'; import { v4 as uuid } from 'uuid'; // ----- components ----- @@ -8,18 +10,22 @@ import { dataSourceFormConfigs } from '@/components/ConnectionEdit/config/dataSo import { IConnectionConfig } from '@/components/ConnectionEdit/config/types'; // ----- config ----- -import { ITreeConfigItem, treeConfig } from '../treeConfig'; import { useMemo } from 'react'; +import { ITreeConfigItem, treeConfig } from '../treeConfig'; // ----- store ----- -import { createConsole, addWorkspaceTab } from '@/pages/main/workspace/store/console'; import { useWorkspaceStore } from '@/pages/main/workspace/store'; +import { addWorkspaceTab, createConsole } from '@/pages/main/workspace/store/console'; // ---- functions ----- -import { openView, openFunction, openProcedure, openTrigger } from '../functions/openAsyncSql'; +import { deleteTable } from '../functions/deleteTable'; +import { deleteDatabase } from '../functions/deleteDatabase'; +import { truncateTable } from '../functions/truncateTable'; +import { openFunction, openProcedure, openTrigger, openView } from '../functions/openAsyncSql'; import { handelPinTable } from '../functions/pinTable'; +import { deprecatedTable, restoreDeprecatedTable } from '../functions/deprecatedTable'; import { viewDDL } from '../functions/viewDDL'; -import { deleteTable } from '../functions/deleteTable'; +import { openTableRelationModal } from '../components/TableRelationModal'; // ----- utils ----- import { compatibleDataBaseName } from '@/utils/database'; @@ -27,6 +33,7 @@ import { compatibleDataBaseName } from '@/utils/database'; interface IProps { treeNodeData: ITreeNode; loadData: any; + refreshRootData?: (refresh?: boolean) => void; } interface IOperationColumnConfigItem { @@ -38,18 +45,77 @@ interface IOperationColumnConfigItem { } interface IRightClickMenu { - key: number; + key: number | string; onClick: (treeNodeData: ITreeNode) => void; - type: OperationColumn; + type: OperationColumn | string; doubleClickTrigger?: boolean; labelProps: { icon: string; label: string; }; + children?: IRightClickMenu[]; } +// 将"导入数据""导出数据""生成数据"合并到二级菜单"数据操作" +const DATA_OPS: (OperationColumn | string)[] = [ + OperationColumn.ImportData, + OperationColumn.ExportData, + OperationColumn.DataTransfer, + OperationColumn.GenerateData, +]; + +const REDIS_EXCLUDED_OPERATIONS = new Set([ + OperationColumn.SchemaDiff, + OperationColumn.CreateSchema, + OperationColumn.CreateTable, + OperationColumn.ViewERDiagram, + OperationColumn.ViewTableRelation, + OperationColumn.ViewDDL, + OperationColumn.ViewAllTable, + OperationColumn.ExportSchemaDoc, + OperationColumn.ExecuteSqlStatement, + OperationColumn.DeleteDatabase, + OperationColumn.DeleteTable, + OperationColumn.EditTable, + OperationColumn.EditTableData, + OperationColumn.TruncateTable, + OperationColumn.ImportData, + OperationColumn.ExportData, + OperationColumn.DataTransfer, + OperationColumn.DeprecatedTable, + OperationColumn.RestoreTable, + OperationColumn.GenerateData, +]); + +const groupDataOperations = (list: IRightClickMenu[]): IRightClickMenu[] => { + const dataOps = list.filter((m) => DATA_OPS.includes(m.type)); + if (dataOps.length === 0) return list; + const result: IRightClickMenu[] = []; + let inserted = false; + list.forEach((m) => { + if (DATA_OPS.includes(m.type)) { + if (!inserted) { + result.push({ + key: 'dataOperation', + type: 'dataOperation', + onClick: () => {}, + labelProps: { + icon: '\ue653', + label: i18n('workspace.menu.dataOperation'), + }, + children: dataOps, + }); + inserted = true; + } + } else { + result.push(m); + } + }); + return result; +}; + export const useGetRightClickMenu = (props: IProps) => { - const { treeNodeData, loadData } = props; + const { treeNodeData, loadData, refreshRootData } = props; const { openCreateDatabaseModal, currentConnectionDetails } = useWorkspaceStore((state) => { return { @@ -59,12 +125,11 @@ export const useGetRightClickMenu = (props: IProps) => { }); const handelOpenCreateDatabaseModal = (type: 'database' | 'schema') => { - const relyOnParams = { databaseType: treeNodeData.extraParams!.databaseType, dataSourceId: treeNodeData.extraParams!.dataSourceId!, databaseName: treeNodeData.name, - } + }; openCreateDatabaseModal?.({ type, @@ -90,6 +155,7 @@ export const useGetRightClickMenu = (props: IProps) => { function excludeSomeOperation() { const excludes = dataSourceFormConfig.baseInfo.excludes; const newOperationColumn: OperationColumn[] = []; + const isRedis = treeNodeData.extraParams?.databaseType === DatabaseTypeCode.REDIS; operationColumn?.map((item: OperationColumn) => { let flag = false; excludes?.map((t) => { @@ -97,6 +163,9 @@ export const useGetRightClickMenu = (props: IProps) => { flag = true; } }); + if (isRedis && REDIS_EXCLUDED_OPERATIONS.has(item)) { + flag = true; + } if (!flag) { newOperationColumn.push(item); } @@ -120,13 +189,61 @@ export const useGetRightClickMenu = (props: IProps) => { [OperationColumn.CreateConsole]: { text: i18n('workspace.menu.queryConsole'), icon: '\ue619', + doubleClickTrigger: treeNodeData.treeNodeType === TreeNodeType.REDIS_QUERY, handle: () => { + const isRedis = treeNodeData.extraParams?.databaseType === DatabaseTypeCode.REDIS; + const tableName = isRedis + ? '' + : compatibleDataBaseName( + treeNodeData.name!, + treeNodeData.extraParams!.databaseType, + treeNodeData.extraParams?.schemaName, + ); createConsole({ dataSourceId: treeNodeData.extraParams!.dataSourceId!, dataSourceName: treeNodeData.extraParams!.dataSourceName!, databaseType: treeNodeData.extraParams!.databaseType!, databaseName: treeNodeData.extraParams?.databaseName, schemaName: treeNodeData.extraParams?.schemaName, + ddl: isRedis ? '' : `select * from ${tableName}`, + }); + }, + }, + + [OperationColumn.OpenRedisData]: { + text: '打开数据', + icon: '\ue618', + doubleClickTrigger: true, + handle: () => { + addWorkspaceTab({ + id: `${OperationColumn.OpenRedisData}-${treeNodeData.uuid}`, + title: `${treeNodeData.extraParams?.databaseName || '0'}-数据`, + type: WorkspaceTabType.RedisData, + uniqueData: { + dataSourceId: treeNodeData.extraParams!.dataSourceId!, + dataSourceName: treeNodeData.extraParams!.dataSourceName!, + databaseType: treeNodeData.extraParams!.databaseType!, + databaseName: treeNodeData.extraParams?.databaseName, + }, + }); + }, + }, + + [OperationColumn.OpenRedisMonitor]: { + text: '打开监控', + icon: '\ue611', + doubleClickTrigger: true, + handle: () => { + addWorkspaceTab({ + id: `${OperationColumn.OpenRedisMonitor}-${treeNodeData.uuid}`, + title: `${treeNodeData.extraParams?.databaseName || '0'}-监控`, + type: WorkspaceTabType.RedisMonitor, + uniqueData: { + dataSourceId: treeNodeData.extraParams!.dataSourceId!, + dataSourceName: treeNodeData.extraParams!.dataSourceName!, + databaseType: treeNodeData.extraParams!.databaseType!, + databaseName: treeNodeData.extraParams?.databaseName, + }, }); }, }, @@ -147,8 +264,43 @@ export const useGetRightClickMenu = (props: IProps) => { databaseName: treeNodeData.extraParams?.databaseName, schemaName: treeNodeData.extraParams?.schemaName, }, - }) - + }); + }, + }, + // 添加查看 ER 图 + [OperationColumn.ViewERDiagram]: { + text: i18n('workspace.menu.viewERDiagram'), + icon: '\ue611', + handle: () => { + addWorkspaceTab({ + id: uuid(), + type: WorkspaceTabType.ViewERDiagram, + title: `${treeNodeData.extraParams!.databaseName!}-ER`, + uniqueData: { + dataSourceId: treeNodeData.extraParams!.dataSourceId!, + dataSourceName: treeNodeData.extraParams!.dataSourceName!, + databaseType: treeNodeData.extraParams!.databaseType!, + databaseName: treeNodeData.extraParams?.databaseName, + schemaName: treeNodeData.extraParams?.schemaName, + }, + }); + }, + }, + + // 结构对比 + [OperationColumn.SchemaDiff]: { + text: i18n('schemaDiff.title'), + icon: '\ue6f3', + handle: () => { + addWorkspaceTab({ + id: uuid(), + type: WorkspaceTabType.SchemaDiff, + title: i18n('schemaDiff.title'), + uniqueData: { + dataSourceId: treeNodeData.extraParams!.dataSourceId!, + databaseType: treeNodeData.extraParams!.databaseType!, + }, + }); }, }, @@ -166,11 +318,13 @@ export const useGetRightClickMenu = (props: IProps) => { databaseType: treeNodeData.extraParams!.databaseType!, databaseName: treeNodeData.extraParams?.databaseName, schemaName: treeNodeData.extraParams?.schemaName, - submitCallback: () => {loadData?.({refresh: true})}, + submitCallback: () => { + loadData?.({ refresh: true }); + }, }, }); }, - discard: (treeNodeData.treeNodeType === TreeNodeType.DATABASE && currentConnectionDetails?.supportSchema), + discard: treeNodeData.treeNodeType === TreeNodeType.DATABASE && currentConnectionDetails?.supportSchema, }, // 删除表 @@ -178,16 +332,22 @@ export const useGetRightClickMenu = (props: IProps) => { text: i18n('workspace.menu.deleteTable'), icon: '\ue6a7', handle: () => { - deleteTable(treeNodeData,loadData); + deleteTable(treeNodeData, loadData); + }, + }, + [OperationColumn.TruncateTable]: { + text: i18n('workspace.menu.truncateTable'), // 假设i18n函数已定义好对应的语言资源 + icon: '\ue60c', // 选择一个合适的图标 + handle: () => { + truncateTable(treeNodeData, loadData); }, }, - // 查看ddl [OperationColumn.ViewDDL]: { text: i18n('workspace.menu.ViewDDL'), icon: '\ue665', handle: () => { - viewDDL(treeNodeData) + viewDDL(treeNodeData); }, }, @@ -199,8 +359,8 @@ export const useGetRightClickMenu = (props: IProps) => { handelPinTable({ treeNodeData, loadData: () => { - loadData({treeNodeData:treeNodeData.parentNode}) - } + loadData({ treeNodeData: treeNodeData.parentNode }); + }, }); }, }, @@ -221,11 +381,10 @@ export const useGetRightClickMenu = (props: IProps) => { schemaName: treeNodeData.extraParams?.schemaName, tableName: treeNodeData?.name, submitCallback: () => { - loadData({ treeNodeData: treeNodeData.parentNode, - refresh: true - }) + refresh: true, + }); }, }, }); @@ -247,7 +406,11 @@ export const useGetRightClickMenu = (props: IProps) => { icon: '\ue618', doubleClickTrigger: true, handle: () => { - const databaseName = compatibleDataBaseName(treeNodeData.name!, treeNodeData.extraParams!.databaseType); + const databaseName = compatibleDataBaseName( + treeNodeData.name!, + treeNodeData.extraParams!.databaseType, + treeNodeData.extraParams?.schemaName, + ); addWorkspaceTab({ id: `${OperationColumn.OpenTable}-${treeNodeData.uuid}`, title: treeNodeData.name, @@ -325,6 +488,26 @@ export const useGetRightClickMenu = (props: IProps) => { }, }, + // 删除数据库 + [OperationColumn.DeleteDatabase]: { + text: i18n('workspace.menu.deleteDatabase'), + icon: '\ue6a7', + handle: () => { + deleteDatabase(treeNodeData, loadData, refreshRootData); + }, + }, + + [OperationColumn.ViewTableRelation]: { + text: + treeNodeData.treeNodeType === TreeNodeType.KEY || treeNodeData.treeNodeType === TreeNodeType.V_KEY + ? i18n('workspace.menu.view') + : i18n('workspace.menu.viewTableRelation'), + icon: '\ue611', + handle: () => { + openTableRelationModal(treeNodeData); + }, + }, + // 创建schema [OperationColumn.CreateSchema]: { text: i18n('workspace.menu.createSchema'), @@ -334,13 +517,152 @@ export const useGetRightClickMenu = (props: IProps) => { }, discard: !currentConnectionDetails?.supportSchema, }, + + // 删除虚拟外键 + [OperationColumn.DeleteVirtualKey]: { + text: i18n('workspace.menu.deleteVirtualKey'), + icon: '\ue6a7', + handle: async () => { + await deleteVirtualForeignKey(treeNodeData, loadData); + }, + }, + + // 导入数据 + [OperationColumn.ImportData]: { + text: i18n('workspace.menu.importData'), + icon: '\ue653', + handle: () => { + const { openImportDataModal } = useWorkspaceStore.getState(); + openImportDataModal?.({ + tableName: treeNodeData.name, + dataSourceId: treeNodeData.extraParams!.dataSourceId!, + databaseName: treeNodeData.extraParams?.databaseName, + schemaName: treeNodeData.extraParams?.schemaName, + executedCallback: () => { + loadData?.({ + refresh: true, + }); + }, + }); + }, + }, + + // 导出数据 + [OperationColumn.ExportData]: { + text: i18n('workspace.menu.exportData'), + icon: '\ue613', + handle: () => { + const { openExportDataModal } = useWorkspaceStore.getState(); + openExportDataModal?.({ + tableName: treeNodeData.name, + dataSourceId: treeNodeData.extraParams!.dataSourceId!, + databaseName: treeNodeData.extraParams?.databaseName, + schemaName: treeNodeData.extraParams?.schemaName, + }); + }, + }, + + // 数据传输 + [OperationColumn.DataTransfer]: { + text: '数据传输', + icon: '\ue60e', + handle: () => { + const { openDataTransferModal } = useWorkspaceStore.getState(); + openDataTransferModal?.({ + dataSourceId: treeNodeData.extraParams!.dataSourceId!, + databaseName: treeNodeData.extraParams?.databaseName, + schemaName: treeNodeData.extraParams?.schemaName, + tableNames: [treeNodeData.name], + executedCallback: () => { + loadData?.({ + refresh: true, + }); + }, + }); + }, + }, + + // 导出数据结构 + [OperationColumn.ExportSchemaDoc]: { + text: i18n('workspace.menu.exportSchemaDoc'), + icon: '\ue613', + handle: () => { + const { openExportSchemaDocModal } = useWorkspaceStore.getState(); + openExportSchemaDocModal?.({ + dataSourceId: treeNodeData.extraParams!.dataSourceId!, + databaseName: treeNodeData.extraParams?.databaseName, + schemaName: treeNodeData.extraParams?.schemaName, + }); + }, + }, + + [OperationColumn.ExecuteSqlStatement]: { + text: i18n('workspace.menu.executeSqlStatement'), + icon: '\ue619', + handle: () => { + const { openExecuteSqlStatementModal } = useWorkspaceStore.getState(); + openExecuteSqlStatementModal?.({ + dataSourceId: treeNodeData.extraParams!.dataSourceId!, + databaseName: treeNodeData.extraParams?.databaseName, + schemaName: treeNodeData.extraParams?.schemaName, + executedCallback: () => { + loadData?.({ + refresh: true, + }); + }, + }); + }, + }, + + // 废弃表 + [OperationColumn.DeprecatedTable]: { + text: i18n('workspace.menu.deprecatedTable'), + icon: '\ue73c', + handle: () => { + deprecatedTable({ + treeNodeData, + loadData: () => { + loadData({ treeNodeData: treeNodeData.parentNode }); + }, + }); + }, + }, + + // 恢复废弃表 + [OperationColumn.RestoreTable]: { + text: i18n('workspace.menu.restoreTable'), + icon: '\ue63e', + handle: () => { + restoreDeprecatedTable({ + treeNodeData, + loadData: () => { + loadData({ treeNodeData: treeNodeData.parentNode }); + }, + }); + }, + }, + + // 生成数据 + [OperationColumn.GenerateData]: { + text: i18n('workspace.menu.generateData'), + icon: '\ue816', + handle: () => { + const { openDataGenerationModal } = useWorkspaceStore.getState(); + openDataGenerationModal?.({ + dataSourceId: treeNodeData.extraParams!.dataSourceId!, + databaseName: treeNodeData.extraParams?.databaseName, + schemaName: treeNodeData.extraParams?.schemaName, + tableName: treeNodeData.name!, + }); + }, + }, }; // 根据配置生成右键菜单 const finalList: IRightClickMenu[] = []; excludeSomeOperation().forEach((t, i) => { const concrete = operationColumnConfig[t]; - if (!concrete.discard) { + if (!!concrete && !concrete.discard) { finalList.push({ key: i, onClick: concrete?.handle, @@ -353,7 +675,7 @@ export const useGetRightClickMenu = (props: IProps) => { }); } }); - return finalList; + return groupDataOperations(finalList); }, [treeNodeData]); return rightClickMenu; @@ -366,12 +688,11 @@ export const getRightClickMenu = (props: IProps) => { const currentConnectionDetails = useWorkspaceStore.getState().currentConnectionDetails; const handelOpenCreateDatabaseModal = (type: 'database' | 'schema') => { - const relyOnParams = { databaseType: treeNodeData.extraParams!.databaseType, dataSourceId: treeNodeData.extraParams!.dataSourceId!, databaseName: treeNodeData.name, - } + }; openCreateDatabaseModal?.({ type, @@ -426,13 +747,61 @@ export const getRightClickMenu = (props: IProps) => { [OperationColumn.CreateConsole]: { text: i18n('workspace.menu.queryConsole'), icon: '\ue619', + doubleClickTrigger: treeNodeData.treeNodeType === TreeNodeType.REDIS_QUERY, handle: () => { + const isRedis = treeNodeData.extraParams?.databaseType === DatabaseTypeCode.REDIS; + const tableName = isRedis + ? '' + : compatibleDataBaseName( + treeNodeData.name!, + treeNodeData.extraParams!.databaseType, + treeNodeData.extraParams?.schemaName, + ); createConsole({ dataSourceId: treeNodeData.extraParams!.dataSourceId!, dataSourceName: treeNodeData.extraParams!.dataSourceName!, databaseType: treeNodeData.extraParams!.databaseType!, databaseName: treeNodeData.extraParams?.databaseName, schemaName: treeNodeData.extraParams?.schemaName, + ddl: isRedis ? '' : `select * from ${tableName}`, + }); + }, + }, + + [OperationColumn.OpenRedisData]: { + text: '打开数据', + icon: '\ue618', + doubleClickTrigger: true, + handle: () => { + addWorkspaceTab({ + id: `${OperationColumn.OpenRedisData}-${treeNodeData.uuid}`, + title: `${treeNodeData.extraParams?.databaseName || '0'}-数据`, + type: WorkspaceTabType.RedisData, + uniqueData: { + dataSourceId: treeNodeData.extraParams!.dataSourceId!, + dataSourceName: treeNodeData.extraParams!.dataSourceName!, + databaseType: treeNodeData.extraParams!.databaseType!, + databaseName: treeNodeData.extraParams?.databaseName, + }, + }); + }, + }, + + [OperationColumn.OpenRedisMonitor]: { + text: '打开监控', + icon: '\ue611', + doubleClickTrigger: true, + handle: () => { + addWorkspaceTab({ + id: `${OperationColumn.OpenRedisMonitor}-${treeNodeData.uuid}`, + title: `${treeNodeData.extraParams?.databaseName || '0'}-监控`, + type: WorkspaceTabType.RedisMonitor, + uniqueData: { + dataSourceId: treeNodeData.extraParams!.dataSourceId!, + dataSourceName: treeNodeData.extraParams!.dataSourceName!, + databaseType: treeNodeData.extraParams!.databaseType!, + databaseName: treeNodeData.extraParams?.databaseName, + }, }); }, }, @@ -453,8 +822,26 @@ export const getRightClickMenu = (props: IProps) => { databaseName: treeNodeData.extraParams?.databaseName, schemaName: treeNodeData.extraParams?.schemaName, }, - }) - + }); + }, + }, + // 添加查看 ER 图 + [OperationColumn.ViewERDiagram]: { + text: i18n('workspace.menu.viewERDiagram'), + icon: '\ue611', + handle: () => { + addWorkspaceTab({ + id: uuid(), + type: WorkspaceTabType.ViewERDiagram, + title: `${treeNodeData.extraParams!.databaseName!}-ER`, + uniqueData: { + dataSourceId: treeNodeData.extraParams!.dataSourceId!, + dataSourceName: treeNodeData.extraParams!.dataSourceName!, + databaseType: treeNodeData.extraParams!.databaseType!, + databaseName: treeNodeData.extraParams?.databaseName, + schemaName: treeNodeData.extraParams?.schemaName, + }, + }); }, }, @@ -472,11 +859,13 @@ export const getRightClickMenu = (props: IProps) => { databaseType: treeNodeData.extraParams!.databaseType!, databaseName: treeNodeData.extraParams?.databaseName, schemaName: treeNodeData.extraParams?.schemaName, - submitCallback: () => {treeNodeData.loadData?.({refresh: true})}, + submitCallback: () => { + treeNodeData.loadData?.({ refresh: true }); + }, }, }); }, - discard: (treeNodeData.treeNodeType === TreeNodeType.DATABASE && currentConnectionDetails?.supportSchema), + discard: treeNodeData.treeNodeType === TreeNodeType.DATABASE && currentConnectionDetails?.supportSchema, }, // 删除表 @@ -484,7 +873,7 @@ export const getRightClickMenu = (props: IProps) => { text: i18n('workspace.menu.deleteTable'), icon: '\ue6a7', handle: () => { - deleteTable(treeNodeData); + deleteTable(treeNodeData, loadData); }, }, @@ -493,7 +882,7 @@ export const getRightClickMenu = (props: IProps) => { text: i18n('workspace.menu.ViewDDL'), icon: '\ue665', handle: () => { - viewDDL(treeNodeData) + viewDDL(treeNodeData); }, }, @@ -502,7 +891,7 @@ export const getRightClickMenu = (props: IProps) => { text: treeNodeData.pinned ? i18n('workspace.menu.unPin') : i18n('workspace.menu.pin'), icon: treeNodeData.pinned ? '\ue61d' : '\ue627', handle: () => { - handelPinTable({treeNodeData, loadData: treeNodeData.parentNode!.loadData!}); + handelPinTable({ treeNodeData, loadData: treeNodeData.parentNode!.loadData! }); }, }, @@ -521,7 +910,9 @@ export const getRightClickMenu = (props: IProps) => { databaseName: treeNodeData.extraParams?.databaseName, schemaName: treeNodeData.extraParams?.schemaName, tableName: treeNodeData?.name, - submitCallback: () => {treeNodeData.parentNode?.loadData?.({refresh: true})}, + submitCallback: () => { + treeNodeData.parentNode?.loadData?.({ refresh: true }); + }, }, }); }, @@ -542,7 +933,11 @@ export const getRightClickMenu = (props: IProps) => { icon: '\ue618', doubleClickTrigger: true, handle: () => { - const databaseName = compatibleDataBaseName(treeNodeData.name!, treeNodeData.extraParams!.databaseType); + const databaseName = compatibleDataBaseName( + treeNodeData.name!, + treeNodeData.extraParams!.databaseType, + treeNodeData.extraParams?.schemaName, + ); addWorkspaceTab({ id: `${OperationColumn.OpenTable}-${treeNodeData.uuid}`, title: treeNodeData.name, @@ -629,13 +1024,173 @@ export const getRightClickMenu = (props: IProps) => { }, discard: !currentConnectionDetails?.supportSchema, }, + + // 删除虚拟外键 + [OperationColumn.DeleteVirtualKey]: { + text: i18n('workspace.menu.deleteVirtualKey'), + icon: '\ue6a7', + handle: async () => { + await deleteVirtualForeignKey(treeNodeData, loadData); + }, + }, + + // 导入数据 + [OperationColumn.ImportData]: { + text: i18n('workspace.menu.importData'), + icon: '\ue653', + handle: () => { + const { openImportDataModal } = useWorkspaceStore.getState(); + openImportDataModal?.({ + tableName: treeNodeData.name, + dataSourceId: treeNodeData.extraParams!.dataSourceId!, + databaseName: treeNodeData.extraParams?.databaseName, + schemaName: treeNodeData.extraParams?.schemaName, + executedCallback: () => { + loadData?.({ + refresh: true, + }); + }, + }); + }, + }, + + // 导出数据 + [OperationColumn.ExportData]: { + text: i18n('workspace.menu.exportData'), + icon: '\ue613', + handle: () => { + const { openExportDataModal } = useWorkspaceStore.getState(); + openExportDataModal?.({ + tableName: treeNodeData.name, + dataSourceId: treeNodeData.extraParams!.dataSourceId!, + databaseName: treeNodeData.extraParams?.databaseName, + schemaName: treeNodeData.extraParams?.schemaName, + }); + }, + }, + + [OperationColumn.ViewTableRelation]: { + text: + treeNodeData.treeNodeType === TreeNodeType.KEY || treeNodeData.treeNodeType === TreeNodeType.V_KEY + ? i18n('workspace.menu.view') + : i18n('workspace.menu.viewTableRelation'), + icon: '\ue611', + handle: () => { + openTableRelationModal(treeNodeData); + }, + }, + + // 数据传输 + [OperationColumn.DataTransfer]: { + text: '数据传输', + icon: '\ue60e', + handle: () => { + const { openDataTransferModal } = useWorkspaceStore.getState(); + openDataTransferModal?.({ + dataSourceId: treeNodeData.extraParams!.dataSourceId!, + databaseName: treeNodeData.extraParams?.databaseName, + schemaName: treeNodeData.extraParams?.schemaName, + tableNames: [treeNodeData.name], + executedCallback: () => { + loadData?.({ + refresh: true, + }); + }, + }); + }, + }, + + // 导出数据结构 + [OperationColumn.ExportSchemaDoc]: { + text: i18n('workspace.menu.exportSchemaDoc'), + icon: '\ue613', + handle: () => { + const { openExportSchemaDocModal } = useWorkspaceStore.getState(); + openExportSchemaDocModal?.({ + dataSourceId: treeNodeData.extraParams!.dataSourceId!, + databaseName: treeNodeData.extraParams?.databaseName, + schemaName: treeNodeData.extraParams?.schemaName, + }); + }, + }, + + [OperationColumn.ExecuteSqlStatement]: { + text: i18n('workspace.menu.executeSqlStatement'), + icon: '\ue619', + handle: () => { + const { openExecuteSqlStatementModal } = useWorkspaceStore.getState(); + openExecuteSqlStatementModal?.({ + dataSourceId: treeNodeData.extraParams!.dataSourceId!, + databaseName: treeNodeData.extraParams?.databaseName, + schemaName: treeNodeData.extraParams?.schemaName, + executedCallback: () => { + loadData?.({ + refresh: true, + }); + }, + }); + }, + }, + + // 废弃表 + [OperationColumn.DeprecatedTable]: { + text: i18n('workspace.menu.deprecatedTable'), + icon: '\ue73c', + handle: () => { + deprecatedTable({ + treeNodeData, + loadData: () => { + // 如果有 parentNode,刷新父节点;否则刷新当前节点 + if (treeNodeData.parentNode?.loadData) { + loadData({ treeNodeData: treeNodeData.parentNode }); + } else { + loadData({ refresh: true }); + } + }, + }); + }, + }, + + // 恢复废弃表 + [OperationColumn.RestoreTable]: { + text: i18n('workspace.menu.restoreTable'), + icon: '\ue63e', + handle: () => { + restoreDeprecatedTable({ + treeNodeData, + loadData: () => { + // 如果有 parentNode,刷新父节点;否则刷新当前节点 + if (treeNodeData.parentNode?.loadData) { + loadData({ treeNodeData: treeNodeData.parentNode }); + } else { + loadData({ refresh: true }); + } + }, + }); + }, + }, + + // 生成数据 + [OperationColumn.GenerateData]: { + text: i18n('workspace.menu.generateData'), + icon: '\ue816', + handle: () => { + const { openDataGenerationModal } = useWorkspaceStore.getState(); + openDataGenerationModal?.({ + dataSourceId: treeNodeData.extraParams!.dataSourceId!, + databaseName: treeNodeData.extraParams?.databaseName, + schemaName: treeNodeData.extraParams?.schemaName, + tableName: treeNodeData.name!, + }); + }, + }, }; // 根据配置生成右键菜单 const finalList: IRightClickMenu[] = []; - excludeSomeOperation().forEach((t,i) => { + excludeSomeOperation().forEach((t, i) => { const concrete = operationColumnConfig[t]; - if (!concrete.discard) { + if (!!concrete && !concrete.discard) { finalList.push({ key: i, onClick: concrete?.handle, @@ -648,5 +1203,37 @@ export const getRightClickMenu = (props: IProps) => { }); } }); - return finalList; + return groupDataOperations(finalList); +}; + +const deleteVirtualForeignKey = async (treeNode: ITreeNode, loadData: () => void) => { + const { dataSourceId, databaseName, schemaName, tableName } = treeNode.extraParams!; + if (!databaseName) { + message.error('数据库名称不能为空'); + return; + } + if (!tableName) { + message.error('表名不能为空'); + return; + } + try { + await sqlServer.deleteVirtualForeignKey({ + dataSourceId, + databaseName, + schemaName, + tableName, + keyName: treeNode.name, + }); + + message.success('删除虚拟外键成功'); + + // 刷新父节点(KEYS节点) + loadData({ + refresh: true, + treeNodeData: treeNode.parentNode, + }); + } catch (error) { + message.error('删除虚拟外键失败'); + console.error('删除虚拟外键失败:', error); + } }; diff --git a/chat2db-client/src/blocks/Tree/index.tsx b/chat2db-client/src/blocks/Tree/index.tsx index b134dcb28..ff630b846 100644 --- a/chat2db-client/src/blocks/Tree/index.tsx +++ b/chat2db-client/src/blocks/Tree/index.tsx @@ -1,4 +1,4 @@ -import React, { memo, useEffect, useMemo, useState, createContext, useContext } from 'react'; +import React, { memo, useEffect, useMemo, useState, createContext, useContext, useRef } from 'react'; import styles from './index.less'; import classnames from 'classnames'; import Iconfont from '@/components/Iconfont'; @@ -8,23 +8,27 @@ import { TreeNodeType, databaseMap } from '@/constants'; import { treeConfig, switchIcon, ITreeConfigItem } from './treeConfig'; import { useCommonStore } from '@/store/common'; import { setCurrentWorkspaceGlobalExtend } from '@/pages/main/workspace/store/common'; +import { useWorkspaceStore } from '@/pages/main/workspace/store'; import LoadingGracile from '@/components/Loading/LoadingGracile'; import { setFocusId, setFocusTreeNode, useTreeStore, clearTreeStore } from './treeStore'; import { useGetRightClickMenu } from './hooks/useGetRightClickMenu'; import MenuLabel from '@/components/MenuLabel'; import LoadingContent from '@/components/Loading/LoadingContent'; import { cloneDeep } from 'lodash'; +import sqlService from '@/service/sql'; // import { flushSync } from 'react-dom'; interface IProps { className?: string; treeData: ITreeNode[] | null; searchValue: string; + refreshRootData?: (refresh?: boolean) => void; } interface TreeNodeIProps { data: ITreeNode; level: number; + refreshRootData?: (refresh?: boolean) => void; } interface IContext { @@ -52,18 +56,18 @@ const smoothTree = (treeData: ITreeNode[], result: ITreeNode[] = [], parentNode? }; // 平级转树 -function tranListToTreeData(list:ITreeNode[], rootValue) { - const arr:ITreeNode[] = [] - list.forEach((item:ITreeNode) => { +function tranListToTreeData(list: ITreeNode[], rootValue) { + const arr: ITreeNode[] = []; + list.forEach((item: ITreeNode) => { if (item.parentNode?.uuid === rootValue) { - arr.push(item) - const children = tranListToTreeData(list, item.uuid) + arr.push(item); + const children = tranListToTreeData(list, item.uuid); if (children.length) { - item.children = children + item.children = children; } } - }) - return arr + }); + return arr; } // 判断是否匹配 @@ -108,11 +112,18 @@ const itemHeight = 26; // 每个 item 的高度 const paddingCount = 2; const Tree = (props: IProps) => { - const { className, treeData: outerTreeData, searchValue } = props; + const { className, treeData: outerTreeData, searchValue, refreshRootData } = props; const [treeData, setTreeData] = useState(null); const [smoothTreeData, setSmoothTreeData] = useState([]); - const [searchTreeData, setSearchTreeData] = useState(null); // 搜索结果 - const [searchSmoothTreeData, setSearchSmoothTreeData] = useState(null); // 搜索结果 平级 + const [searchTreeData, setSearchTreeData] = useState(null); // 前端搜索结果 + const [searchSmoothTreeData, setSearchSmoothTreeData] = useState(null); // 前端搜索结果平级 + const [backendSmoothTreeData, setBackendSmoothTreeData] = useState(null); // 后端搜索结果平级 + const [backendSearchLoading, setBackendSearchLoading] = useState(false); + const searchValueRef = useRef(''); + const searchAbortControllerRef = useRef(null); + const searchRequestIdRef = useRef(0); + + const currentConnectionDetails = useWorkspaceStore((state) => state.currentConnectionDetails); const [scrollTop, setScrollTop] = useState(0); // 滚动位置 // 继续需要渲染的 item 索引有哪些 @@ -128,7 +139,7 @@ const Tree = (props: IProps) => { useEffect(() => { return () => { clearTreeStore(); - } + }; }, [searchValue]); useEffect(() => { @@ -158,30 +169,181 @@ const Tree = (props: IProps) => { }, [searchTreeData]); const treeNodes = useMemo(() => { - const realNodeList = (searchSmoothTreeData || smoothTreeData).slice(startIdx, startIdx + 50); + const realNodeList = (backendSmoothTreeData || searchSmoothTreeData || smoothTreeData).slice( + startIdx, + startIdx + 50, + ); return realNodeList.map((item) => { - return ; + return ; }); - }, [smoothTreeData, searchSmoothTreeData, startIdx]); + }, [smoothTreeData, searchSmoothTreeData, backendSmoothTreeData, startIdx, refreshRootData]); useEffect(() => { if (searchValue && treeData) { + searchValueRef.current = searchValue; const _searchTreeData = searchTree(cloneDeep(treeData), searchValue); - setSearchTreeData(_searchTreeData); - setScrollTop(0); + + const flatResult: ITreeNode[] = []; + smoothTree(_searchTreeData, flatResult); + const matchCount = flatResult.filter((item) => isMatch(item.name, searchValue)).length; + + if (matchCount > 0) { + setSearchTreeData(_searchTreeData); + setBackendSmoothTreeData(null); + setScrollTop(0); + } else if (currentConnectionDetails?.id) { + setSearchTreeData(null); + setBackendSearchLoading(true); + searchAbortControllerRef.current?.abort(); + searchAbortControllerRef.current = new AbortController(); + const searchRequestId = searchRequestIdRef.current + 1; + searchRequestIdRef.current = searchRequestId; + const signal = searchAbortControllerRef.current.signal; + sqlService + .searchTree( + { + dataSourceId: currentConnectionDetails.id, + dataSourceName: currentConnectionDetails.alias, + databaseType: currentConnectionDetails.type, + searchKey: searchValue, + }, + { signal }, + ) + .then((res) => { + if (!signal.aborted && searchRequestIdRef.current === searchRequestId) { + const enrichedNodes = (res || []).map((node) => ({ + ...node, + treeNodeType: node.treeNodeType?.toLowerCase() || node.treeNodeType, + pretendNodeType: node.pretendNodeType?.toLowerCase() || node.pretendNodeType, + extraParams: { + ...node.extraParams, + dataSourceId: currentConnectionDetails.id, + dataSourceName: currentConnectionDetails.alias, + databaseType: currentConnectionDetails.type, + }, + })); + const treeResult = buildTreeFromFlatData(enrichedNodes); + const smoothResult: ITreeNode[] = []; + smoothTree(treeResult, smoothResult); + setBackendSmoothTreeData(smoothResult); + setScrollTop(0); + } + }) + .catch(() => { + if (!signal.aborted && searchRequestIdRef.current === searchRequestId) { + setBackendSmoothTreeData([]); + } + }) + .finally(() => { + if (!signal.aborted && searchRequestIdRef.current === searchRequestId) { + setBackendSearchLoading(false); + } + }); + } else { + setSearchTreeData(_searchTreeData); + setBackendSmoothTreeData(null); + setScrollTop(0); + } } else { + searchValueRef.current = ''; + searchAbortControllerRef.current?.abort(); + searchRequestIdRef.current += 1; setSearchTreeData(null); + setBackendSmoothTreeData(null); + setBackendSearchLoading(false); } - }, [searchValue]); + }, [searchValue, treeData, currentConnectionDetails]); + + useEffect(() => { + return () => { + searchAbortControllerRef.current?.abort(); + searchRequestIdRef.current += 1; + }; + }, []); + + function buildTreeFromFlatData(flatNodes: ITreeNode[]): ITreeNode[] { + if (!flatNodes || flatNodes.length === 0) return []; + + const firstNode = flatNodes[0]; + const baseExtraParams = firstNode.extraParams || {}; + + const map = new Map(); + const pathMap = new Map(); + const roots: ITreeNode[] = []; + + flatNodes.forEach((item) => { + map.set(item.uuid, { ...item, children: [] }); + }); + + flatNodes.forEach((node) => { + if (node.parentPath && node.parentPath.length > 0) { + let currentPath = ''; + node.parentPath.forEach((pathItem, index) => { + const prevPath = currentPath; + currentPath = prevPath ? `${prevPath}/${pathItem}` : pathItem; + + if (!pathMap.has(currentPath)) { + const pathNode: ITreeNode = { + uuid: `path-${currentPath}`, + key: `path-${currentPath}`, + name: pathItem, + treeNodeType: index === 0 ? TreeNodeType.DATABASE : TreeNodeType.SCHEMAS, + pretendNodeType: index === 0 ? TreeNodeType.DATABASE : TreeNodeType.SCHEMAS, + isLeaf: false, + children: [], + extraParams: { + ...baseExtraParams, + databaseName: index === 0 ? pathItem : baseExtraParams.databaseName, + schemaName: index === 1 ? pathItem : baseExtraParams.schemaName, + }, + }; + pathMap.set(currentPath, pathNode); + } + }); + } + }); + + pathMap.forEach((pathNode, path) => { + const pathParts = path.split('/'); + if (pathParts.length > 1) { + const parentPath = pathParts.slice(0, -1).join('/'); + const parentNode = pathMap.get(parentPath); + if (parentNode && parentNode.children) { + parentNode.children.push(pathNode); + pathNode.parentNode = parentNode; + } + } else { + roots.push(pathNode); + } + }); + + flatNodes.forEach((node) => { + const mappedNode = map.get(node.uuid); + if (mappedNode) { + if (node.parentPath && node.parentPath.length > 0) { + const parentKey = node.parentPath.join('/'); + const parentNode = pathMap.get(parentKey); + if (parentNode && parentNode.children) { + parentNode.children.push(mappedNode); + mappedNode.parentNode = parentNode; + } + } else { + roots.push(mappedNode); + } + } + }); + + return roots; + } return ( - +
{ >
{treeNodes} @@ -204,77 +368,135 @@ const Tree = (props: IProps) => { }; const TreeNode = memo((props: TreeNodeIProps) => { - const { data: treeNodeData, level } = props; + const { data: treeNodeData, level, refreshRootData } = props; const [isLoading, setIsLoading] = useState(false); const indentArr = new Array(level).fill('indent'); const { treeData, setTreeData, searchTreeData, setSearchTreeData } = useContext(Context); + const abortControllerRef = useRef(null); + const loadRequestIdRef = useRef(0); // 加载数据 - function loadData(_props?: { refresh: boolean; pageNo: number; treeNodeData?: ITreeNode }) { + function loadData(_props?: { + refresh?: boolean; + pageNo?: number; + lastDocId?: number; + treeNodeData?: ITreeNode; + deletedNodeName?: string; + }) { const _treeNodeData = _props?.treeNodeData || props.data; const treeNodeConfig: ITreeConfigItem = treeConfig[_treeNodeData.pretendNodeType || _treeNodeData.treeNodeType]; + + if (_props?.refresh) { + if (abortControllerRef.current) { + abortControllerRef.current.abort(); + } + abortControllerRef.current = new AbortController(); + } + + const signal = abortControllerRef.current?.signal; + const loadRequestId = loadRequestIdRef.current + 1; + loadRequestIdRef.current = loadRequestId; + setIsLoading(true); if (_props?.pageNo === 1 || !_props?.pageNo) { - insertData(treeData!, _treeNodeData.uuid!, null,[treeData, setTreeData]); - if(searchTreeData){ - insertData(searchTreeData!, _treeNodeData.uuid!, null,[searchTreeData, setSearchTreeData]); + insertData(treeData!, _treeNodeData.uuid!, null, [treeData, setTreeData]); + if (searchTreeData) { + insertData(searchTreeData!, _treeNodeData.uuid!, null, [searchTreeData, setSearchTreeData]); } } - treeNodeConfig - .getChildren?.({ - ..._treeNodeData.extraParams, - extraParams: { - ..._treeNodeData.extraParams, - }, - refresh: _props?.refresh || false, - pageNo: _props?.pageNo || 1, - }) + Promise.resolve() + .then( + () => + treeNodeConfig.getChildren?.( + { + ..._treeNodeData.extraParams, + extraParams: { + ..._treeNodeData.extraParams, + }, + refresh: _props?.refresh || false, + pageNo: _props?.pageNo || 1, + lastDocId: _props?.lastDocId, + }, + { signal }, + ) || [], + ) .then((res: any) => { - if (res.length || res.data) { - if (res.data) { - insertData(treeData!, _treeNodeData.uuid!, res.data, [treeData, setTreeData]); - if(searchTreeData){ - insertData(searchTreeData!, _treeNodeData.uuid!, res.data,[searchTreeData, setSearchTreeData]); + if (signal?.aborted) return; + const filteredRes = filterDeletedNode(res, _props?.deletedNodeName); + if (filteredRes?.length || filteredRes?.data) { + if (filteredRes.data) { + insertData(treeData!, _treeNodeData.uuid!, filteredRes.data, [treeData, setTreeData]); + if (searchTreeData) { + insertData(searchTreeData!, _treeNodeData.uuid!, filteredRes.data, [searchTreeData, setSearchTreeData]); } - if (res.hasNextPage) { + if (filteredRes.hasNextPage) { loadData({ refresh: _props?.refresh || false, - pageNo: res.pageNo + 1, + pageNo: filteredRes.pageNo + 1, + lastDocId: filteredRes.lastDocId, + deletedNodeName: _props?.deletedNodeName, }); + return; } } else { - insertData(treeData!, _treeNodeData.uuid!, res,[treeData, setTreeData]); - if(searchTreeData){ - insertData(searchTreeData!, _treeNodeData.uuid!, res,[searchTreeData, setSearchTreeData]); + insertData(treeData!, _treeNodeData.uuid!, filteredRes, [treeData, setTreeData]); + if (searchTreeData) { + insertData(searchTreeData!, _treeNodeData.uuid!, filteredRes, [searchTreeData, setSearchTreeData]); } } - setIsLoading(false); } else { - // 处理树可能出现不连续的情况 + if (signal?.aborted) return; if (treeNodeConfig.next) { _treeNodeData.pretendNodeType = treeNodeConfig.next; loadData(); + return; } else { - insertData(treeData!, _treeNodeData.uuid!, [],[treeData, setTreeData]); - if(searchTreeData){ - insertData(searchTreeData!, _treeNodeData.uuid!, [],[searchTreeData, setSearchTreeData]); + insertData(treeData!, _treeNodeData.uuid!, [], [treeData, setTreeData]); + if (searchTreeData) { + insertData(searchTreeData!, _treeNodeData.uuid!, [], [searchTreeData, setSearchTreeData]); } - setIsLoading(false); } } }) - .catch(() => { + .catch((error) => { + if (signal?.aborted || error?.name === 'AbortError') return; + }) + .finally(() => { + if (loadRequestIdRef.current !== loadRequestId) return; setIsLoading(false); }); } + useEffect(() => { + return () => { + loadRequestIdRef.current += 1; + abortControllerRef.current?.abort(); + }; + }, []); + + const filterDeletedNode = (res: any, deletedNodeName?: string) => { + if (!deletedNodeName) { + return res; + } + if (res?.data) { + return { + ...res, + data: res.data.filter((item: ITreeNode) => item.name !== deletedNodeName), + }; + } + if (Array.isArray(res)) { + return res.filter((item: ITreeNode) => item.name !== deletedNodeName); + } + return res; + }; + // 当前节点是否是focus const isFocus = useTreeStore((state) => state.focusId) === treeNodeData.uuid; // 在treeData中找到对应的节点,插入数据 - const insertData = (_treeData: ITreeNode[], uuid: string, data: any, originalDataList:any): ITreeNode | null => { - const [originalData,setOriginalData] = originalDataList + const insertData = (_treeData: ITreeNode[], uuid: string, data: any, originalDataList: any): ITreeNode | null => { + const [originalData, setOriginalData] = originalDataList; let result: ITreeNode | null = null; for (let i = 0; i < _treeData?.length; i++) { if (_treeData[i].uuid === uuid) { @@ -304,9 +526,9 @@ const TreeNode = memo((props: TreeNodeIProps) => { //展开-收起 const handleClick = () => { if (treeNodeData?.children) { - insertData(treeData!, treeNodeData.uuid!, null,[treeData, setTreeData]); - if(searchTreeData){ - insertData(searchTreeData!, treeNodeData.uuid!, null,[searchTreeData, setSearchTreeData]); + insertData(treeData!, treeNodeData.uuid!, null, [treeData, setTreeData]); + if (searchTreeData) { + insertData(searchTreeData!, treeNodeData.uuid!, null, [searchTreeData, setSearchTreeData]); } } else { loadData(); @@ -329,7 +551,7 @@ const TreeNode = memo((props: TreeNodeIProps) => { useCommonStore.setState({ focusedContent: (treeNodeData.name || '') as any, }); - if(treeNodeData.treeNodeType === TreeNodeType.TABLE){ + if (treeNodeData.treeNodeType === TreeNodeType.TABLE) { setCurrentWorkspaceGlobalExtend({ code: 'viewDDL', uniqueData: { @@ -339,7 +561,7 @@ const TreeNode = memo((props: TreeNodeIProps) => { databaseType: treeNodeData.extraParams?.databaseType, schemaName: treeNodeData.extraParams?.schemaName, tableName: treeNodeData.name, - } + }, }); } setFocusId(treeNodeData.uuid || ''); @@ -360,7 +582,10 @@ const TreeNode = memo((props: TreeNodeIProps) => { treeNodeData.treeNodeType === TreeNodeType.VIEW || treeNodeData.treeNodeType === TreeNodeType.PROCEDURE || treeNodeData.treeNodeType === TreeNodeType.FUNCTION || - treeNodeData.treeNodeType === TreeNodeType.TRIGGER + treeNodeData.treeNodeType === TreeNodeType.TRIGGER || + treeNodeData.treeNodeType === TreeNodeType.REDIS_DATA || + treeNodeData.treeNodeType === TreeNodeType.REDIS_QUERY || + treeNodeData.treeNodeType === TreeNodeType.REDIS_MONITOR ) { rightClickMenu.find((item) => item.doubleClickTrigger)?.onClick(treeNodeData); } else { @@ -371,18 +596,25 @@ const TreeNode = memo((props: TreeNodeIProps) => { const rightClickMenu = useGetRightClickMenu({ treeNodeData, loadData, + refreshRootData, }); const treeNodeDom = useMemo(() => { - const dropdownsItems: any = rightClickMenu.map((item) => { - return { + const buildMenuItem = (item: any): any => { + const menuItem: any = { key: item.key, - onClick: () => { - item.onClick(treeNodeData); - }, label: , }; - }); + if (item.children && item.children.length > 0) { + menuItem.children = item.children.map(buildMenuItem); + } else { + menuItem.onClick = () => { + item.onClick(treeNodeData); + }; + } + return menuItem; + }; + const dropdownsItems: any = rightClickMenu.map(buildMenuItem); return ( { + getChildren: (_params, options) => { return new Promise((r: (value: ITreeNode[]) => void, j) => { const p = { pageNo: 1, pageSize: 1000, }; connectionService - .getList(p) + .getList(p, options) .then((res) => { const data: ITreeNode[] = res.data.map((t: IConnectionDetails) => { return { @@ -112,20 +138,20 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { }); r(data); }) - .catch(() => { - j(); + .catch((error) => { + j(error); }); }); }, }, [TreeNodeType.DATA_SOURCE]: { - getChildren: (params: { dataSourceId: number; dataSourceName: string; extraParams: any }) => { + getChildren: (params: { dataSourceId: number; dataSourceName: string; extraParams: any }, options) => { return new Promise((r, j) => { const _extraParams = params.extraParams; delete params.extraParams; connectionService - .getDatabaseList(params) + .getDatabaseList(params, options) .then((res) => { const data: ITreeNode[] = res.map((t: any) => { return { @@ -141,50 +167,110 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { }); r(data); }) - .catch(() => { - j(); + .catch((error) => { + j(error); }); }); }, - operationColumn: [OperationColumn.EditSource, OperationColumn.Refresh, OperationColumn.ShiftOut], + operationColumn: [ + OperationColumn.EditSource, + OperationColumn.SchemaDiff, + OperationColumn.Refresh, + OperationColumn.ShiftOut, + ], next: TreeNodeType.DATABASE, }, [TreeNodeType.DATABASE]: { icon: '\ue62c', - getChildren: (params) => { + getChildren: (params, options) => { const _extraParams = params.extraParams; delete params.extraParams; + if (_extraParams?.databaseType === DatabaseTypeCode.REDIS) { + const databaseName = params.databaseName || _extraParams?.databaseName; + const preCode = [_extraParams?.dataSourceId, databaseName].join('-'); + return Promise.resolve([ + { + uuid: uuid(), + key: `${preCode}-redis-data`, + name: '数据', + treeNodeType: TreeNodeType.REDIS_DATA, + isLeaf: true, + extraParams: { + ..._extraParams, + databaseName, + }, + }, + { + uuid: uuid(), + key: `${preCode}-redis-query`, + name: '查询', + treeNodeType: TreeNodeType.REDIS_QUERY, + isLeaf: true, + extraParams: { + ..._extraParams, + databaseName, + }, + }, + { + uuid: uuid(), + key: `${preCode}-redis-monitor`, + name: '监控', + treeNodeType: TreeNodeType.REDIS_MONITOR, + isLeaf: true, + extraParams: { + ..._extraParams, + databaseName, + }, + }, + { + uuid: uuid(), + key: `${preCode}-redis-backup`, + name: '备份', + treeNodeType: TreeNodeType.REDIS_BACKUP, + isLeaf: true, + extraParams: { + ..._extraParams, + databaseName, + }, + }, + ]); + } return new Promise((r: (value: ITreeNode[], b?: any) => void, j) => { connectionService - .getSchemaList(params) + .getSchemaList(params, options) .then((res) => { const data: ITreeNode[] = res.map((t: any) => { + const isRedis = _extraParams?.databaseType === DatabaseTypeCode.REDIS; + const schemaName = t.name || ''; return { uuid: uuid(), - key: t.name, - name: t.name, - treeNodeType: TreeNodeType.SCHEMAS, - schemaName: t.name, + key: isRedis && !schemaName ? 'keys' : schemaName, + name: isRedis && !schemaName ? 'keys' : schemaName, + treeNodeType: t.treeNodeType === 'tables' ? TreeNodeType.TABLES : TreeNodeType.SCHEMAS, + schemaName, extraParams: { ..._extraParams, - schemaName: t.name, + schemaName, }, }; }); r(data); }) - .catch(() => { - j(); + .catch((error) => { + j(error); }); }); }, operationColumn: [ OperationColumn.CreateConsole, + OperationColumn.ExecuteSqlStatement, + OperationColumn.ExportSchemaDoc, OperationColumn.CreateSchema, // OperationColumn.CreateTable, OperationColumn.CopyName, OperationColumn.Refresh, + OperationColumn.DeleteDatabase, ], next: TreeNodeType.SCHEMAS, }, @@ -203,6 +289,13 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { treeNodeType: TreeNodeType.TABLES, extraParams: parentData.extraParams, }, + { + uuid: uuid(), + key: `${preCode}-deprecated-tables`, + name: 'recycleBin', + treeNodeType: TreeNodeType.DEPRECATED_TABLES, + extraParams: parentData.extraParams, + }, { uuid: uuid(), key: `${preCode}-views`, @@ -254,6 +347,7 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { name: t.name, treeNodeType: TreeNodeType.TABLE, key: t.name, + isLeaf: _extraParams?.databaseType === DatabaseTypeCode.REDIS, pinned: t.pinned, comment: t.comment, extraParams: { @@ -278,15 +372,90 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { operationColumn: [ OperationColumn.CreateConsole, OperationColumn.ViewAllTable, + OperationColumn.ViewERDiagram, + OperationColumn.ViewTableRelation, OperationColumn.CreateTable, OperationColumn.Refresh, ], }, + [TreeNodeType.DEPRECATED_TABLES]: { + icon: '\ue73c', + getChildren: (params, options) => { + const _extraParams = params.extraParams; + delete params.extraParams; + params.pageSize = 1000; + return new Promise((r, j) => { + mysqlServer + .getDeprecatedTableList(params, options) + .then((res) => { + const deprecatedTables = Array.isArray(res) ? res : res?.data || []; + const tableList: ITreeNode[] = deprecatedTables.map((t: any) => { + return { + uuid: uuid(), + name: t.name, + treeNodeType: TreeNodeType.DEPRECATED_TABLE, + key: t.name, + comment: t.comment, + extraParams: { + ..._extraParams, + tableName: t.name, + }, + }; + }); + r({ + data: tableList, + pageNo: 1, + pageSize: tableList.length, + total: tableList.length, + hasNextPage: false, + } as any); + }) + .catch((error) => { + j(error); + }); + }); + }, + operationColumn: [OperationColumn.CreateConsole, OperationColumn.Refresh], + }, + + [TreeNodeType.DEPRECATED_TABLE]: { + icon: '\ue63e', + getChildren: (params) => { + return new Promise((r: (value: ITreeNode[]) => void) => { + const { dataSourceId, databaseName, schemaName, tableName } = params.extraParams!; + const preCode = [dataSourceId, databaseName, schemaName, tableName].join('-'); + const list = [ + { + uuid: uuid(), + key: `${preCode}-columns`, + name: 'columns', + treeNodeType: TreeNodeType.COLUMNS, + extraParams: params.extraParams, + }, + ]; + + r(list); + }); + }, + operationColumn: [ + OperationColumn.OpenTable, + OperationColumn.CreateConsole, + OperationColumn.ViewDDL, + OperationColumn.CopyName, + OperationColumn.Refresh, + OperationColumn.RestoreTable, + ], + }, + [TreeNodeType.TABLE]: { icon: '\ue63e', getChildren: (params) => { return new Promise((r: (value: ITreeNode[]) => void) => { + if (params.extraParams?.databaseType === DatabaseTypeCode.REDIS) { + r([]); + return; + } const { dataSourceId, databaseName, schemaName, tableName } = params.extraParams!; const preCode = [dataSourceId, databaseName, schemaName, tableName].join('-'); const list = [ @@ -319,23 +488,29 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { operationColumn: [ OperationColumn.OpenTable, OperationColumn.CreateConsole, + OperationColumn.EditTable, OperationColumn.Pin, OperationColumn.ViewDDL, - OperationColumn.EditTable, OperationColumn.CopyName, + OperationColumn.ImportData, + OperationColumn.ExportData, + OperationColumn.DataTransfer, + OperationColumn.GenerateData, OperationColumn.Refresh, OperationColumn.DeleteTable, + OperationColumn.TruncateTable, + OperationColumn.DeprecatedTable, ], }, [TreeNodeType.VIEWS]: { icon: '\ue70c', - getChildren: (params) => { + getChildren: (params, options) => { const _extraParams = params.extraParams; delete params.extraParams; return new Promise((r: (value: ITreeNode[]) => void, j) => { mysqlServer - .getViewList(params) + .getViewList(params, options) .then((res) => { const viewList: ITreeNode[] = res.data?.map((t: any) => { return { @@ -363,17 +538,17 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { [TreeNodeType.FUNCTIONS]: { icon: '\ue76a', - getChildren: (params) => { + getChildren: (params, options) => { const _extraParams = params.extraParams; delete params.extraParams; return new Promise((r: (value: ITreeNode[]) => void, j) => { mysqlServer - .getFunctionList(params) + .getFunctionList(params, options) .then((res) => { const list: ITreeNode[] = res.data?.map((t: any) => { return { uuid: uuid(), - name: t.functionName, + name: t.name, treeNodeType: TreeNodeType.FUNCTION, key: t.name, pinned: t.pinned, @@ -381,7 +556,7 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { isLeaf: true, extraParams: { ..._extraParams, - functionName: t.functionName, + functionName: t.name, }, }; }); @@ -402,17 +577,17 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { [TreeNodeType.PROCEDURES]: { icon: '\ue73c', - getChildren: (params) => { + getChildren: (params, options) => { const _extraParams = params.extraParams; delete params.extraParams; return new Promise((r: (value: ITreeNode[]) => void, j) => { mysqlServer - .getProcedureList(params) + .getProcedureList(params, options) .then((res) => { const list: ITreeNode[] = res.data?.map((t: any) => { return { uuid: uuid(), - name: t.procedureName, + name: t.name, treeNodeType: TreeNodeType.PROCEDURE, key: t.name, pinned: t.pinned, @@ -420,7 +595,7 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { isLeaf: true, extraParams: { ..._extraParams, - procedureName: t.procedureName, + procedureName: t.name, }, }; }); @@ -441,17 +616,17 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { [TreeNodeType.TRIGGERS]: { icon: '\ue64a', - getChildren: (params) => { + getChildren: (params, options) => { const _extraParams = params.extraParams; delete params.extraParams; return new Promise((r: (value: ITreeNode[]) => void, j) => { mysqlServer - .getTriggerList(params) + .getTriggerList(params, options) .then((res) => { const list: ITreeNode[] = res.data?.map((t: any) => { return { uuid: uuid(), - name: t.triggerName, + name: t.name, treeNodeType: TreeNodeType.TRIGGER, key: t.name, pinned: t.pinned, @@ -459,7 +634,7 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { isLeaf: true, extraParams: { ..._extraParams, - triggerName: t.triggerName, + triggerName: t.name, }, }; }); @@ -499,12 +674,12 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { [TreeNodeType.VIEWCOLUMNS]: { icon: '\ue647', - getChildren: (params) => { + getChildren: (params, options) => { const _extraParams = params.extraParams; delete params.extraParams; return new Promise((r: (value: ITreeNode[]) => void, j) => { mysqlServer - .getViewColumnList(params) + .getViewColumnList(params, options) .then((res) => { const list: ITreeNode[] = res.data?.map((t: any) => { return { @@ -535,12 +710,12 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { [TreeNodeType.COLUMNS]: { icon: '\ueac5', - getChildren: (params) => { + getChildren: (params, options) => { const _extraParams = params.extraParams; delete params.extraParams; return new Promise((r: (value: ITreeNode[]) => void, j) => { mysqlServer - .getColumnList(params) + .getColumnList(params, options) .then((res) => { const tableList: ITreeNode[] = res?.map((item) => { return { @@ -556,8 +731,8 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { }); r(tableList); }) - .catch(() => { - j(); + .catch((error) => { + j(error); }); }); }, @@ -569,18 +744,19 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { }, [TreeNodeType.KEYS]: { icon: '\ueac5', - getChildren: (params) => { + getChildren: (params, options) => { const _extraParams = params.extraParams; delete params.extraParams; return new Promise((r: (value: ITreeNode[]) => void, j) => { mysqlServer - .getKeyList(params) + .getKeyList(params, options) .then((res) => { const tableList: ITreeNode[] = res?.map((item) => { + const isVirtual = item.sourceType === 'VIRTUAL' || item.editable; return { uuid: uuid(), name: item.name, - treeNodeType: TreeNodeType.KEY, + treeNodeType: isVirtual ? TreeNodeType.V_KEY : TreeNodeType.KEY, key: item.name, isLeaf: true, extraParams: _extraParams, @@ -588,8 +764,8 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { }); r(tableList); }) - .catch(() => { - j(); + .catch((error) => { + j(error); }); }); }, @@ -597,16 +773,16 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { }, [TreeNodeType.KEY]: { icon: '\ue775', - operationColumn: [OperationColumn.CreateConsole, OperationColumn.CopyName], + operationColumn: [OperationColumn.ViewTableRelation, OperationColumn.CreateConsole, OperationColumn.CopyName], }, [TreeNodeType.INDEXES]: { icon: '\ueac5', - getChildren: (params) => { + getChildren: (params, options) => { const _extraParams = params.extraParams; delete params.extraParams; return new Promise((r: (value: ITreeNode[]) => void, j) => { mysqlServer - .getIndexList(params) + .getIndexList(params, options) .then((res) => { const tableList: ITreeNode[] = res?.map((item) => { return { @@ -620,8 +796,8 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { }); r(tableList); }) - .catch(() => { - j(); + .catch((error) => { + j(error); }); }); }, @@ -631,4 +807,29 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { icon: '\ue65b', operationColumn: [OperationColumn.CreateConsole, OperationColumn.CopyName], }, + [TreeNodeType.V_KEY]: { + icon: '\ue775', + operationColumn: [ + OperationColumn.ViewTableRelation, + OperationColumn.CreateConsole, + OperationColumn.CopyName, + OperationColumn.DeleteVirtualKey, + ], + }, + [TreeNodeType.REDIS_DATA]: { + icon: '\ue618', + operationColumn: [OperationColumn.OpenRedisData, OperationColumn.Refresh, OperationColumn.CopyName], + }, + [TreeNodeType.REDIS_QUERY]: { + icon: '\ue619', + operationColumn: [OperationColumn.CreateConsole, OperationColumn.CopyName], + }, + [TreeNodeType.REDIS_MONITOR]: { + icon: '\ue611', + operationColumn: [OperationColumn.OpenRedisMonitor, OperationColumn.CopyName], + }, + [TreeNodeType.REDIS_BACKUP]: { + icon: '\ue73c', + operationColumn: [OperationColumn.CopyName], + }, }; diff --git a/chat2db-client/src/components/AiChat/ConversationSidebar.tsx b/chat2db-client/src/components/AiChat/ConversationSidebar.tsx new file mode 100644 index 000000000..6044e9c44 --- /dev/null +++ b/chat2db-client/src/components/AiChat/ConversationSidebar.tsx @@ -0,0 +1,240 @@ +import React, { memo, useEffect, useState, useRef } from 'react'; +import { Button, Input, Modal, Dropdown, message, Spin } from 'antd'; +import { + PlusOutlined, + DeleteOutlined, + EditOutlined, + MenuFoldOutlined, + MenuUnfoldOutlined, + MessageOutlined, + MoreOutlined, +} from '@ant-design/icons'; +import i18n from '@/i18n'; +import { useAiChatStore, AiChatSession } from '@/pages/main/workspace/store/aiChatStore'; +import styles from './index.less'; + +interface IConversationItemProps { + session: AiChatSession; + active: boolean; + onClick: () => void; + onDelete: () => void; + onRename: (title: string) => void; +} + +const ConversationItem = memo(({ session, active, onClick, onDelete, onRename }) => { + const [editing, setEditing] = useState(false); + const [draft, setDraft] = useState(session.title || ''); + const inputRef = useRef(null); + + useEffect(() => { + if (editing) { + inputRef.current?.focus(); + inputRef.current?.select?.(); + } + }, [editing]); + + const handleSubmit = () => { + const next = draft.trim(); + if (next && next !== session.title) { + onRename(next); + } + setEditing(false); + }; + + const displayTitle = session.title || (session.messages[0]?.content || i18n('chat.header.titleFallback')).slice(0, 20); + + return ( +
+ + {editing ? ( + setDraft(e.target.value)} + onPressEnter={handleSubmit} + onBlur={handleSubmit} + onClick={(e) => e.stopPropagation()} + className={styles.conversationItemInput} + /> + ) : ( + + {displayTitle} + + )} + , + onClick: ({ domEvent }) => { + domEvent.stopPropagation(); + setEditing(true); + }, + }, + { + key: 'delete', + label: i18n('chat.sidebar.delete'), + icon: , + danger: true, + onClick: ({ domEvent }) => { + domEvent.stopPropagation(); + onDelete(); + }, + }, + ], + }} + trigger={['click']} + placement="bottomRight" + > + e.stopPropagation()} + /> + +
+ ); +}); +ConversationItem.displayName = 'ConversationItem'; + +export default memo((props: { + boundInfo?: { dataSourceId?: number | null; databaseName?: string | null; schemaName?: string | null }; + collapsed?: boolean; + onCollapsedChange?: (collapsed: boolean) => void; + onNewConversation?: () => void; +}) => { + const { + conversationList, + conversationListLoading, + conversationListHasMore, + sessions, + currentSessionId, + startNewConversation, + switchToSession, + deleteSession, + renameSession, + loadConversationList, + } = useAiChatStore(); + + useEffect(() => { + loadConversationList(true); + }, [loadConversationList]); + + const handleNew = () => { + startNewConversation(props.boundInfo); + props.onNewConversation?.(); + }; + + const handleDelete = (conversationId: string) => { + Modal.confirm({ + title: i18n('chat.sidebar.delete.confirm'), + okText: i18n('chat.sidebar.delete'), + okButtonProps: { danger: true }, + cancelText: '取消', + onOk: () => deleteSession(conversationId).catch(() => message.error('删除失败')), + }); + }; + + const handleRename = (conversationId: string, title: string) => { + renameSession(conversationId, title).catch(() => message.error('重命名失败')); + }; + + if (props.collapsed) { + return ( +
+
+ ); + } + + return ( +
+
+ {i18n('chat.sidebar.title')} +
+
+
+
+ {conversationList.length === 0 && !conversationListLoading ? ( +
{i18n('chat.sidebar.empty')}
+ ) : ( + conversationList.map((item) => { + const localSession = sessions[item.conversationId]; + const sessionForRender: AiChatSession = + localSession || + ({ + sessionId: item.conversationId, + state: 'COMPLETED', + messages: item.lastMessagePreview + ? [{ id: 'preview', role: 'assistant' as const, content: item.lastMessagePreview }] + : [], + currentContent: '', + currentThinking: '', + title: item.title, + dataSourceId: item.dataSourceId, + databaseName: item.databaseName, + schemaName: item.schemaName, + createdAt: 0, + updatedAt: 0, + } as AiChatSession); + return ( + switchToSession(item.conversationId)} + onDelete={() => handleDelete(item.conversationId)} + onRename={(title) => handleRename(item.conversationId, title)} + /> + ); + }) + )} + {conversationListLoading && ( +
+ +
+ )} + {conversationListHasMore && !conversationListLoading && ( +
loadConversationList(false)} + > + 加载更多 +
+ )} +
+
+ ); +}); diff --git a/chat2db-client/src/components/AiChat/index.less b/chat2db-client/src/components/AiChat/index.less new file mode 100644 index 000000000..f457f629b --- /dev/null +++ b/chat2db-client/src/components/AiChat/index.less @@ -0,0 +1,440 @@ +@import '../../styles/var.less'; + +.aiChatContainer { + height: 100%; + display: flex; + flex-direction: row; + padding: 0; + overflow: hidden; + + .sidebarLayout { + width: 240px; + flex-shrink: 0; + display: flex; + flex-direction: column; + border-right: 1px solid var(--color-border-secondary); + background-color: var(--color-bg-subtle); + transition: width 0.16s ease; + } + + .sidebarLayoutCollapsed { + width: 44px; + } + + .mainPanel { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + padding: 16px; + overflow: hidden; + } + + .conversationSidebar { + display: flex; + flex-direction: column; + height: 100%; + overflow: hidden; + + &.conversationSidebarCollapsed { + align-items: center; + gap: 8px; + padding: 8px 6px; + } + + .sidebarHeader { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + padding: 12px 12px 8px; + border-bottom: 1px solid var(--color-border-secondary); + flex-shrink: 0; + + .sidebarTitle { + flex: 1; + min-width: 0; + font-size: 14px; + font-weight: 600; + color: var(--color-text); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .sidebarHeaderActions { + display: flex; + align-items: center; + flex-shrink: 0; + } + } + + .sidebarList { + flex: 1; + overflow-y: auto; + padding: 4px 0; + } + + .sidebarEmpty { + padding: 24px 12px; + text-align: center; + color: var(--color-text-tertiary); + font-size: 12px; + } + + .sidebarLoading { + padding: 12px; + display: flex; + justify-content: center; + } + + .sidebarLoadMore { + padding: 8px 12px; + text-align: center; + color: var(--color-primary); + cursor: pointer; + font-size: 12px; + + &:hover { + background-color: var(--color-fill-quaternary); + } + } + } + + .conversationItem { + display: flex; + align-items: center; + gap: 6px; + padding: 8px 12px; + cursor: pointer; + color: var(--color-text-secondary); + font-size: 13px; + transition: background-color 0.15s ease; + + &:hover { + background-color: var(--color-fill-quaternary); + } + + &.conversationItemActive { + background-color: var(--color-info-bg); + color: var(--color-primary); + } + + .conversationItemIcon { + flex-shrink: 0; + font-size: 14px; + } + + .conversationItemTitle { + flex: 1; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .conversationItemInput { + flex: 1; + min-width: 0; + } + + .conversationItemMore { + flex-shrink: 0; + opacity: 0; + transition: opacity 0.15s ease; + padding: 4px; + font-size: 14px; + + &:hover { + opacity: 1 !important; + } + } + + &:hover .conversationItemMore { + opacity: 0.6; + } + } + + .chatHeader { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 0 12px; + border-bottom: 1px solid var(--color-border-secondary); + margin-bottom: 12px; + flex-shrink: 0; + + .chatHeaderTitle { + font-size: 14px; + font-weight: 600; + color: var(--color-text); + flex: 1; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .chatHeaderActions { + display: flex; + gap: 8px; + } + } + + .statusBar { + display: flex; + align-items: center; + gap: 12px; + padding: 8px 12px; + background-color: var(--color-bg-subtle); + border-radius: 6px; + margin-bottom: 12px; + flex-shrink: 0; + + .selectedTables { + display: flex; + align-items: center; + gap: 4px; + flex-wrap: wrap; + + > span:first-child { + color: var(--color-text-tertiary); + font-size: 12px; + } + } + } + + .contentArea { + flex: 1; + overflow-y: auto; + margin-bottom: 12px; + display: flex; + flex-direction: column; + gap: 12px; + + .userBlock { + padding: 12px 16px; + background-color: var(--color-info-bg); + border-radius: 6px; + border: 1px solid var(--color-info-border); + align-self: flex-end; + max-width: 80%; + } + + .aiBlock { + padding: 16px; + background-color: var(--color-bg-subtle); + border-radius: 6px; + border: 1px solid var(--color-border-secondary); + align-self: flex-start; + max-width: 80%; + + .thinkingBlock { + margin-bottom: 16px; + padding: 12px; + background-color: var(--color-warning-bg); + border: 1px solid var(--color-warning-border); + border-radius: 4px; + font-size: 13px; + color: var(--color-text-secondary); + + .thinkingHeader { + font-weight: 600; + color: var(--color-warning); + font-size: 12px; + cursor: pointer; + display: flex; + align-items: center; + gap: 8px; + user-select: none; + + :global { + .anticon { + font-size: 10px; + } + } + } + + .thinkingContent { + margin-top: 8px; + } + + :global { + p:last-child { + margin-bottom: 0; + } + } + } + + :global { + h1, h2, h3, h4, h5, h6 { + margin-top: 24px; + margin-bottom: 16px; + font-weight: 600; + line-height: 1.25; + } + + p { + margin-bottom: 16px; + line-height: 1.5; + } + + code { + padding: 0.2em 0.4em; + margin: 0; + font-size: 85%; + background-color: var(--color-fill-tertiary); + border-radius: 3px; + } + + pre { + padding: 16px; + overflow: auto; + font-size: 85%; + line-height: 1.45; + background-color: var(--color-fill-secondary); + border-radius: 3px; + + code { + padding: 0; + margin: 0; + background-color: transparent; + border: 0; + background: none; + } + } + + ul, ol { + padding-left: 2em; + margin-bottom: 16px; + } + + blockquote { + padding: 0 1em; + color: var(--color-text-tertiary); + border-left: 0.25em solid var(--color-border-secondary); + margin: 0 0 16px 0; + } + + table { + border-spacing: 0; + border-collapse: collapse; + margin-bottom: 16px; + + th, td { + padding: 6px 13px; + border: 1px solid var(--color-border-secondary); + } + + tr:nth-child(2n) { + background-color: var(--color-fill-quaternary); + } + } + } + } + + .sqlActionButtons { + margin-top: 12px; + display: flex; + gap: 8px; + padding-top: 12px; + border-top: 1px solid var(--color-border-secondary); + } + + .errorBlock { + padding: 16px; + background-color: var(--color-error-bg); + border-radius: 6px; + border: 1px solid var(--color-error-border); + align-self: flex-start; + max-width: 80%; + } + + .explainPanel { + padding: 12px 16px; + background-color: var(--color-info-bg); + border-radius: 6px; + border: 1px solid var(--color-info-border); + align-self: flex-start; + max-width: 90%; + + .explainHeader { + display: flex; + align-items: center; + gap: 8px; + cursor: pointer; + font-weight: 500; + color: var(--color-primary); + padding: 4px 0; + user-select: none; + + &:hover { + color: var(--color-primary-hover); + } + } + + .explainContent { + margin-top: 12px; + + .explainSql { + margin-bottom: 12px; + padding: 8px; + background-color: var(--color-bg-subtle); + border-radius: 4px; + font-size: 12px; + overflow-x: auto; + + code { + font-family: 'Consolas', 'Monaco', monospace; + color: var(--color-text); + } + } + + .explainTable { + overflow-x: auto; + + .explainTableInner { + width: 100%; + border-collapse: collapse; + font-size: 12px; + + th, td { + padding: 6px 10px; + border: 1px solid var(--color-border-secondary); + text-align: left; + white-space: nowrap; + } + + th { + background-color: var(--color-info-bg); + font-weight: 600; + color: var(--color-primary); + } + + tr:nth-child(2n) { + background-color: var(--color-fill-quaternary); + } + } + } + } + } + } + + .inputFormArea { + border: 1px solid var(--color-border-secondary); + border-radius: 6px; + padding: 12px; + background-color: var(--color-bg-container); + flex-shrink: 0; + margin-bottom: 20px; + + .ant-input-textarea { + margin-bottom: 8px; + } + + .buttonGroup { + display: flex; + gap: 8px; + justify-content: flex-end; + flex-shrink: 0; + } + } +} diff --git a/chat2db-client/src/components/AiChat/index.tsx b/chat2db-client/src/components/AiChat/index.tsx new file mode 100644 index 000000000..389e12ce2 --- /dev/null +++ b/chat2db-client/src/components/AiChat/index.tsx @@ -0,0 +1,915 @@ +import React, { memo, useState, useRef, useEffect, useCallback } from 'react'; +import { Button, Input, Spin, message, Tag, Alert, Modal, Select } from 'antd'; +import { DownOutlined, RightOutlined, PlayCircleOutlined, SendOutlined, PlusOutlined } from '@ant-design/icons'; +import ReactMarkdown from 'react-markdown'; +import remarkGfm from 'remark-gfm'; +import { v4 as uuidv4 } from 'uuid'; +import { formatParams } from '@/utils/url'; +import connectToEventSource, { cancelChatSession, createChatPayload } from '@/utils/eventSource'; +import CascaderDB from '@/components/CascaderDB'; +import aiConversationService from '@/service/aiConversation'; +import sqlService from '@/service/sql'; +import { IAiChatPromptType, ITableCommentResult, IBatchTableCommentResult, IFieldMappingResult, IDataExpressionResult } from '@/pages/main/workspace/store/common'; +import { useWorkspaceStore } from '@/pages/main/workspace/store'; +import { useAiChatStore, ChatStateType, IChatMessage, generateTitle } from '@/pages/main/workspace/store/aiChatStore'; +import ConversationSidebar from './ConversationSidebar'; +import styles from './index.less'; + +const STATE_LABELS: Record = { + IDLE: { text: '等待中', color: 'default' }, + AUTO_SELECTING_TABLES: { text: '选择表...', color: 'processing' }, + FETCHING_TABLE_SCHEMA: { text: '获取表结构...', color: 'processing' }, + EXECUTING_EXPLAIN: { text: '分析执行计划...', color: 'processing' }, + BUILDING_PROMPT: { text: '构建提示...', color: 'processing' }, + STREAMING: { text: 'AI生成中', color: 'processing' }, + COMPLETED: { text: '完成', color: 'success' }, + FAILED: { text: '失败', color: 'error' }, +}; + +const isActiveState = (state?: ChatStateType): boolean => { + return state + ? ['AUTO_SELECTING_TABLES', 'FETCHING_TABLE_SCHEMA', 'EXECUTING_EXPLAIN', 'BUILDING_PROMPT', 'STREAMING'].includes(state) + : false; +}; + +const ThinkingBlock = memo<{ thinking: string; collapsed?: boolean }>(({ thinking, collapsed = true }) => { + const [isCollapsed, setIsCollapsed] = useState(collapsed); + + return ( +
+
setIsCollapsed(!isCollapsed)}> + {isCollapsed ? : } + 思考过程 +
+ {!isCollapsed && ( +
+ {thinking} +
+ )} +
+ ); +}); + +interface ExplainPanelProps { + explainResult: { sql: string; plan: string[][]; formatted: string; success: boolean }; +} + +const ExplainPanel = memo(({ explainResult }) => { + const [collapsed, setCollapsed] = useState(true); + + if (!explainResult || !explainResult.success || !explainResult.plan.length) { + return null; + } + + const headers = explainResult.plan[0]; + const rows = explainResult.plan.slice(1); + + return ( +
+
setCollapsed(!collapsed)}> + {collapsed ? : } + SQL 执行计划 (EXPLAIN) +
+ {!collapsed && ( +
+
+ {explainResult.sql} +
+
+
+ + + {headers.map((h, i) => ( + + ))} + + + + {rows.map((row, ri) => ( + + {row.map((cell, ci) => ( + + ))} + + ))} + +
{h}
{cell}
+ + + )} + + ); +}); + +function extractJsonFromContent(content: string): ITableCommentResult | null { + try { + const jsonMatch = content.match(/\{[\s\S]*"table_comment"[\s\S]*"column_comments"[\s\S]*\}/); + if (jsonMatch) { + return JSON.parse(jsonMatch[0]) as ITableCommentResult; + } + const directJson = JSON.parse(content); + if (directJson.table_comment) { + return directJson as ITableCommentResult; + } + } catch (e) { + console.error('[extractJsonFromContent] Parse error:', e); + } + return null; +} + +function extractBatchJsonFromContent(content: string): IBatchTableCommentResult | null { + try { + const jsonMatch = content.match(/\{[\s\S]*"tables"[\s\S]*\}/); + if (jsonMatch) { + return JSON.parse(jsonMatch[0]) as IBatchTableCommentResult; + } + const directJson = JSON.parse(content); + if (directJson.tables) { + return directJson as IBatchTableCommentResult; + } + } catch (e) { + console.error('[extractBatchJsonFromContent] Parse error:', e); + } + return null; +} + +function extractFieldMappingFromContent(content: string): IFieldMappingResult | null { + try { + const jsonMatch = content.match(/\{[\s\S]*"mappings"[\s\S]*\}/); + if (jsonMatch) { + return JSON.parse(jsonMatch[0]) as IFieldMappingResult; + } + const directJson = JSON.parse(content); + if (directJson.mappings) { + return directJson as IFieldMappingResult; + } + } catch (e) { + console.error('[extractFieldMappingFromContent] Parse error:', e); + } + return null; +} + +function extractDataExpressionFromContent(content: string): IDataExpressionResult | null { + try { + const jsonMatch = content.match(/\{[\s\S]*"column_expressions"[\s\S]*\}/); + if (jsonMatch) { + return JSON.parse(jsonMatch[0]) as IDataExpressionResult; + } + const directJson = JSON.parse(content); + if (directJson.column_expressions) { + return directJson as IDataExpressionResult; + } + } catch (e) { + console.error('[extractDataExpressionFromContent] Parse error:', e); + } + return null; +} + +function extractSqlFromContent(content: string): string | null { + try { + const sqlMatch = content.match(/```sql\s*([\s\S]*?)```/i); + if (sqlMatch && sqlMatch[1]) { + return sqlMatch[1].trim(); + } + const genericCodeMatch = content.match(/```\s*([\s\S]*?)```/); + if (genericCodeMatch && genericCodeMatch[1]) { + const code = genericCodeMatch[1].trim(); + if (code.match(/select|insert|update|delete|create|alter|drop/i)) { + return code; + } + } + } catch (e) { + console.error('[extractSqlFromContent] Parse error:', e); + } + return null; +} + +const MAX_DIRECT_SSE_URL_LENGTH = 1800; + +const getLastAssistantSql = (messages: IChatMessage[]): string | null => { + for (let i = messages.length - 1; i >= 0; i--) { + const messageItem = messages[i]; + if (messageItem.role === 'assistant') { + const sql = extractSqlFromContent(messageItem.content); + if (sql) { + return sql; + } + } + } + return null; +}; + +const normalizeNullableText = (value?: string | null): string => value || ''; + +const SqlActionButtons = memo<{ sql: string; onExecute?: (sql: string) => void; onSendToEditor?: (sql: string) => void }>(({ sql, onExecute, onSendToEditor }) => { + return ( +
+ + +
+ ); +}); + +interface IProps { + className?: string; + data?: any; +} + +export default memo((props) => { + const [inputValue, setInputValue] = useState(''); + const closeEventSource = useRef<() => void>(); + const sessionIdRef = useRef(); + const commentCallbackRef = useRef<(result: ITableCommentResult) => void>(); + const batchCommentCallbackRef = useRef<(result: IBatchTableCommentResult) => void>(); + const mappingCallbackRef = useRef<(result: IFieldMappingResult) => void>(); + const expressionCallbackRef = useRef<(result: IDataExpressionResult) => void>(); + const sqlFixCallbackRef = useRef<(sql: string) => void>(); + const extractedSqlRef = useRef(null); + const [tableSelectorOpen, setTableSelectorOpen] = useState(false); + const [tableSelectorLoading, setTableSelectorLoading] = useState(false); + const [databaseTables, setDatabaseTables] = useState>([]); + const [selectedTableDraft, setSelectedTableDraft] = useState([]); + const [sidebarCollapsed, setSidebarCollapsed] = useState(false); + + const { + currentSessionId, + sessions, + createSession, + updateState, + appendContent, + addMessage, + setSelectedTables, + setSchemaInfo, + setError, + setLastRequest, + clearSession, + resetCurrentContent, + lastRequest, + } = useAiChatStore(); + + const currentSession = currentSessionId ? sessions[currentSessionId] : null; + + const { consoleList, activeConsoleId, currentConnectionDetails, pendingAiChat } = useWorkspaceStore((state) => ({ + consoleList: state.consoleList, + activeConsoleId: state.activeConsoleId, + currentConnectionDetails: state.currentConnectionDetails, + pendingAiChat: state.pendingAiChat, + })); + + const activeConsole = consoleList?.find((c) => c.id === activeConsoleId); + + const [boundInfo, setBoundInfo] = useState(() => ({ + dataSourceId: activeConsole?.dataSourceId || currentConnectionDetails?.id, + databaseName: activeConsole?.databaseName || '', + schemaName: activeConsole?.schemaName || '', + tableNames: pendingAiChat?.tableNames || null, + })); + + useEffect(() => { + const activeConsoleInfo = consoleList?.find((c) => c.id === activeConsoleId); + if (activeConsoleInfo) { + setBoundInfo((prev) => ({ + dataSourceId: activeConsoleInfo.dataSourceId || prev.dataSourceId, + databaseName: activeConsoleInfo.databaseName || prev.databaseName, + schemaName: activeConsoleInfo.schemaName || prev.schemaName, + tableNames: prev.tableNames, + })); + } + }, [activeConsoleId, consoleList]); + + useEffect(() => { + setDatabaseTables([]); + }, [boundInfo.dataSourceId, boundInfo.databaseName, boundInfo.schemaName]); + + const sendAiChat = (messageText: string, promptType: IAiChatPromptType = 'NL_2_SQL', tableNames?: string[] | null, ext?: string) => { + const infoWithTables = { + ...boundInfo, + tableNames: tableNames !== undefined ? tableNames : boundInfo.tableNames, + }; + sendAiChatInternal(messageText, promptType, infoWithTables, ext); + }; + + const syncSelectedTables = useCallback( + (tables: string[]) => { + const nextTables = Array.from(new Set(tables.filter(Boolean))); + setBoundInfo((prev) => ({ + ...prev, + tableNames: nextTables.length ? nextTables : null, + })); + if (currentSessionId) { + setSelectedTables(currentSessionId, nextTables); + } + }, + [currentSessionId, setSelectedTables], + ); + + const openTableSelector = useCallback(async () => { + if (!boundInfo.dataSourceId) { + message.warning('请先选择数据库连接'); + return; + } + + const currentTables = currentSession?.selectedTables || boundInfo.tableNames || []; + setSelectedTableDraft(currentTables); + setTableSelectorOpen(true); + + if (databaseTables.length > 0) { + return; + } + + setTableSelectorLoading(true); + try { + const tables = await sqlService.getAllTableList({ + dataSourceId: boundInfo.dataSourceId, + databaseName: boundInfo.databaseName, + schemaName: boundInfo.schemaName, + }); + setDatabaseTables(tables || []); + } catch (error) { + console.error('[AiChat] Failed to load tables:', error); + message.error('加载表列表失败'); + } finally { + setTableSelectorLoading(false); + } + }, [boundInfo, currentSession?.selectedTables, databaseTables.length]); + + const handleRemoveSelectedTable = useCallback( + (tableName: string) => { + const currentTables = currentSession?.selectedTables || boundInfo.tableNames || []; + syncSelectedTables(currentTables.filter((item) => item !== tableName)); + }, + [boundInfo.tableNames, currentSession?.selectedTables, syncSelectedTables], + ); + + const handleConfirmTableSelector = useCallback(() => { + syncSelectedTables(selectedTableDraft); + setTableSelectorOpen(false); + setInputValue((value) => (value.endsWith('@') ? value.slice(0, -1) : value)); + }, [selectedTableDraft, syncSelectedTables]); + + const sendAiChatInternal = useCallback( + async (messageText: string, promptType: IAiChatPromptType = 'NL_2_SQL', info: typeof boundInfo, ext?: string) => { + console.log('[AiChat] sendAiChat called with:', { messageText, promptType, info }); + if (!messageText.trim()) { + message.warning('请输入问题'); + return; + } + + if (!info.dataSourceId) { + message.warning('请先选择数据库连接'); + return; + } + + const storeState = useAiChatStore.getState(); + const existingSessionId = storeState.currentSessionId; + const existingSession = existingSessionId ? storeState.sessions[existingSessionId] : null; + const previousRequest = storeState.lastRequest; + const isSameConnection = + existingSession + ? existingSession.dataSourceId === info.dataSourceId && + normalizeNullableText(existingSession.databaseName) === normalizeNullableText(info.databaseName) && + normalizeNullableText(existingSession.schemaName) === normalizeNullableText(info.schemaName) + : !previousRequest || + (previousRequest.dataSourceId === info.dataSourceId && + normalizeNullableText(previousRequest.databaseName) === normalizeNullableText(info.databaseName) && + normalizeNullableText(previousRequest.schemaName) === normalizeNullableText(info.schemaName)); + const canReuseNl2SqlConversation = + promptType === 'NL_2_SQL' && + isSameConnection && + !!existingSessionId && + !!existingSession && + existingSession.messages.length > 0 && + !isActiveState(existingSession.state); + const canUseEmptyConversation = + promptType === 'NL_2_SQL' && + isSameConnection && + !!existingSessionId && + !!existingSession && + existingSession.messages.length === 0 && + !isActiveState(existingSession.state); + const previousSql = canReuseNl2SqlConversation ? getLastAssistantSql(existingSession.messages) : null; + const shouldReuseConversation = canReuseNl2SqlConversation || canUseEmptyConversation; + const isRevision = canReuseNl2SqlConversation && !!previousSql; + const requestTableNames = + shouldReuseConversation && + (!info.tableNames || info.tableNames.length === 0) && + existingSession?.selectedTables?.length + ? existingSession.selectedTables + : info.tableNames; + + const sessionId = shouldReuseConversation ? existingSessionId! : uuidv4(); + console.log('[AiChat] Using sessionId:', sessionId, 'reuse:', shouldReuseConversation, 'revision:', isRevision); + sessionIdRef.current = sessionId; + const sessionTitle = existingSession?.title || generateTitle(messageText); + if (!shouldReuseConversation) { + createSession(sessionId, { + dataSourceId: info.dataSourceId, + databaseName: info.databaseName, + schemaName: info.schemaName, + selectedTables: requestTableNames || undefined, + title: sessionTitle, + }); + try { + await aiConversationService.createAiConversation({ + conversationId: sessionId, + dataSourceId: info.dataSourceId, + databaseName: info.databaseName, + schemaName: info.schemaName, + title: sessionTitle, + }); + } catch (error) { + console.warn('[AiChat] Create conversation before chat failed, backend will retry:', error); + } + } + + const userMessage: IChatMessage = { + id: uuidv4(), + role: 'user', + content: messageText, + }; + addMessage(sessionId, userMessage); + + setLastRequest({ + message: messageText, + promptType, + dataSourceId: info.dataSourceId, + databaseName: info.databaseName, + schemaName: info.schemaName, + tableNames: requestTableNames, + ext, + conversationId: sessionId, + isRevision, + }); + + resetCurrentContent(sessionId); + + const requestParams = { + message: messageText, + promptType, + dataSourceId: info.dataSourceId, + databaseName: info.databaseName, + schemaName: info.schemaName, + tableNames: requestTableNames, + ext, + conversationId: sessionId, + isRevision, + }; + + setInputValue(''); + + let params = formatParams(requestParams); + if (`/api/ai/chat?${params}`.length > MAX_DIRECT_SSE_URL_LENGTH) { + try { + const payloadId = await createChatPayload(requestParams); + params = formatParams({ + payloadId, + dataSourceId: info.dataSourceId, + databaseName: info.databaseName, + schemaName: info.schemaName, + }); + } catch (error: any) { + console.error('[AiChat] Failed to create chat payload:', error); + setError(sessionId, error?.message || '创建 AI 请求失败'); + message.error('创建 AI 请求失败'); + return; + } + } + + closeEventSource.current = connectToEventSource({ + url: `/api/ai/chat?${params}`, + uid: sessionId, + onOpen: () => { + console.log('[AiChat] SSE connection opened'); + updateState(sessionId, 'IDLE'); + }, + onStateChange: (state, _msg) => { + console.log('[AiChat] State changed:', state, _msg); + updateState(sessionId, state); + }, + onMessage: (content, thinking) => { + console.log('[AiChat] Message received:', { content, thinking }); + appendContent(sessionId, content, thinking); + }, + onTablesSelected: (tables) => { + console.log('[AiChat] Tables selected:', tables); + setSelectedTables(sessionId, tables); + setBoundInfo((prev) => ({ + ...prev, + tableNames: tables.length ? tables : null, + })); + }, + onSchemaFetched: (ddl) => { + console.log('[AiChat] Schema fetched, ddl length:', ddl?.length); + setSchemaInfo(sessionId, ddl); + }, + onExplain: (data) => { + console.log('[AiChat] Explain result:', data); + useAiChatStore.getState().setExplainResult(sessionId, data); + }, + onDone: () => { + console.log('[AiChat] onDone callback, sessionId:', sessionId); + updateState(sessionId, 'COMPLETED'); + const currentSessions = useAiChatStore.getState().sessions; + console.log('[AiChat] sessions in onDone:', currentSessions); + const session = currentSessions[sessionId]; + console.log('[AiChat] session in onDone:', session); + if (session?.currentContent) { + addMessage(sessionId, { + id: uuidv4(), + role: 'assistant', + content: session.currentContent, + thinking: session.currentThinking || undefined, + }); + + if (promptType === 'NL_2_COMMENT' && commentCallbackRef.current) { + try { + const jsonContent = extractJsonFromContent(session.currentContent); + if (jsonContent) { + console.log('[AiChat] Parsed comment result:', jsonContent); + commentCallbackRef.current(jsonContent); + message.success('AI 注释已生成,请查看并确认'); + } + } catch (e) { + console.error('[AiChat] Failed to parse comment JSON:', e); + message.warning('无法解析 AI 生成的注释,请手动查看'); + } + commentCallbackRef.current = undefined; + } + + if (promptType === 'NL_2_COMMENT_BATCH' && batchCommentCallbackRef.current) { + try { + const jsonContent = extractBatchJsonFromContent(session.currentContent); + if (jsonContent) { + console.log('[AiChat] Parsed batch comment result:', jsonContent); + batchCommentCallbackRef.current(jsonContent); + message.success('AI 批量注释已生成'); + } + } catch (e) { + console.error('[AiChat] Failed to parse batch comment JSON:', e); + message.warning('无法解析 AI 生成的批量注释,请手动查看'); + } + batchCommentCallbackRef.current = undefined; + } + + if (promptType === 'NL_2_FIELD_MAPPING' && mappingCallbackRef.current) { + try { + const jsonContent = extractFieldMappingFromContent(session.currentContent); + if (jsonContent) { + console.log('[AiChat] Parsed field mapping result:', jsonContent); + mappingCallbackRef.current(jsonContent); + message.success('AI 字段映射推荐已生成,请查看并确认'); + } + } catch (e) { + console.error('[AiChat] Failed to parse field mapping JSON:', e); + message.warning('无法解析 AI 生成的映射推荐,请手动查看'); + } + mappingCallbackRef.current = undefined; + } + + if (promptType === 'NL_2_DATA_EXPRESSION' && expressionCallbackRef.current) { + try { + const jsonContent = extractDataExpressionFromContent(session.currentContent); + if (jsonContent) { + console.log('[AiChat] Parsed data expression result:', jsonContent); + expressionCallbackRef.current(jsonContent); + message.success('AI 表达式推荐已生成,请查看并确认'); + } + } catch (e) { + console.error('[AiChat] Failed to parse data expression JSON:', e); + message.warning('无法解析 AI 生成的表达式,请手动查看'); + } + expressionCallbackRef.current = undefined; + } + + if (promptType === 'SQL_FIX' && sqlFixCallbackRef.current) { + try { + const sql = extractSqlFromContent(session.currentContent); + if (sql) { + console.log('[AiChat] Extracted SQL:', sql); + extractedSqlRef.current = sql; + sqlFixCallbackRef.current(sql); + message.success('AI 已修复SQL,请查看并确认'); + } else { + message.warning('AI 未能生成修复后的SQL'); + } + } catch (e) { + console.error('[AiChat] Failed to extract SQL:', e); + message.warning('无法提取 AI 生成的SQL'); + } + sqlFixCallbackRef.current = undefined; + } + } + closeEventSource.current = undefined; + }, + onError: (error) => { + console.error('[AiChat] SSE error:', error); + setError(sessionId, error); + message.error(error); + closeEventSource.current = undefined; + }, + }); + }, + [ + createSession, + addMessage, + setLastRequest, + resetCurrentContent, + updateState, + appendContent, + setSelectedTables, + setSchemaInfo, + setError, + ], + ); + + useEffect(() => { + if (pendingAiChat && pendingAiChat.message) { + const overrideBoundInfo = { + dataSourceId: pendingAiChat.dataSourceId || boundInfo.dataSourceId, + databaseName: pendingAiChat.databaseName || boundInfo.databaseName, + schemaName: pendingAiChat.schemaName || boundInfo.schemaName, + tableNames: pendingAiChat.tableNames || boundInfo.tableNames || null, + }; + if (pendingAiChat.dataSourceId && pendingAiChat.dataSourceId !== boundInfo.dataSourceId) { + setBoundInfo((prev) => ({ + ...prev, + dataSourceId: pendingAiChat.dataSourceId, + tableNames: pendingAiChat.tableNames || prev.tableNames, + })); + } + if (pendingAiChat.onCommentGenerated) { + commentCallbackRef.current = pendingAiChat.onCommentGenerated; + } + if (pendingAiChat.onBatchCommentGenerated) { + batchCommentCallbackRef.current = pendingAiChat.onBatchCommentGenerated; + } + if (pendingAiChat.onMappingGenerated) { + mappingCallbackRef.current = pendingAiChat.onMappingGenerated; + } + if (pendingAiChat.onExpressionGenerated) { + expressionCallbackRef.current = pendingAiChat.onExpressionGenerated; + } + if (pendingAiChat.onSqlFixed) { + sqlFixCallbackRef.current = pendingAiChat.onSqlFixed; + } + sendAiChatInternal(pendingAiChat.message, pendingAiChat.promptType, overrideBoundInfo, pendingAiChat.ext); + useWorkspaceStore.setState({ pendingAiChat: null }); + } + }, [pendingAiChat, boundInfo, sendAiChatInternal]); + + const handleCancel = async () => { + if (closeEventSource.current) { + closeEventSource.current(); + closeEventSource.current = undefined; + } + if (sessionIdRef.current) { + await cancelChatSession(sessionIdRef.current); + clearSession(sessionIdRef.current); + } + }; + + const handleExecuteSql = useCallback((sql: string) => { + const activeConsoleInfo = consoleList?.find((c) => c.id === activeConsoleId); + if (activeConsoleInfo) { + const { getSearchResult } = require('@/pages/main/workspace/components/SQLExecute/searchResultRegistry'); + const searchResultRef = getSearchResult(activeConsoleInfo.id); + if (searchResultRef?.current) { + searchResultRef.current.handleExecuteSQL(sql); + message.success('SQL已执行'); + } else { + message.warning('未找到执行结果组件'); + } + } + }, [consoleList, activeConsoleId]); + + const handleSendToEditor = useCallback((sql: string) => { + const activeConsoleInfo = consoleList?.find((c) => c.id === activeConsoleId); + if (activeConsoleInfo) { + const { getConsoleEditor } = require('@/pages/main/workspace/components/SQLExecute/consoleEditorRegistry'); + const consoleRef = getConsoleEditor(activeConsoleInfo.id); + if (consoleRef?.current?.editorRef) { + consoleRef.current.editorRef.setValue(sql, 'cover'); + message.success('SQL已发送到编辑器'); + } else { + message.warning('未找到编辑器组件'); + } + } + }, [consoleList, activeConsoleId]); + + const handleRetry = () => { + if (lastRequest) { + sendAiChat(lastRequest.message, lastRequest.promptType as IAiChatPromptType, lastRequest.tableNames, lastRequest.ext); + } + }; + + const handleKeyPress = (e: React.KeyboardEvent) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + sendAiChat(inputValue); + } + }; + + const handleInputChange = (e: React.ChangeEvent) => { + const nextValue = e.target.value; + setInputValue(nextValue); + if (nextValue.endsWith('@') && !tableSelectorOpen) { + openTableSelector(); + } + }; + + const handleBoundInfoChange = (value: { dataSourceId: number; databaseName: string; schemaName: string }) => { + setBoundInfo({ + dataSourceId: value.dataSourceId, + databaseName: value.databaseName, + schemaName: value.schemaName, + tableNames: null, + }); + }; + + const handleNewConversation = useCallback(() => { + setBoundInfo((prev) => ({ + ...prev, + tableNames: null, + })); + setSelectedTableDraft([]); + }, []); + + const stateInfo = currentSession ? STATE_LABELS[currentSession.state] : null; + const isProcessing = isActiveState(currentSession?.state); + const isInputDisabled = isProcessing || !boundInfo.dataSourceId; + const selectedTablesForDisplay = currentSession ? currentSession.selectedTables || [] : boundInfo.tableNames || []; + const tableOptions = databaseTables.map((table) => ({ + label: table.comment ? `${table.name} - ${table.comment}` : table.name, + value: table.name, + })); + + return ( +
+
+ +
+
+ {currentSession && ( +
+ + {currentSession.title || (currentSession.messages[0]?.content || '新对话').slice(0, 30)} + +
+ )} + {stateInfo && ( +
+ {stateInfo.text} +
+ 已选择表: + {selectedTablesForDisplay.map((tableName) => ( + { + event.preventDefault(); + handleRemoveSelectedTable(tableName); + }} + > + {tableName} + + ))} + +
+ {isProcessing && ( + + )} +
+ )} + +
+ {currentSession?.messages.map((msg) => { + const extractedSql = msg.role === 'assistant' ? extractSqlFromContent(msg.content) : null; + return ( +
+ {msg.thinking && } + {msg.content} + {extractedSql && msg.role === 'assistant' && ( + + )} +
+ ); + })} + {currentSession?.explainResult && currentSession.explainResult.success && ( + + )} + {currentSession?.state === 'STREAMING' && (currentSession.currentContent || currentSession.currentThinking) && ( +
+ + {currentSession.currentThinking && ( +
+
+ + 思考过程... +
+
+ {currentSession.currentThinking} +
+
+ )} + {currentSession.currentContent && ( + {currentSession.currentContent} + )} +
+
+ )} + {currentSession?.state === 'FAILED' && currentSession.error && ( +
+ + +
+ )} +
+ +
+ + +
+ +
+
+ setTableSelectorOpen(false)} + okText="添加引用" + cancelText="取消" + > + ((props) => { )}
{ diff --git a/chat2db-client/src/components/ConnectionEdit/config/dataSource.ts b/chat2db-client/src/components/ConnectionEdit/config/dataSource.ts index 115aa9403..f37169c0e 100644 --- a/chat2db-client/src/components/ConnectionEdit/config/dataSource.ts +++ b/chat2db-client/src/components/ConnectionEdit/config/dataSource.ts @@ -22,7 +22,6 @@ export const sshConfig: IConnectionConfig['ssh'] = { value: true, }, ], - }, { defaultValue: '', @@ -33,7 +32,7 @@ export const sshConfig: IConnectionConfig['ssh'] = { required: false, styles: { width: '70%', - } + }, }, { defaultValue: '22', @@ -46,8 +45,8 @@ export const sshConfig: IConnectionConfig['ssh'] = { width: '30%', labelWidthEN: '40px', labelWidthCN: '70px', - labelAlign: 'right' - } + labelAlign: 'right', + }, }, { defaultValue: '', @@ -58,7 +57,7 @@ export const sshConfig: IConnectionConfig['ssh'] = { required: false, styles: { width: '70%', - } + }, }, { defaultValue: '', @@ -73,8 +72,8 @@ export const sshConfig: IConnectionConfig['ssh'] = { width: '30%', labelWidthEN: '70px', labelWidthCN: '70px', - labelAlign: 'right' - } + labelAlign: 'right', + }, }, { defaultValue: 'password', @@ -125,13 +124,12 @@ export const sshConfig: IConnectionConfig['ssh'] = { ], styles: { width: '50%', - } + }, }, + ], +}; - ] -} - -const envItem = { +const envItem = { defaultValue: '', inputType: InputType.SELECT, labelNameCN: '环境', @@ -141,8 +139,8 @@ const envItem = { selects: [], styles: { width: '50%', - } -} + }, +}; export const dataSourceFormConfigs: IConnectionConfig[] = [ // MYSQL @@ -167,7 +165,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '70%', - } + }, }, { defaultValue: '3306', @@ -181,8 +179,8 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ width: '30%', labelWidthEN: '40px', labelWidthCN: '40px', - labelAlign: 'right' - } + labelAlign: 'right', + }, }, { defaultValue: AuthenticationType.USERANDPASSWORD, @@ -218,12 +216,11 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ label: 'NONE', value: AuthenticationType.NONE, items: [], - }, ], styles: { width: '50%', - } + }, }, { defaultValue: '', @@ -248,9 +245,9 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ ssh: sshConfig, extendInfo: [ { - "key": "zeroDateTimeBehavior", - "value": "convertToNull" - } + key: 'zeroDateTimeBehavior', + value: 'convertToNull', + }, ], type: DatabaseTypeCode.MYSQL, }, @@ -277,8 +274,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '70%', - - } + }, }, { defaultValue: '5432', @@ -292,8 +288,8 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ width: '30%', labelWidthEN: '40px', labelWidthCN: '40px', - labelAlign: 'right' - } + labelAlign: 'right', + }, }, { defaultValue: AuthenticationType.USERANDPASSWORD, @@ -312,7 +308,6 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ labelNameEN: 'User', name: 'user', required: true, - }, { defaultValue: '', @@ -321,24 +316,20 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ labelNameEN: 'Password', name: 'password', required: true, - }, ], label: 'User&Password', value: AuthenticationType.USERANDPASSWORD, }, { - label: 'NONE', value: AuthenticationType.NONE, items: [], - }, ], styles: { width: '50%', - - } + }, }, { defaultValue: 'postgres', @@ -347,7 +338,6 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ labelNameEN: 'Database', name: 'database', required: false, - }, { defaultValue: 'jdbc:postgresql://localhost:5432', @@ -356,7 +346,6 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ labelNameEN: 'URL', name: 'url', required: true, - }, ], pattern: /jdbc:postgresql:\/\/(.*):(\d+)(\/(\w+))?/, @@ -387,8 +376,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '70%', - - } + }, }, { defaultValue: '1521', @@ -402,8 +390,8 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ width: '30%', labelWidthEN: '40px', labelWidthCN: '40px', - labelAlign: 'right' - } + labelAlign: 'right', + }, }, { defaultValue: 'sid', @@ -426,39 +414,41 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '70%', - } + }, }, ], onChange: (data: IConnectionConfig) => { data.baseInfo.pattern = /jdbc:oracle:(.*):@(.*):(\d+):(.*)/; data.baseInfo.template = 'jdbc:oracle:{driver}:@{host}:{port}:{sid}'; - return data - } + return data; + }, }, { label: 'Service', value: 'service', - items: [{ - defaultValue: 'XE', - inputType: InputType.INPUT, - labelNameCN: '服务名', - labelNameEN: 'Service name', - name: 'serviceName', - required: true, - styles: { - width: '70%', + items: [ + { + defaultValue: 'XE', + inputType: InputType.INPUT, + labelNameCN: '服务名', + labelNameEN: 'Service name', + name: 'serviceName', + required: true, + styles: { + width: '70%', + }, }, - }], + ], onChange: (data: IConnectionConfig) => { data.baseInfo.pattern = /jdbc:oracle:(.*):@\/\/(.*):(\d+)\/(.*)/; data.baseInfo.template = 'jdbc:oracle:{driver}:@//{host}:{port}/{serviceName}'; - return data - } + return data; + }, }, ], styles: { width: '50%', - } + }, }, { defaultValue: 'thin', @@ -478,7 +468,6 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ label: 'oci', }, { - value: 'OCI8', label: 'oci8', }, @@ -487,8 +476,8 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ width: '30%', labelWidthEN: '70px', labelWidthCN: '40px', - labelAlign: 'right' - } + labelAlign: 'right', + }, }, { defaultValue: AuthenticationType.USERANDPASSWORD, @@ -507,7 +496,6 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ labelNameEN: 'User', name: 'user', required: true, - }, { defaultValue: '', @@ -516,24 +504,20 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ labelNameEN: 'Password', name: 'password', required: true, - }, ], label: 'User&Password', value: AuthenticationType.USERANDPASSWORD, }, { - label: 'NONE', value: AuthenticationType.NONE, items: [], - }, ], styles: { width: '50%', - - } + }, }, { defaultValue: 'jdbc:oracle:thin:@localhost:1521:XE', @@ -572,7 +556,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '70%', - } + }, }, { defaultValue: '9092', @@ -586,8 +570,8 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ width: '30%', labelWidthEN: '40px', labelWidthCN: '40px', - labelAlign: 'right' - } + labelAlign: 'right', + }, }, { defaultValue: 'TCP', @@ -604,8 +588,8 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ onChange: (data: IConnectionConfig) => { data.baseInfo.pattern = /jdbc:h2:tcp:\/\/(.*):(\d+)(\/(\w+))?/; data.baseInfo.template = 'jdbc:h2:tcp://{host}:{port}/{database}'; - return data - } + return data; + }, }, { label: i18n('common.label.LocalFile'), @@ -623,13 +607,13 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ onChange: (data: IConnectionConfig) => { data.baseInfo.pattern = /jdbc:h2:(.*)?/; data.baseInfo.template = 'jdbc:h2:{file}'; - return data - } + return data; + }, }, ], styles: { width: '70%', - } + }, }, { defaultValue: AuthenticationType.USERANDPASSWORD, @@ -648,7 +632,6 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ labelNameEN: 'User', name: 'user', required: true, - }, { defaultValue: '', @@ -657,7 +640,6 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ labelNameEN: 'Password', name: 'password', required: true, - }, ], @@ -665,7 +647,6 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ value: AuthenticationType.USERANDPASSWORD, }, { - label: 'NONE', value: AuthenticationType.NONE, items: [], @@ -673,8 +654,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ ], styles: { width: '50%', - - } + }, }, { defaultValue: '', @@ -691,7 +671,6 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ labelNameEN: 'URL', name: 'url', required: true, - }, ], pattern: /jdbc:h2:tcp:\/\/(.*):(\d+)(\/(\w+))?/, @@ -704,20 +683,20 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ type: DatabaseTypeCode.SQLSERVER, extendInfo: [ { - "key": "encrypt", - "value": "false" + key: 'encrypt', + value: 'false', }, { - "key": "trustServerCertificate", - "value": "true" + key: 'trustServerCertificate', + value: 'true', }, { - "key": "integratedSecurity", - "value": "false" + key: 'integratedSecurity', + value: 'false', }, { - "key": "Trusted_Connection", - "value": "yes" + key: 'Trusted_Connection', + value: 'yes', }, ], baseInfo: { @@ -740,7 +719,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '70%', - } + }, }, { defaultValue: '1433', @@ -754,8 +733,8 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ width: '30%', labelWidthEN: '40px', labelWidthCN: '40px', - labelAlign: 'right' - } + labelAlign: 'right', + }, }, { defaultValue: '', @@ -782,7 +761,6 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ labelNameEN: 'User', name: 'user', required: true, - }, { defaultValue: '', @@ -791,7 +769,6 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ labelNameEN: 'Password', name: 'password', required: true, - }, ], @@ -799,17 +776,14 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ value: AuthenticationType.USERANDPASSWORD, }, { - label: 'NONE', value: AuthenticationType.NONE, items: [], - }, ], styles: { width: '50%', - - } + }, }, { defaultValue: '', @@ -845,7 +819,6 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ labelNameEN: 'Name', name: 'alias', required: true, - }, envItem, { @@ -863,7 +836,6 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ labelNameEN: 'URL', name: 'url', required: true, - }, ], pattern: /jdbc:sqlite:(.*)?/, @@ -883,7 +855,6 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ labelNameEN: 'Name', name: 'alias', required: true, - }, envItem, { @@ -895,8 +866,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '70%', - - } + }, }, { defaultValue: '3306', @@ -910,8 +880,8 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ width: '30%', labelWidthEN: '40px', labelWidthCN: '40px', - labelAlign: 'right' - } + labelAlign: 'right', + }, }, { defaultValue: AuthenticationType.USERANDPASSWORD, @@ -930,7 +900,6 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ labelNameEN: 'User', name: 'user', required: true, - }, { defaultValue: '', @@ -939,7 +908,6 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ labelNameEN: 'Password', name: 'password', required: true, - }, ], @@ -947,17 +915,14 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ value: AuthenticationType.USERANDPASSWORD, }, { - label: 'NONE', value: AuthenticationType.NONE, items: [], - }, ], styles: { width: '50%', - - } + }, }, { defaultValue: '', @@ -966,7 +931,6 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ labelNameEN: 'Database', name: 'database', required: false, - }, { defaultValue: 'jdbc:mariadb://localhost:3306', @@ -975,7 +939,6 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ labelNameEN: 'URL', name: 'url', required: true, - }, ], pattern: /jdbc:mariadb:\/\/(.*):(\d+)(\/(\w+))?/, @@ -995,7 +958,6 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ labelNameEN: 'Name', name: 'alias', required: true, - }, envItem, { @@ -1007,8 +969,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '70%', - - } + }, }, { defaultValue: '8123', @@ -1022,8 +983,8 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ width: '30%', labelWidthEN: '40px', labelWidthCN: '40px', - labelAlign: 'right' - } + labelAlign: 'right', + }, }, { defaultValue: AuthenticationType.USERANDPASSWORD, @@ -1042,7 +1003,6 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ labelNameEN: 'User', name: 'user', required: true, - }, { defaultValue: '', @@ -1051,7 +1011,6 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ labelNameEN: 'Password', name: 'password', required: true, - }, ], @@ -1059,17 +1018,14 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ value: AuthenticationType.USERANDPASSWORD, }, { - label: 'NONE', value: AuthenticationType.NONE, items: [], - }, ], styles: { width: '50%', - - } + }, }, { defaultValue: '', @@ -1078,7 +1034,6 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ labelNameEN: 'Database', name: 'database', required: false, - }, { defaultValue: 'jdbc:clickhouse://localhost:8123', @@ -1087,12 +1042,11 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ labelNameEN: 'URL', name: 'url', required: true, - }, ], pattern: /jdbc:clickhouse:\/\/(.*):(\d+)(\/(\w+))?/, template: 'jdbc:clickhouse://{host}:{port}/{database}', - excludes: [OperationColumn.ViewDDL, OperationColumn.CreateTable,OperationColumn.EditTable] + excludes: [OperationColumn.ViewDDL, OperationColumn.CreateTable, OperationColumn.EditTable], //排除掉导出ddl 和 创建表功能 支持的功能见 ./enum.ts => OperationColumn }, ssh: sshConfig, @@ -1108,7 +1062,6 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ labelNameEN: 'Name', name: 'alias', required: true, - }, envItem, { @@ -1120,8 +1073,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '70%', - - } + }, }, { defaultValue: '5236', @@ -1135,8 +1087,8 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ width: '30%', labelWidthEN: '40px', labelWidthCN: '40px', - labelAlign: 'right' - } + labelAlign: 'right', + }, }, { defaultValue: AuthenticationType.USERANDPASSWORD, @@ -1155,7 +1107,6 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ labelNameEN: 'User', name: 'user', required: true, - }, { defaultValue: '', @@ -1177,8 +1128,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ ], styles: { width: '50%', - - } + }, }, { defaultValue: '', @@ -1187,7 +1137,6 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ labelNameEN: 'Database', name: 'database', required: false, - }, { defaultValue: 'jdbc:dm://localhost:5236', @@ -1196,22 +1145,20 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ labelNameEN: 'URL', name: 'url', required: true, - }, ], pattern: /jdbc:dm:\/\/(.*):(\d+)(\/(\w+))?/, template: 'jdbc:dm://{host}:{port}/{database}', // excludes: [OperationColumn.EditTable] - }, ssh: sshConfig, extendInfo: [ { - "key": "zeroDateTimeBehavior", - "value": "convertToNull" + key: 'zeroDateTimeBehavior', + value: 'convertToNull', }, ], - type: DatabaseTypeCode.DM + type: DatabaseTypeCode.DM, }, //DB2 { @@ -1226,7 +1173,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '100%', - } + }, }, envItem, { @@ -1238,7 +1185,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '70%', - } + }, }, { defaultValue: '50000', @@ -1252,8 +1199,8 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ width: '30%', labelWidthEN: '40px', labelWidthCN: '40px', - labelAlign: 'right' - } + labelAlign: 'right', + }, }, { defaultValue: AuthenticationType.USERANDPASSWORD, @@ -1274,7 +1221,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '100%', - } + }, }, { defaultValue: '', @@ -1285,7 +1232,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '100%', - } + }, }, ], label: 'User&Password', @@ -1295,12 +1242,11 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ label: 'NONE', value: AuthenticationType.NONE, items: [], - }, ], styles: { width: '50%', - } + }, }, { defaultValue: '', @@ -1311,7 +1257,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: false, styles: { width: '100%', - } + }, }, { defaultValue: 'jdbc:db2://localhost:50000', @@ -1322,20 +1268,16 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '100%', - } + }, }, - ], pattern: /jdbc:db2:\/\/(.*):(\d+)(\/(\w+))?/, template: 'jdbc:db2://{host}:{port}/{database}', // excludes: [OperationColumn.EditTable] - }, ssh: sshConfig, - extendInfo: [ - - ], - type: DatabaseTypeCode.DB2 + extendInfo: [], + type: DatabaseTypeCode.DB2, }, //presto { @@ -1350,7 +1292,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '100%', - } + }, }, envItem, { @@ -1362,7 +1304,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '70%', - } + }, }, { defaultValue: '8080', @@ -1376,8 +1318,8 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ width: '30%', labelWidthEN: '40px', labelWidthCN: '40px', - labelAlign: 'right' - } + labelAlign: 'right', + }, }, { defaultValue: AuthenticationType.USERANDPASSWORD, @@ -1398,7 +1340,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '100%', - } + }, }, { defaultValue: '', @@ -1409,7 +1351,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '100%', - } + }, }, ], label: 'User&Password', @@ -1419,12 +1361,11 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ label: 'NONE', value: AuthenticationType.NONE, items: [], - }, ], styles: { width: '50%', - } + }, }, { defaultValue: '', @@ -1435,7 +1376,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: false, styles: { width: '100%', - } + }, }, { defaultValue: 'jdbc:presto://localhost:8080', @@ -1446,20 +1387,185 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '100%', - } + }, }, - ], pattern: /jdbc:presto:\/\/(.*):(\d+)(\/(\w+))?/, template: 'jdbc:presto://{host}:{port}/{database}', // excludes: [OperationColumn.EditTable] - }, ssh: sshConfig, - extendInfo: [ - - ], - type: DatabaseTypeCode.PRESTO + extendInfo: [], + type: DatabaseTypeCode.PRESTO, + }, + //tencent dlc + { + baseInfo: { + items: [ + { + defaultValue: '@dlc.tencentcloudapi.com', + inputType: InputType.INPUT, + labelNameCN: '名称', + labelNameEN: 'Name', + name: 'alias', + required: true, + styles: { + width: '100%', + }, + }, + envItem, + { + defaultValue: 'dlc.tencentcloudapi.com', + inputType: InputType.INPUT, + labelNameCN: '接入地址', + labelNameEN: 'Endpoint', + name: 'host', + required: true, + styles: { + width: '100%', + }, + }, + { + defaultValue: AuthenticationType.USERANDPASSWORD, + inputType: InputType.SELECT, + labelNameCN: '身份验证', + labelNameEN: 'Authentication', + name: 'authenticationType', + required: true, + selects: [ + { + items: [ + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: 'SecretId', + labelNameEN: 'SecretId', + name: 'user', + required: true, + styles: { + width: '100%', + }, + }, + { + defaultValue: '', + inputType: InputType.PASSWORD, + labelNameCN: 'SecretKey', + labelNameEN: 'SecretKey', + name: 'password', + required: true, + styles: { + width: '100%', + }, + }, + ], + label: 'SecretId&SecretKey', + value: AuthenticationType.USERANDPASSWORD, + }, + ], + styles: { + width: '50%', + }, + }, + { + defaultValue: 'SQLTask', + inputType: InputType.SELECT, + labelNameCN: '任务类型', + labelNameEN: 'Task Type', + name: 'taskType', + required: true, + selects: [ + { + label: 'SQLTask', + value: 'SQLTask', + items: [], + }, + { + label: 'SparkSQLTask', + value: 'SparkSQLTask', + items: [], + }, + ], + styles: { + width: '50%', + }, + }, + { + defaultValue: 'ap-guangzhou', + inputType: InputType.INPUT, + labelNameCN: '地域', + labelNameEN: 'Region', + name: 'region', + required: true, + styles: { + width: '50%', + }, + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: '数据库', + labelNameEN: 'Database', + name: 'databaseName', + required: false, + styles: { + width: '50%', + }, + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: '数据源连接', + labelNameEN: 'Data Source', + name: 'datasourceConnection', + required: false, + styles: { + width: '50%', + }, + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: '数据引擎', + labelNameEN: 'Engine', + name: 'dataEngineName', + required: false, + styles: { + width: '50%', + }, + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: 'Spark App', + labelNameEN: 'Spark App', + name: 'sparkAppName', + required: false, + styles: { + width: '50%', + }, + }, + { + defaultValue: + 'jdbc:dlc:dlc.tencentcloudapi.com?task_type=SQLTask&database_name=&datasource_connection=®ion=ap-guangzhou&data_engine_name=&spark_app_name=', + inputType: InputType.INPUT, + labelNameCN: 'URL', + labelNameEN: 'URL', + name: 'url', + required: true, + styles: { + width: '100%', + }, + }, + ], + pattern: + /jdbc:dlc:(.*?)\?task_type=(.*?)&database_name=([^&]*)&datasource_connection=([^&]*)®ion=([^&]*)&data_engine_name=([^&]*)&spark_app_name=([^&]*)/, + template: + 'jdbc:dlc:{host}?task_type={taskType}&database_name={databaseName}&datasource_connection={datasourceConnection}®ion={region}&data_engine_name={dataEngineName}&spark_app_name={sparkAppName}', + // excludes: [OperationColumn.EditTable] + }, + ssh: sshConfig, + extendInfo: [], + type: DatabaseTypeCode.DLC, }, //oceanbase { @@ -1474,7 +1580,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '100%', - } + }, }, envItem, { @@ -1486,7 +1592,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '70%', - } + }, }, { defaultValue: '2883', @@ -1500,8 +1606,8 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ width: '30%', labelWidthEN: '40px', labelWidthCN: '40px', - labelAlign: 'right' - } + labelAlign: 'right', + }, }, { defaultValue: AuthenticationType.USERANDPASSWORD, @@ -1522,7 +1628,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '100%', - } + }, }, { defaultValue: '', @@ -1533,7 +1639,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '100%', - } + }, }, ], label: 'User&Password', @@ -1543,12 +1649,11 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ label: 'NONE', value: AuthenticationType.NONE, items: [], - }, ], styles: { width: '50%', - } + }, }, { defaultValue: '', @@ -1559,7 +1664,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: false, styles: { width: '100%', - } + }, }, { defaultValue: 'jdbc:oceanbase://localhost:2883', @@ -1570,20 +1675,16 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '100%', - } + }, }, - ], pattern: /jdbc:oceanbase:\/\/(.*):(\d+)(\/(\w+))?/, template: 'jdbc:oceanbase://{host}:{port}/{database}', // excludes: [OperationColumn.EditTable] - }, ssh: sshConfig, - extendInfo: [ - - ], - type: DatabaseTypeCode.OCEANBASE + extendInfo: [], + type: DatabaseTypeCode.OCEANBASE, }, //redis { @@ -1598,7 +1699,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '100%', - } + }, }, envItem, { @@ -1610,7 +1711,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '70%', - } + }, }, { defaultValue: '6379', @@ -1624,8 +1725,8 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ width: '30%', labelWidthEN: '40px', labelWidthCN: '40px', - labelAlign: 'right' - } + labelAlign: 'right', + }, }, { defaultValue: AuthenticationType.USERANDPASSWORD, @@ -1646,7 +1747,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '100%', - } + }, }, { defaultValue: '', @@ -1657,7 +1758,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '100%', - } + }, }, ], label: 'User&Password', @@ -1667,12 +1768,11 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ label: 'NONE', value: AuthenticationType.NONE, items: [], - }, ], styles: { width: '50%', - } + }, }, { defaultValue: '', @@ -1683,10 +1783,10 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: false, styles: { width: '100%', - } + }, }, { - defaultValue: 'jdbc:redis://localhost:6379', + defaultValue: 'redis://localhost:6379/0', inputType: InputType.INPUT, labelNameCN: 'URL', labelNameEN: 'URL', @@ -1694,18 +1794,15 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '100%', - } + }, }, - ], - pattern: /jdbc:redis:\/\/(.*):(\d+)(\/(\w+))?/, - template: 'jdbc:redis://{host}:{port}/{database}', + pattern: /redis:\/\/(.*):(\d+)(\/(\w+))?/, + template: 'redis://{host}:{port}/{database}', }, ssh: sshConfig, - extendInfo: [ - - ], - type: DatabaseTypeCode.REDIS + extendInfo: [], + type: DatabaseTypeCode.REDIS, }, //hive { @@ -1720,7 +1817,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '100%', - } + }, }, envItem, { @@ -1732,7 +1829,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '70%', - } + }, }, { defaultValue: '10000', @@ -1746,8 +1843,8 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ width: '30%', labelWidthEN: '40px', labelWidthCN: '40px', - labelAlign: 'right' - } + labelAlign: 'right', + }, }, { defaultValue: AuthenticationType.USERANDPASSWORD, @@ -1768,7 +1865,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '100%', - } + }, }, { defaultValue: '', @@ -1779,7 +1876,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '100%', - } + }, }, ], label: 'User&Password', @@ -1789,12 +1886,11 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ label: 'NONE', value: AuthenticationType.NONE, items: [], - }, ], styles: { width: '50%', - } + }, }, { defaultValue: '', @@ -1805,7 +1901,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: false, styles: { width: '100%', - } + }, }, { defaultValue: 'jdbc:hive2://localhost:10000', @@ -1816,20 +1912,16 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '100%', - } + }, }, - ], pattern: /jdbc:hive2:\/\/(.*):(\d+)(\/(\w+))?/, template: 'jdbc:hive2://{host}:{port}/{database}', // excludes: [OperationColumn.EditTable] - }, ssh: sshConfig, - extendInfo: [ - - ], - type: DatabaseTypeCode.HIVE + extendInfo: [], + type: DatabaseTypeCode.HIVE, }, //KINGBASE { @@ -1844,7 +1936,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '100%', - } + }, }, envItem, { @@ -1856,7 +1948,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '70%', - } + }, }, { defaultValue: '54321', @@ -1870,8 +1962,8 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ width: '30%', labelWidthEN: '40px', labelWidthCN: '40px', - labelAlign: 'right' - } + labelAlign: 'right', + }, }, { defaultValue: AuthenticationType.USERANDPASSWORD, @@ -1892,7 +1984,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '100%', - } + }, }, { defaultValue: '', @@ -1903,7 +1995,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '100%', - } + }, }, ], label: 'User&Password', @@ -1913,12 +2005,11 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ label: 'NONE', value: AuthenticationType.NONE, items: [], - }, ], styles: { width: '50%', - } + }, }, { defaultValue: '', @@ -1929,7 +2020,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: false, styles: { width: '100%', - } + }, }, { defaultValue: 'jdbc:kingbase8://127.0.0.1:54321', @@ -1940,20 +2031,16 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '100%', - } + }, }, - ], pattern: /jdbc:kingbase8:\/\/(.*):(\d+)(\/(\w+))?/, template: 'jdbc:kingbase8://{host}:{port}/{database}', // excludes: [OperationColumn.EditTable] - }, ssh: sshConfig, - extendInfo: [ - - ], - type: DatabaseTypeCode.KINGBASE + extendInfo: [], + type: DatabaseTypeCode.KINGBASE, }, //MONGODB { @@ -1968,7 +2055,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '100%', - } + }, }, envItem, { @@ -1980,7 +2067,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '70%', - } + }, }, { defaultValue: '27017', @@ -1994,8 +2081,8 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ width: '30%', labelWidthEN: '40px', labelWidthCN: '40px', - labelAlign: 'right' - } + labelAlign: 'right', + }, }, { defaultValue: AuthenticationType.USERANDPASSWORD, @@ -2016,7 +2103,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '100%', - } + }, }, { defaultValue: '', @@ -2027,7 +2114,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '100%', - } + }, }, ], label: 'User&Password', @@ -2037,12 +2124,11 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ label: 'NONE', value: AuthenticationType.NONE, items: [], - }, ], styles: { width: '50%', - } + }, }, { defaultValue: '', @@ -2053,7 +2139,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: false, styles: { width: '100%', - } + }, }, { defaultValue: 'mongodb://localhost:27017', @@ -2064,16 +2150,133 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '100%', - } + }, }, - ], pattern: /mongodb:\/\/(.*):(\d+)(\/(\w+))?/, template: 'mongodb://{host}:{port}/{database}', - excludes: [OperationColumn.ViewDDL, OperationColumn.CreateTable,OperationColumn.EditTable] + excludes: [OperationColumn.ViewDDL, OperationColumn.CreateTable, OperationColumn.EditTable], + }, + ssh: sshConfig, + extendInfo: [], + type: DatabaseTypeCode.MONGODB, + }, + // 在文件的适当位置添加 Phoenix 的配置 + { + type: DatabaseTypeCode.PHOENIX, + baseInfo: { + items: [ + { + defaultValue: '@localhost', + inputType: InputType.INPUT, + labelNameCN: '名称', + labelNameEN: 'Name', + name: 'alias', + required: true, + styles: { + width: '100%', + }, + }, + envItem, + { + defaultValue: 'localhost', + inputType: InputType.INPUT, + labelNameCN: '主机', + labelNameEN: 'Host', + name: 'host', + required: true, + styles: { + width: '70%', + }, + }, + { + defaultValue: '2181', // Phoenix 的默认端口 + inputType: InputType.INPUT, + labelNameCN: '端口', + labelNameEN: 'Port', + name: 'port', + labelTextAlign: 'right', + required: true, + styles: { + width: '30%', + labelWidthEN: '40px', + labelWidthCN: '40px', + labelAlign: 'right', + }, + }, + { + defaultValue: AuthenticationType.USERANDPASSWORD, + inputType: InputType.SELECT, + labelNameCN: '身份验证', + labelNameEN: 'Authentication', + name: 'authenticationType', + required: true, + selects: [ + { + items: [ + { + defaultValue: 'root', + inputType: InputType.INPUT, + labelNameCN: '用户名', + labelNameEN: 'User', + name: 'user', + required: true, + styles: { + width: '100%', + }, + }, + { + defaultValue: '', + inputType: InputType.PASSWORD, + labelNameCN: '密码', + labelNameEN: 'Password', + name: 'password', + required: true, + styles: { + width: '100%', + }, + }, + ], + label: 'User&Password', + value: AuthenticationType.USERANDPASSWORD, + }, + { + label: 'NONE', + value: AuthenticationType.NONE, + items: [], + }, + ], + styles: { + width: '50%', + }, + }, + { + defaultValue: 'hbase', + inputType: InputType.INPUT, + labelNameCN: '根目录', + labelNameEN: 'rootdir', + name: 'rootdir', + required: false, + styles: { + width: '100%', + }, + }, + { + defaultValue: 'jdbc:phoenix:localhost:2181:/hbase', + inputType: InputType.INPUT, + labelNameCN: 'URL', + labelNameEN: 'URL', + name: 'url', + required: true, + styles: { + width: '100%', + }, + }, + ], + pattern: /jdbc:phoenix:(.*):(\d+)(\/(\w+))?:/, + template: 'jdbc:phoenix:{host}:{port}:/{rootdir}', }, ssh: sshConfig, extendInfo: [], - type: DatabaseTypeCode.MONGODB }, ]; diff --git a/chat2db-client/src/components/ConsoleEditor/components/ChatInput/index.less b/chat2db-client/src/components/ConsoleEditor/components/ChatInput/index.less index 5a78ff68e..cb6248189 100644 --- a/chat2db-client/src/components/ConsoleEditor/components/ChatInput/index.less +++ b/chat2db-client/src/components/ConsoleEditor/components/ChatInput/index.less @@ -1,10 +1,8 @@ .chatWrapper { display: flex; align-items: center; - padding: 2px 4px 2px 20px; - height: 42px; - box-sizing: border-box; - border-bottom: 1px solid var(--color-border-secondary); + gap: 8px; + width: 100%; } .chatShortcut { @@ -86,4 +84,10 @@ padding-bottom: 4px; border-bottom: 1px solid var(--color-border-secondary); margin-bottom: 4px; +} + +.tableSelect { + min-width: 10%; + max-width: 50%; + flex: 0 1 auto; // 允许收缩但不允许增长,保持自动基础大小 } \ No newline at end of file diff --git a/chat2db-client/src/components/ConsoleEditor/components/ChatInput/index.tsx b/chat2db-client/src/components/ConsoleEditor/components/ChatInput/index.tsx index 592755979..978adac27 100644 --- a/chat2db-client/src/components/ConsoleEditor/components/ChatInput/index.tsx +++ b/chat2db-client/src/components/ConsoleEditor/components/ChatInput/index.tsx @@ -1,10 +1,9 @@ -import React, { useState } from 'react'; -import styles from './index.less'; -import AIImg from '@/assets/img/ai.svg'; -import { Button, Input, Popover, Select, Radio, Space } from 'antd'; -import i18n from '@/i18n/'; import Iconfont from '@/components/Iconfont'; +import i18n from '@/i18n/'; import { AIType } from '@/typings/ai'; +import { Button, Input, Popover, Radio, Select, Space } from 'antd'; +import React, { useState } from 'react'; +import styles from './index.less'; export const enum SyncModelType { AUTO = 0, @@ -23,7 +22,6 @@ interface IProps { onPressEnter: (value: string) => void; onSelectTableSyncModel: (model: number) => void; onSelectTables?: (tables: string[]) => void; - // onClickRemainBtn: Function; onCancelStream: () => void; } @@ -38,43 +36,23 @@ const ChatInput = (props: IProps) => { e.preventDefault(); return; } - props.onPressEnter && props.onPressEnter(e.target.value); + props.onPressEnter?.(e.target.value); }; const renderSelectTable = () => { - const { tables, onSelectTableSyncModel, selectedTables, onSelectTables } = props; - const options = (tables || []).map((t) => ({ value: t, label: t })); + const { onSelectTableSyncModel, syncTableModel } = props; return (
onSelectTableSyncModel(v.target.value)} - // value={syncTableModel} - value={SyncModelType.MANUAL} + value={syncTableModel} style={{ marginBottom: '8px' }} > - {/* 自动 */} + 自动 手动 - {/* {syncTableModel === 0 ? ( - i18n('chat.input.syncTable.tips') - ) : ( - )} */} - <> - {i18n('chat.input.remain.tooltip')} - onSelectTables?.(v)} + /> { useEffect(() => { if(!isActive){ - resetSenseKeyword(); - resetSenseTable(); - resetSenseDatabase(); - resetSenseField(); + // 注释掉旧的 IntelliSense 重置 + // resetSenseKeyword(); + // resetSenseTable(); + // resetSenseDatabase(); + // resetSenseField(); } }, [isActive]); @@ -79,7 +82,8 @@ const SelectBoundInfo = memo((props: IProps) => { if(!isActive){ return } - registerIntelliSenseKeyword(boundInfo.databaseType); + // 注释掉旧的关键字注册,使用新的 syntax-parser + // registerIntelliSenseKeyword(boundInfo.databaseType); }, [boundInfo.dataSourceId, isActive]); // 当数据源变化时,重新获取数据库列表 @@ -169,32 +173,34 @@ const SelectBoundInfo = memo((props: IProps) => { // 注册表名 useEffect(() => { if (isActive) { - const tableNameListTemp = allTableList.map((t) => t.name); - setTableNameList(tableNameListTemp); - registerIntelliSenseTable( - allTableList, - boundInfo.databaseType, - boundInfo.dataSourceId, - boundInfo.databaseName, - boundInfo.schemaName, - ); - registerIntelliSenseField( - tableNameListTemp, - boundInfo.dataSourceId, - boundInfo.databaseName, - boundInfo.schemaName, - ); - setSelectedTables(tableNameListTemp.slice(0, 1)); + // 注释掉旧的表名注册,使用新的 syntax-parser + // const tableNameListTemp = allTableList.map((t) => t.name); + // setTableNameList(tableNameListTemp); + // registerIntelliSenseTable( + // allTableList, + // boundInfo.databaseType, + // boundInfo.dataSourceId, + // boundInfo.databaseName, + // boundInfo.schemaName, + // ); + // registerIntelliSenseField( + // tableNameListTemp, + // boundInfo.dataSourceId, + // boundInfo.databaseName, + // boundInfo.schemaName, + // ); + //setSelectedTables(tableNameListTemp.slice(0, 1)); } }, [allTableList, isActive]); // 注册数据库名 useEffect(() => { - const editorDatabaseTips = databaseNameList.map((item) => ({ - name: item.value, - dataSourceName: boundInfo.dataSourceName, - })); - registerIntelliSenseDatabase(editorDatabaseTips); + // 注释掉旧的数据库名注册,使用新的 syntax-parser + // const editorDatabaseTips = databaseNameList.map((item) => ({ + // name: item.value, + // dataSourceName: boundInfo.dataSourceName, + // })); + // registerIntelliSenseDatabase(editorDatabaseTips); }, [databaseNameList]); // 选择数据源 @@ -220,7 +226,8 @@ const SelectBoundInfo = memo((props: IProps) => { // 选择数据库 const changeDataBase = (item) => { - const _databaseName = databaseNameList?.find((i) => i.key === item.key)?.value; + // debugger; + const _databaseName = databaseNameList?.find((i) => i.key === item)?.value; setBoundInfo({ ...boundInfo, @@ -282,19 +289,20 @@ const SelectBoundInfo = memo((props: IProps) => { {supportDatabase && ( - -
- -
{boundInfo.databaseName || `<${'database'}>`}
- -
-
+ {databaseNameList.map((item) => ( + + ))} + )} {supportSchema && ( diff --git a/chat2db-client/src/components/ConsoleEditor/hooks/useSaveEditorData.ts b/chat2db-client/src/components/ConsoleEditor/hooks/useSaveEditorData.ts index 761c70358..a80e1ee4c 100644 --- a/chat2db-client/src/components/ConsoleEditor/hooks/useSaveEditorData.ts +++ b/chat2db-client/src/components/ConsoleEditor/hooks/useSaveEditorData.ts @@ -1,6 +1,6 @@ -import {useState, useEffect, useRef} from 'react'; +import { useState, useEffect, useRef } from 'react'; import { ConsoleStatus } from '@/constants'; -import { message } from 'antd'; +import { App } from 'antd'; import indexedDB from '@/indexedDB'; import historyServer from '@/service/history'; import i18n from '@/i18n'; @@ -18,6 +18,7 @@ interface IProps { export const useSaveEditorData = (props: IProps) => { const { isActive, source, editorRef, boundInfo, defaultValue } = props; + const { message } = App.useApp(); const timerRef = useRef(); // 上一次同步的console数据 const lastSyncConsole = useRef(defaultValue); @@ -124,5 +125,5 @@ export const useSaveEditorData = (props: IProps) => { } }, []); - return {saveConsole, saveStatus} -} + return { saveConsole, saveStatus }; +}; diff --git a/chat2db-client/src/components/ConsoleEditor/index.less b/chat2db-client/src/components/ConsoleEditor/index.less index dd212a6ba..ca8d93517 100644 --- a/chat2db-client/src/components/ConsoleEditor/index.less +++ b/chat2db-client/src/components/ConsoleEditor/index.less @@ -1,6 +1,7 @@ .console { - position: relative; height: 100%; + display: flex; + flex-direction: column; :global { .ant-spin-nested-loading { @@ -8,20 +9,94 @@ } .ant-spin-container { - height: calc(100% - 40px); + height: 100%; + display: flex; + flex: 1; } } } + .consoleEditor { height: 100%; + flex: 1; + min-height: 0; /* Allow the editor to shrink */ } .consoleEditorWithChat { - height: calc(100% - 42px); + height: 100%; + flex: 1; + min-height: 0; /* Allow the editor to shrink */ } .aiBlock { - font-size: 14px; - line-height: 20px; + padding: 16px; + height: 100%; + overflow-y: auto; + + // Markdown 样式 + :global { + h1, h2, h3, h4, h5, h6 { + margin-top: 24px; + margin-bottom: 16px; + font-weight: 600; + line-height: 1.25; + } + + p { + margin-bottom: 16px; + line-height: 1.5; + } + + code { + padding: 0.2em 0.4em; + margin: 0; + font-size: 85%; + background-color: rgba(27,31,35,0.05); + border-radius: 3px; + } + + pre { + padding: 16px; + overflow: auto; + font-size: 85%; + line-height: 1.45; + background-color: #f6f8fa; + border-radius: 3px; + + code { + padding: 0; + margin: 0; + background-color: transparent; + border: 0; + } + } + + ul, ol { + padding-left: 2em; + margin-bottom: 16px; + } + + blockquote { + padding: 0 1em; + color: #6a737d; + border-left: 0.25em solid #dfe2e5; + margin: 0 0 16px 0; + } + + table { + border-spacing: 0; + border-collapse: collapse; + margin-bottom: 16px; + + th, td { + padding: 6px 13px; + border: 1px solid #dfe2e5; + } + + tr:nth-child(2n) { + background-color: #f6f8fa; + } + } + } } diff --git a/chat2db-client/src/components/ConsoleEditor/index.tsx b/chat2db-client/src/components/ConsoleEditor/index.tsx index 237e4ef9a..1c5774c9c 100644 --- a/chat2db-client/src/components/ConsoleEditor/index.tsx +++ b/chat2db-client/src/components/ConsoleEditor/index.tsx @@ -1,46 +1,40 @@ +import Popularize from '@/components/Popularize'; +import i18n from '@/i18n'; +import { IBoundInfo } from '@/typings'; +import { requestAiSqlCompletion, IAiSqlCompletionTask } from '@/service/aiSqlCompletion'; +import { Modal, Spin, message } from 'antd'; import React, { + createContext, + ForwardedRef, + forwardRef, + useCallback, useEffect, + useImperativeHandle, useMemo, useRef, useState, - useImperativeHandle, - ForwardedRef, - forwardRef, - createContext, } from 'react'; -import { formatParams } from '@/utils/url'; -import connectToEventSource from '@/utils/eventSource'; -import { Spin, Drawer, Modal } from 'antd'; -import ChatInput, { SyncModelType } from './components/ChatInput'; -import MonacoEditor, { IEditorOptions, IExportRefFunction, IRangeType } from '../MonacoEditor'; -import aiServer from '@/service/ai'; import { v4 as uuidv4 } from 'uuid'; -import { IAiConfig, IBoundInfo } from '@/typings'; -import Popularize from '@/components/Popularize'; +import MonacoEditor, { + IEditorIns, + IEditorOptions, + IExportRefFunction, + IRangeType, + ISelectedContentInfo, + registerVinPasteTransform, +} from '../MonacoEditor'; import OperationLine from './components/OperationLine'; -import { chatErrorForKey, chatErrorToLogin } from '@/constants/chat'; -import { AIType } from '@/typings/ai'; -import i18n from '@/i18n'; -import configService from '@/service/config'; import styles from './index.less'; // ----- hooks ----- import { useSaveEditorData } from './hooks/useSaveEditorData'; // ----- store ----- -import { useSettingStore, fetchRemainingUse, setAiConfig } from '@/store/setting'; +import { setCurrentWorkspaceExtend, setPendingAiChat, IAiChatPromptType } from '@/pages/main/workspace/store/common'; // ----- function ----- import { handelCreateConsole } from '@/pages/main/workspace/functions/shortcutKeyCreateConsole'; -enum IPromptType { - NL_2_SQL = 'NL_2_SQL', - SQL_EXPLAIN = 'SQL_EXPLAIN', - SQL_OPTIMIZER = 'SQL_OPTIMIZER', - SQL_2_SQL = 'SQL_2_SQL', - ChatRobot = 'ChatRobot', -} - export type IAppendValue = { text: any; range?: IRangeType; @@ -53,8 +47,6 @@ interface IProps { /** 添加或修改的内容 */ appendValue?: IAppendValue; defaultValue?: string; - /** 是否开启AI输入 */ - hasAiChat: boolean; /** 是否可以开启SQL转到自然语言的相关ai操作 */ hasAi2Lang?: boolean; /** 是否有 */ @@ -63,7 +55,7 @@ interface IProps { boundInfo: IBoundInfo; setBoundInfo: (params: IBoundInfo) => void; editorOptions?: IEditorOptions; - onExecuteSQL: (sql: string) => void; + onExecuteSQL: (sql: string, selectedInfo?: ISelectedContentInfo | null) => void; } export interface IConsoleRef { @@ -74,45 +66,166 @@ interface IIntelligentEditorContext { isActive: boolean; tableNameList: string[]; setTableNameList: (tables: string[]) => void; - selectedTables: string[]; - setSelectedTables: (tables: string[]) => void; } export const IntelligentEditorContext = createContext({} as any); +interface IAiCompletionContext { + message: string; + ext?: string; + replaceRange: any; + originalText: string; +} + +interface IStatementBoundary { + start: number; + end: number; +} + +const isAiCompletionShortcut = (event: KeyboardEvent) => + event.altKey && + !event.ctrlKey && + !event.metaKey && + (event.key === '\\' || event.code === 'Backslash' || event.code === 'IntlBackslash'); + +const getTrimmedBoundary = (sql: string, boundary: IStatementBoundary): IStatementBoundary => { + let start = boundary.start; + let end = boundary.end; + + while (start < end && /\s/.test(sql[start])) { + start++; + } + while (end > start && /\s/.test(sql[end - 1])) { + end--; + } + + return { start, end }; +}; + +const findStatementBoundary = (sql: string, cursorOffset: number): IStatementBoundary => { + const boundaries: IStatementBoundary[] = []; + let start = 0; + let quote: "'" | '"' | '`' | '[' | null = null; + let inLineComment = false; + let inBlockComment = false; + + for (let i = 0; i < sql.length; i++) { + const char = sql[i]; + const next = sql[i + 1]; + + if (inLineComment) { + if (char === '\n' || char === '\r') { + inLineComment = false; + } + continue; + } + + if (inBlockComment) { + if (char === '*' && next === '/') { + inBlockComment = false; + i++; + } + continue; + } + + if (quote) { + if (quote === "'" && char === "'" && next === "'") { + i++; + continue; + } + if (quote === '"' && char === '"' && next === '"') { + i++; + continue; + } + if (quote === '`' && char === '`' && next === '`') { + i++; + continue; + } + if ( + (quote === "'" && char === "'") || + (quote === '"' && char === '"') || + (quote === '`' && char === '`') || + (quote === '[' && char === ']') + ) { + quote = null; + } + continue; + } + + if (char === '-' && next === '-') { + inLineComment = true; + i++; + continue; + } + + if (char === '/' && next === '*') { + inBlockComment = true; + i++; + continue; + } + + if (char === "'" || char === '"' || char === '`' || char === '[') { + quote = char as typeof quote; + continue; + } + + if (char === ';') { + boundaries.push({ start, end: i }); + start = i + 1; + } + } + + boundaries.push({ start, end: sql.length }); + + const containingBoundary = boundaries.find( + (boundary) => cursorOffset >= boundary.start && cursorOffset <= boundary.end, + ); + const preferredBoundary = containingBoundary || boundaries[boundaries.length - 1]; + const preferredIndex = boundaries.indexOf(preferredBoundary); + const searchOrder = [ + preferredBoundary, + ...boundaries.slice(preferredIndex + 1), + ...boundaries.slice(0, preferredIndex).reverse(), + ]; + + for (const boundary of searchOrder) { + const trimmedBoundary = getTrimmedBoundary(sql, boundary); + if (trimmedBoundary.start < trimmedBoundary.end) { + return trimmedBoundary; + } + } + + return { start: cursorOffset, end: cursorOffset }; +}; + function ConsoleEditor(props: IProps, ref: ForwardedRef) { - const { - hasAiChat = true, - boundInfo, - setBoundInfo, - appendValue, - hasSaveBtn = true, - source, - defaultValue, - isActive, - } = props; + const { boundInfo, setBoundInfo, appendValue, hasSaveBtn = true, source, defaultValue, isActive } = props; const uid = useMemo(() => uuidv4(), []); - const chatResult = useRef(''); const editorRef = useRef(); - const [selectedTables, setSelectedTables] = useState([]); + const disposeVinPasteTransformRef = useRef<(() => void) | undefined>(); + const shortcutRegisteredRef = useRef(false); + const aiCompletionTaskRef = useRef(null); + const triggerAiCompletionRef = useRef<(editor: IEditorIns, monaco: any) => void>(); + const aiCompletionVisibleKeyRef = useRef(null); + const aiCompletionStateRef = useRef<{ + id: string; + text: string; + range: any; + replaceRange: any; + originalText: string; + } | null>(null); const [tableNameList, setTableNameList] = useState([]); - const [syncTableModel, setSyncTableModel] = useState(0); const [isLoading, setIsLoading] = useState(false); - const [aiContent, setAiContent] = useState(''); - const [isAiDrawerOpen, setIsAiDrawerOpen] = useState(false); - const [isAiDrawerLoading, setIsAiDrawerLoading] = useState(false); const [popularizeModal, setPopularizeModal] = useState(false); - const [modalProps, setModalProps] = useState({}); - const [isStream, setIsStream] = useState(false); + const [modalProps] = useState({}); + const [aiCompletion, setAiCompletion] = useState<{ + id: string; + text: string; + range: any; + replaceRange: any; + originalText: string; + } | null>(null); const aiFetchIntervalRef = useRef(); - const closeEventSource = useRef(); - const { aiConfig, hasWhite, remainingUse } = useSettingStore((state) => { - return { - aiConfig: state.aiConfig, - hasWhite: state.hasWhite, - remainingUse: state.remainingUse, - }; - }); // ---------------- new-code ---------------- const { saveConsole } = useSaveEditorData({ @@ -124,21 +237,19 @@ function ConsoleEditor(props: IProps, ref: ForwardedRef) { }); // ---------------- new-code ---------------- - /** - * 当前选择的AI类型是Chat2DBAI - */ - const isChat2DBAI = useMemo(() => aiConfig?.aiSqlSource === AIType.CHAT2DBAI, [aiConfig?.aiSqlSource]); - - useEffect(() => { - handleSelectTableSyncModel(); - }, [hasWhite, localStorage.getItem('syncTableModel')]); - useEffect(() => { if (appendValue) { editorRef?.current?.setValue(appendValue.text, appendValue.range); } }, [appendValue]); + useEffect(() => { + return () => { + aiCompletionTaskRef.current?.cancel(); + disposeVinPasteTransformRef.current?.(); + }; + }, []); + useImperativeHandle( ref, () => ({ @@ -147,218 +258,216 @@ function ConsoleEditor(props: IProps, ref: ForwardedRef) { [editorRef?.current], ); - const handleApiKeyEmptyOrGetQrCode = async (shouldPoll?: boolean) => { - setIsLoading(true); - try { - const { wechatQrCodeUrl, token, tip } = await aiServer.getLoginQrCode({}); - setIsLoading(false); - - setPopularizeModal(true); - setModalProps({ - imageUrl: wechatQrCodeUrl, - token, - tip, - }); - if (shouldPoll) { - let pollCnt = 0; - aiFetchIntervalRef.current = setInterval(async () => { - const { apiKey } = (await aiServer.getLoginStatus({ token })) || {}; - pollCnt++; - if (apiKey || pollCnt >= 60) { - clearInterval(aiFetchIntervalRef.current); - } - if (apiKey) { - setPopularizeModal(false); - - setAiConfig({ - ...(aiConfig || {}), - apiKey, - }); + const executeSQL = (sql?: string) => { + const selectedInfo = editorRef?.current?.getCurrentSelectInfo(); + const sqlContent = sql || selectedInfo?.text || editorRef?.current?.getAllContent(); - fetchRemainingUse(apiKey); - } - }, 3000); - } - } catch (e) { - setIsLoading(false); + if (!sqlContent) { + return; } + props.onExecuteSQL && props.onExecuteSQL(sqlContent, selectedInfo); }; - const handleAIChatInEditor = async (content: string, promptType: IPromptType, ext?: string) => { - const _aiConfig = await configService.getAiSystemConfig({}); - handleAiChat(content, promptType, _aiConfig, ext); + const handleEditorDidMount = useCallback((editor: IEditorIns) => { + // SQL consoles support pasting Excel VIN rows directly inside IN (...). + disposeVinPasteTransformRef.current?.(); + disposeVinPasteTransformRef.current = registerVinPasteTransform(editor); + }, []); + + // 打开 AI 聊天面板并发送选中内容 + const openAiChatWithMessage = (selectedText: string, promptType: IAiChatPromptType) => { + // 打开 AI 扩展面板 + setCurrentWorkspaceExtend('ai'); + // 设置待发送的 AI 聊天消息 + setPendingAiChat({ + dataSourceId: boundInfo.dataSourceId, + message: selectedText, + promptType, + }); }; - const handleAiChat = async (content: string, promptType: IPromptType, _aiConfig?: IAiConfig, ext?: string) => { - const { apiKey } = _aiConfig || aiConfig || {}; - if (!apiKey && isChat2DBAI) { - handleApiKeyEmptyOrGetQrCode(true); - return; + const addAction = [ + { + id: 'explainSQL', + label: i18n('common.text.explainSQL'), + action: (selectedText: string) => { + openAiChatWithMessage(selectedText, 'SQL_EXPLAIN'); + }, + }, + { + id: 'optimizeSQL', + label: i18n('common.text.optimizeSQL'), + action: (selectedText: string) => { + openAiChatWithMessage(selectedText, 'SQL_OPTIMIZER'); + }, + }, + { + id: 'changeSQL', + label: i18n('common.text.conversionSQL'), + action: (selectedText: string) => { + openAiChatWithMessage(selectedText, 'SQL_2_SQL'); + }, + }, + ]; + + const buildAiCompletionContext = (editor: IEditorIns, monaco): IAiCompletionContext | null => { + const model = editor.getModel(); + const position = editor.getPosition(); + const selection = editor.getSelection(); + if (!model || !position) { + return null; } - const { dataSourceId, databaseName, schemaName } = boundInfo; - const isNL2SQL = promptType === IPromptType.NL_2_SQL; - if (isNL2SQL) { - setIsLoading(true); - } else { - setAiContent(''); - setIsAiDrawerOpen(true); - setIsAiDrawerLoading(true); + const currentSql = model?.getValue() || ''; + const selectedText = selection && !selection.isEmpty() ? model?.getValueInRange(selection) || '' : ''; + const cursorOffset = model.getOffsetAt(position); + const boundary = selectedText + ? { + start: model.getOffsetAt(selection!.getStartPosition()), + end: model.getOffsetAt(selection!.getEndPosition()), + } + : findStatementBoundary(currentSql, cursorOffset); + const statementSql = selectedText || currentSql.slice(boundary.start, boundary.end); + const startPosition = model.getPositionAt(boundary.start); + const endPosition = model.getPositionAt(boundary.end); + const replaceRange = new monaco.Range( + startPosition.lineNumber, + startPosition.column, + endPosition.lineNumber, + endPosition.column, + ); + + return { + message: [`${selectedText ? '选中 SQL' : '当前语句'}:`, statementSql || '(空)'].join('\n'), + replaceRange, + originalText: statementSql, + }; + }; + + const getAiCompletionRange = (editor: IEditorIns, monaco) => { + const position = editor.getPosition(); + if (position) { + return new monaco.Range(position.lineNumber, position.column, position.lineNumber, position.column); } + return new monaco.Range(1, 1, 1, 1); + }; - const params = formatParams({ - message: content, - promptType, - dataSourceId, - databaseName, - schemaName, - tableNames: syncTableModel ? selectedTables : null, - ext, - }); + const triggerAiCompletion = useCallback( + async (editor: IEditorIns, monaco) => { + if (!boundInfo?.dataSourceId) { + message.warning('请先选择数据库连接'); + return; + } + + const model = editor.getModel(); + if (!model) { + return; + } + + aiCompletionTaskRef.current?.cancel(); + setAiCompletion(null); + setIsLoading(true); + + const versionId = model.getVersionId(); + const completionContext = buildAiCompletionContext(editor, monaco); + if (!completionContext) { + setIsLoading(false); + return; + } + const task = requestAiSqlCompletion({ + boundInfo, + message: completionContext.message, + ext: completionContext.ext, + }); + aiCompletionTaskRef.current = task; - const handleMessage = (_message: string) => { - setIsLoading(false); - setIsAiDrawerLoading(false); try { - const isEOF = _message === '[DONE]'; - if (isEOF) { - closeEventSource.current(); - setIsStream(false); - if (isChat2DBAI) { - fetchRemainingUse(apiKey); - } - if (isNL2SQL) { - editorRef?.current?.setValue('\n'); - } else { - setIsAiDrawerLoading(false); - chatResult.current += '\n'; - setAiContent(chatResult.current); - chatResult.current = ''; - } + const sql = await task.promise; + if (aiCompletionTaskRef.current !== task) { return; } + aiCompletionTaskRef.current = null; - let hasErrorToLogin = false; - chatErrorToLogin.forEach((err) => { - if (_message.includes(err)) { - hasErrorToLogin = true; - } - }); - let hasKeyLimitedOrExpired = false; - chatErrorForKey.forEach((err) => { - if (_message.includes(err)) { - hasKeyLimitedOrExpired = true; - } - }); - - if (hasKeyLimitedOrExpired) { - closeEventSource.current(); - setIsLoading(false); - handlePopUp(); + if (!sql) { + message.warning('AI 未返回可用的 SQL 建议'); return; } - if (hasErrorToLogin) { - closeEventSource.current(); - setIsLoading(false); - hasErrorToLogin && handleApiKeyEmptyOrGetQrCode(true); - // hasErrorToInvite && handleClickRemainBtn(); - fetchRemainingUse(apiKey); + if (model.getVersionId() !== versionId) { + message.warning('编辑器内容已变化,请重新触发 AI 补全'); return; } - if (isNL2SQL) { - editorRef?.current?.setValue(JSON.parse(_message).content); - } else { - chatResult.current += JSON.parse(_message).content; - setAiContent(chatResult.current); - } + setAiCompletion({ + id: uuidv4(), + text: sql, + range: getAiCompletionRange(editor, monaco), + replaceRange: completionContext.replaceRange, + originalText: completionContext.originalText, + }); } catch (error) { - setIsLoading(false); - setIsStream(false); - setIsAiDrawerLoading(false); - closeEventSource.current(); + if (aiCompletionTaskRef.current === task) { + aiCompletionTaskRef.current = null; + message.error('AI SQL 补全失败'); + } + } finally { + if (aiCompletionTaskRef.current === task || aiCompletionTaskRef.current === null) { + setIsLoading(false); + } } - }; - - const handleError = (error: any) => { - console.error('Error:', error); - setIsLoading(false); - setIsAiDrawerLoading(false); - setIsStream(false); - closeEventSource.current(); - }; - - closeEventSource.current = connectToEventSource({ - url: `/api/ai/chat?${params}`, - uid, - onOpen: () => { - setIsStream(true); - }, - onMessage: handleMessage, - onError: handleError, - }); - }; + }, + [boundInfo], + ); - const executeSQL = (sql?: string) => { - const sqlContent = sql || editorRef?.current?.getCurrentSelectContent() || editorRef?.current?.getAllContent(); + const clearAiCompletion = useCallback(() => { + setAiCompletion(null); + }, []); - if (!sqlContent) { - return; - } - props.onExecuteSQL && props.onExecuteSQL(sqlContent); - }; + const acceptAiCompletion = useCallback( + (editor: IEditorIns) => { + const completion = aiCompletionStateRef.current; + if (!completion) { + return false; + } - const addAction = [ - { - id: 'explainSQL', - label: i18n('common.text.explainSQL'), - action: (selectedText: string) => handleAIChatInEditor(selectedText, IPromptType.SQL_EXPLAIN), - }, - { - id: 'optimizeSQL', - label: i18n('common.text.optimizeSQL'), - action: (selectedText: string) => handleAIChatInEditor(selectedText, IPromptType.SQL_OPTIMIZER), + editor.executeEdits('aiSqlCompletion', [ + { + range: completion.replaceRange, + text: completion.text, + }, + ]); + + const completionLines = completion.text.split(/\r\n|\r|\n/); + const endLineNumber = completion.replaceRange.startLineNumber + completionLines.length - 1; + const endColumn = + completionLines.length === 1 + ? completion.replaceRange.startColumn + completionLines[0].length + : completionLines[completionLines.length - 1].length + 1; + editor.setPosition({ lineNumber: endLineNumber, column: endColumn }); + editor.focus(); + clearAiCompletion(); + return true; }, - { - id: 'changeSQL', - label: i18n('common.text.conversionSQL'), - action: (selectedText: string, ext?: string) => { - handleAIChatInEditor(selectedText, IPromptType.SQL_2_SQL, ext); - }, - }, - ]; - - /** - * 弹框 关注公众号 - */ - const handlePopUp = () => { - setModalProps({ - imageUrl: - 'http://oss.sqlgpt.cn/static/chat2db-wechat.jpg?x-oss-process=image/auto-orient,1/resize,m_lfit,w_256/quality,Q_80/format,webp', - tip: ( - <> - {remainingUse?.remainingUses === 0 &&

Key次数用完或者过期

} -

微信扫描二维码并关注公众号获得 AI 使用机会。

- - ), - }); - setPopularizeModal(true); - }; + [clearAiCompletion], + ); - const handleSelectTableSyncModel = () => { - const syncModel = localStorage.getItem('syncTableModel'); - const hasAiAccess = hasWhite; - if (syncModel !== null) { - setSyncTableModel(Number(syncModel)); - return; - } + useEffect(() => { + triggerAiCompletionRef.current = triggerAiCompletion; + }, [triggerAiCompletion]); - setSyncTableModel(hasAiAccess ? SyncModelType.AUTO : SyncModelType.MANUAL); - }; + useEffect(() => { + aiCompletionStateRef.current = aiCompletion; + aiCompletionVisibleKeyRef.current?.set(Boolean(aiCompletion)); + }, [aiCompletion]); // 注册快捷键 const registerShortcutKey = (editor, monaco) => { + if (shortcutRegisteredRef.current) { + return; + } + shortcutRegisteredRef.current = true; + aiCompletionVisibleKeyRef.current = editor.createContextKey('aiSqlCompletionVisible', false); + // 保存 editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => { const value = editor?.getValue(); @@ -375,6 +484,56 @@ function ConsoleEditor(props: IProps, ref: ForwardedRef) { editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.KeyL, () => { handelCreateConsole(); }); + + const aiCompletionKeyCodes = [ + monaco.KeyCode.Backslash, + monaco.KeyCode.IntlBackslash, + monaco.KeyCode.US_BACKSLASH, + ].filter(Boolean); + [...new Set(aiCompletionKeyCodes)].forEach((keyCode) => { + editor.addCommand(monaco.KeyMod.Alt | keyCode, () => { + triggerAiCompletionRef.current?.(editor, monaco); + }); + }); + + editor.addCommand( + monaco.KeyCode.Tab, + () => { + acceptAiCompletion(editor); + }, + 'aiSqlCompletionVisible', + ); + + editor.getDomNode()?.addEventListener( + 'keydown', + (event) => { + if (isAiCompletionShortcut(event)) { + event.preventDefault(); + event.stopPropagation(); + triggerAiCompletionRef.current?.(editor, monaco); + return; + } + + if (event.key !== 'Tab') { + return; + } + + if (acceptAiCompletion(editor)) { + event.preventDefault(); + event.stopPropagation(); + } + }, + true, + ); + + editor.onKeyDown((event) => { + if (event.browserEvent.key === 'Escape' && aiCompletionStateRef.current) { + event.preventDefault(); + event.stopPropagation(); + clearAiCompletion(); + return; + } + }); }; return ( @@ -383,75 +542,32 @@ function ConsoleEditor(props: IProps, ref: ForwardedRef) { isActive, tableNameList, setTableNameList, - selectedTables, - setSelectedTables, }} >
+ - {hasAiChat && ( - { - handleAiChat(value, IPromptType.NL_2_SQL); - }} - selectedTables={selectedTables} - onSelectTables={(tables: string[]) => { - setSelectedTables(tables); - }} - syncTableModel={syncTableModel} - onSelectTableSyncModel={(model: number) => { - setSyncTableModel(model); - localStorage.setItem('syncTableModel', String(model)); - }} - onCancelStream={() => { - closeEventSource.current(); - setIsStream(false); - setIsLoading(false); - }} - /> - )} - { - try { - setIsAiDrawerOpen(false); - setIsAiDrawerLoading(false); - setIsStream(false); - closeEventSource.current && closeEventSource.current(); - } catch (error) { - // console.log('close drawer', error); - } - }} - > - -
{aiContent}
-
-
- { setOpenCreateDatabaseModal(openCreateDatabaseModal); }, []); - return (!!relyOnParams && ( + return ( { setOpen(false); }} title={config.title} - destroyOnClose + destroyOnHidden + forceRender confirmLoading={confirmLoading} open={open} onOk={onOk} @@ -154,7 +155,7 @@ const CreateDatabase = () => { - {noCommentDatabase.includes(relyOnParams.databaseType) ? null : ( + {relyOnParams && noCommentDatabase.includes(relyOnParams.databaseType) ? null : ( @@ -184,7 +185,7 @@ const CreateDatabase = () => { )}
- )) + ); }; diff --git a/chat2db-client/src/components/DataGenerationModal/index.less b/chat2db-client/src/components/DataGenerationModal/index.less new file mode 100644 index 000000000..172899c7c --- /dev/null +++ b/chat2db-client/src/components/DataGenerationModal/index.less @@ -0,0 +1,52 @@ +.dataGenerationModal { + .ant-table-tbody > tr > td { + padding: 8px 12px; + } + + .ant-form-item { + margin-bottom: 16px; + } + + .previewSection { + margin-top: 16px; + border-top: 1px solid #f0f0f0; + padding-top: 16px; + + h4 { + margin-bottom: 12px; + color: #262626; + font-weight: 500; + } + } + + .progressSection { + margin-top: 16px; + padding: 16px; + background-color: #fafafa; + border-radius: 6px; + + h4 { + margin-bottom: 12px; + color: #262626; + font-weight: 500; + } + } + + .actionButtons { + display: flex; + gap: 8px; + margin-bottom: 16px; + } + + .columnConfigTable { + .ant-select { + width: 100%; + } + } + + .previewTable { + .ant-table-tbody > tr:hover > td { + background-color: #f5f5f5; + } + } +} diff --git a/chat2db-client/src/components/DataGenerationModal/index.tsx b/chat2db-client/src/components/DataGenerationModal/index.tsx new file mode 100644 index 000000000..6942870ac --- /dev/null +++ b/chat2db-client/src/components/DataGenerationModal/index.tsx @@ -0,0 +1,630 @@ +import React, { useState, useEffect, useRef, useCallback } from 'react'; +import { Modal, Form, Button, Table, Select, Input, InputNumber, message, Progress, Spin } from 'antd'; +import { BulbFilled, ReloadOutlined } from '@ant-design/icons'; +import { setOpenDataGenerationModal } from '@/pages/main/workspace/store/modal'; +import { setPendingAiChat, setCurrentWorkspaceExtend } from '@/pages/main/workspace/store/common'; +import { IDataExpressionResult } from '@/pages/main/workspace/store/common'; +import createRequest from '@/service/base'; +import taskService from '@/service/task'; + +const { Option } = Select; + +export interface IDataGenerationModalParams { + dataSourceId: number; + databaseName: string; + schemaName?: string; + tableName: string; +} + +interface TableInfo { + dataSourceId: number; + databaseName: string; + schemaName?: string; + tableName: string; +} + +interface ColumnConfigVO { + columnName: string; + dataType: string; + expression: string; + comment?: string; + nullable: boolean; + maxLength?: number; + scale?: number; + foreignKey?: boolean; + foreignKeySourceType?: 'REAL' | 'VIRTUAL' | null; + referencedTable?: string; + referencedColumnName?: string; +} + +interface GenerateRequest { + dataSourceId: number; + databaseName: string; + schemaName?: string; + tableName: string; + rowCount?: number; + columnConfigs?: ColumnConfigVO[]; + batchSize?: number; +} + +interface GeneratorTemplate { + label: string; + category: string; + expression: string; + example: string; + suggestedDataType: string; +} + +interface SavedConfig { + columnName: string; + dataType: string; + expression: string; + comment?: string; + nullable: boolean; + autoIncrement: boolean; + maxLength?: number; + scale?: number; + foreignKey?: boolean; + foreignKeySourceType?: 'REAL' | 'VIRTUAL' | null; + referencedTable?: string; + referencedColumnName?: string; +} + +interface ColumnConfig { + columnName: string; + dataType: string; + comment?: string; + expression?: string; + nullable: boolean; + autoIncrement: boolean; + maxLength?: number; + scale?: number; + foreignKey?: boolean; + foreignKeySourceType?: 'REAL' | 'VIRTUAL' | null; + referencedTable?: string; + referencedColumnName?: string; +} + +interface PreviewRow { + [key: string]: any; +} + +interface PreviewVO { + tableName: string; + previewData: PreviewRow[]; + columns: { + columnName: string; + dataType: string; + comment?: string; + }[]; +} + +const loadGeneratorTemplates = createRequest('/api/rdb/table/generate-data/templates', { method: 'get' }); + +const loadTableColumns = createRequest('/api/rdb/table/generate-data/config', { method: 'post' }); + +const loadSavedConfigs = createRequest('/api/rdb/table/generate-data/generation-rule/list', { method: 'get' }); + +const generatePreview = createRequest('/api/rdb/table/generate-data/preview', { method: 'post' }); + +const executeGeneration = createRequest('/api/rdb/table/generate-data/execute', { method: 'post' }); + +const legacyExpressionMap: Record = { + '#{Number.uuid}': '#{Internet.uuid}', + '#{Vehicle.driveType}': '#{Vehicle.drive_type}', + '#{Vehicle.licensePlate}': '#{Vehicle.license_plate}', + '#{Date.future}': "#{Date.future '30','DAYS'}", +}; + +const normalizeExpression = (expression?: string): string | undefined => { + if (!expression) return expression; + return legacyExpressionMap[expression] || expression; +}; + +const findInvalidExpressionColumn = (columns: ColumnConfig[]): string | undefined => { + const invalidColumn = columns.find((col) => { + const expression = col.expression?.trim(); + return !col.foreignKey && expression && !/^#\{.+\}$/.test(expression); + }); + return invalidColumn?.columnName; +}; + +const getForeignKeyLabel = (record: ColumnConfig): string => { + const sourceType = record.foreignKeySourceType || 'REAL'; + const referenced = record.referencedTable && record.referencedColumnName + ? `${record.referencedTable}.${record.referencedColumnName}` + : '引用表字段'; + return `由 ${sourceType} 外键驱动: ${referenced}`; +}; + +const groupByCategory = (templates: GeneratorTemplate[]): Record => { + const groups: Record = {}; + for (const t of templates) { + if (!groups[t.category]) groups[t.category] = []; + groups[t.category].push(t); + } + return groups; +}; + +const DataGenerationModal: React.FC = () => { + const [form] = Form.useForm(); + const [open, setOpen] = useState(false); + const [tableInfo, setTableInfo] = useState(null); + const [columns, setColumns] = useState([]); + const [loading, setLoading] = useState(false); + const [templates, setTemplates] = useState([]); + const [templateGroups, setTemplateGroups] = useState>({}); + const [previewData, setPreviewData] = useState([]); + const [showPreview, setShowPreview] = useState(false); + const [progressVisible, setProgressVisible] = useState(false); + const [taskProgress, setTaskProgress] = useState(0); + const [taskStatus, setTaskStatus] = useState(''); + const [taskName, setTaskName] = useState(''); + const [generating, setGenerating] = useState(false); + const [logs, setLogs] = useState([]); + const [rowCount, setRowCount] = useState(100); + const [guessLoading, setGuessLoading] = useState(false); + const tableInfoRef = useRef(null); + const pollingRef = useRef(null); + + const addLog = (log: string) => { + const timestamp = new Date().toLocaleTimeString(); + setLogs(prev => [...prev, `${timestamp}: ${log}`]); + }; + + const handleAiGuessExpression = useCallback(() => { + if (!tableInfo || columns.length === 0) { + message.warning('请先加载表列信息'); + return; + } + + setGuessLoading(true); + setPendingAiChat({ + dataSourceId: tableInfo.dataSourceId, + databaseName: tableInfo.databaseName, + schemaName: tableInfo.schemaName, + tableNames: [tableInfo.tableName], + message: `请为表 ${tableInfo.tableName} 的字段推荐 datafaker 表达式`, + promptType: 'NL_2_DATA_EXPRESSION', + onExpressionGenerated: handleExpressionGenerated, + }); + setCurrentWorkspaceExtend('ai'); + setGuessLoading(false); + message.success('已切换到 AI 助手,请在 AI 聊天面板中查看推荐结果'); + }, [tableInfo, columns]); + + const handleExpressionGenerated = useCallback((result: IDataExpressionResult) => { + if (!result || !result.column_expressions || result.column_expressions.length === 0) { + message.warning('未获取到表达式推荐'); + return; + } + + const newColumns = columns.map(col => { + if (col.foreignKey) { + return col; + } + const matched = result.column_expressions.find(e => e.column_name === col.columnName); + if (matched && matched.expression) { + return { ...col, expression: normalizeExpression(matched.expression) }; + } + return col; + }); + + setColumns(newColumns); + message.success(`AI 已推荐 ${result.column_expressions.length} 个字段表达式,请查看并确认`); + }, [columns]); + + const openDataGenerationModal = useCallback((params: IDataGenerationModalParams) => { + setOpen(true); + setTableInfo(params); + tableInfoRef.current = params; + setPreviewData([]); + setShowPreview(false); + setProgressVisible(false); + setTaskProgress(0); + setLogs([]); + }, []); + + useEffect(() => { + setOpenDataGenerationModal(openDataGenerationModal); + }, [openDataGenerationModal]); + + useEffect(() => { + if (open && templates.length === 0) { + loadGeneratorTemplates({}) + .then((res) => { + if (res) { + setTemplates(res); + setTemplateGroups(groupByCategory(res)); + } + }) + .catch(console.error); + } + }, [open]); + + useEffect(() => { + if (open && tableInfo) { + fetchTableColumns(); + } + }, [open, tableInfo]); + + useEffect(() => { + return () => { + if (pollingRef.current) { + clearInterval(pollingRef.current); + pollingRef.current = null; + } + }; + }, []); + + const fetchTableColumns = async () => { + if (!tableInfo) return; + setLoading(true); + try { + const [columnsRes, savedRes] = await Promise.all([ + loadTableColumns({ + dataSourceId: tableInfo.dataSourceId, + databaseName: tableInfo.databaseName, + schemaName: tableInfo.schemaName, + tableName: tableInfo.tableName, + }), + loadSavedConfigs({ + dataSourceId: tableInfo.dataSourceId, + databaseName: tableInfo.databaseName, + schemaName: tableInfo.schemaName, + tableName: tableInfo.tableName, + }), + ]); + + if (columnsRes) { + const savedMap = new Map(); + if (savedRes) { + for (const saved of savedRes) { + savedMap.set(saved.columnName, saved.expression); + } + } + const merged = columnsRes.map(col => ({ + ...col, + expression: normalizeExpression(savedMap.get(col.columnName) || col.expression), + })); + setColumns(merged); + } + } catch { + message.error('加载表列信息失败'); + } finally { + setLoading(false); + } + }; + + const buildColumnConfigs = (): ColumnConfigVO[] => { + return columns.map(col => ({ + columnName: col.columnName, + dataType: col.dataType, + expression: col.expression || '', + nullable: col.nullable, + maxLength: col.maxLength, + scale: col.scale, + foreignKey: col.foreignKey, + foreignKeySourceType: col.foreignKeySourceType, + referencedTable: col.referencedTable, + referencedColumnName: col.referencedColumnName, + })); + }; + + const handlePreview = async () => { + if (!tableInfo) return; + const invalidColumnName = findInvalidExpressionColumn(columns); + if (invalidColumnName) { + message.warning(`字段 ${invalidColumnName} 的表达式格式应为 #{Provider.method}`); + return; + } + setLoading(true); + try { + const count = form.getFieldValue('rowCount') || 10; + const res = await generatePreview({ + dataSourceId: tableInfo.dataSourceId, + databaseName: tableInfo.databaseName, + schemaName: tableInfo.schemaName, + tableName: tableInfo.tableName, + rowCount: count, + columnConfigs: buildColumnConfigs(), + }); + if (res) { + setPreviewData(res.previewData || []); + setShowPreview(true); + } + } catch { + message.error('生成预览失败'); + } finally { + setLoading(false); + } + }; + + const handleGenerate = async () => { + if (!tableInfo) return; + const invalidColumnName = findInvalidExpressionColumn(columns); + if (invalidColumnName) { + message.warning(`字段 ${invalidColumnName} 的表达式格式应为 #{Provider.method}`); + return; + } + const count = form.getFieldValue('rowCount') || 100; + setRowCount(count); + setGenerating(true); + setProgressVisible(true); + setTaskProgress(0); + setTaskStatus('PROCESSING'); + setTaskName('数据生成 - ' + tableInfo.tableName); + setLogs([]); + addLog('任务已创建,开始生成数据...'); + + try { + const taskId = await executeGeneration({ + dataSourceId: tableInfo.dataSourceId, + databaseName: tableInfo.databaseName, + schemaName: tableInfo.schemaName, + tableName: tableInfo.tableName, + rowCount: count, + columnConfigs: buildColumnConfigs(), + }); + if (taskId) { + addLog(`任务ID: ${taskId}`); + startPolling(taskId); + } + } catch { + addLog('创建任务失败'); + message.error('数据生成失败'); + setGenerating(false); + setTaskStatus('ERROR'); + } + }; + + const startPolling = (taskId: number) => { + if (pollingRef.current) { + clearInterval(pollingRef.current); + } + + pollingRef.current = setInterval(async () => { + try { + const task = await taskService.getTask({ id: taskId }); + if (task) { + const progress = parseInt(task.taskProgress || '0', 10); + setTaskProgress(progress); + + if (task.taskStatus === 'FINISH') { + clearInterval(pollingRef.current!); + pollingRef.current = null; + setTaskStatus('FINISH'); + addLog(`数据生成完成,共 ${rowCount} 行`); + message.success('数据生成完成'); + setGenerating(false); + setTimeout(() => { + setProgressVisible(false); + setOpen(false); + }, 2000); + } else if (task.taskStatus === 'ERROR') { + clearInterval(pollingRef.current!); + pollingRef.current = null; + setTaskStatus('ERROR'); + let errorMsg = '数据生成失败'; + if (task.content) { + errorMsg = task.content; + } + addLog(`错误: ${errorMsg}`); + message.error(errorMsg); + setGenerating(false); + } else { + addLog(`进度: ${progress}%`); + } + } + } catch { + addLog('查询任务状态失败'); + } + }, 1000); + }; + + const handleCloseProgress = () => { + if (pollingRef.current) { + clearInterval(pollingRef.current); + pollingRef.current = null; + } + if (!generating) { + setProgressVisible(false); + } + }; + + const handleTemplateChange = (columnName: string, value: string) => { + const template = templates.find(t => `${t.category} - ${t.label}` === value); + if (template) { + setColumns(prev => prev.map(col => + col.columnName === columnName ? { ...col, expression: template.expression } : col + )); + } + }; + + const handleExpressionChange = (columnName: string, expression: string) => { + setColumns(prev => prev.map(col => + col.columnName === columnName ? { ...col, expression: normalizeExpression(expression) } : col + )); + }; + + const getMatchedTemplate = (expression: string | undefined): string | undefined => { + if (!expression) return undefined; + const matched = templates.find(t => t.expression === expression); + return matched ? `${matched.category} - ${matched.label}` : undefined; + }; + + const tableColumns = [ + { title: '列名', dataIndex: 'columnName', key: 'columnName', width: 140 }, + { title: '数据类型', dataIndex: 'dataType', key: 'dataType', width: 100 }, + { title: '注释', dataIndex: 'comment', key: 'comment', width: 100 }, + { + title: '预设模板', + key: 'template', + width: 200, + render: (_: any, record: ColumnConfig) => { + if (record.autoIncrement) return 自增列(跳过); + if (record.foreignKey) return {getForeignKeyLabel(record)}; + return ( + + ); + }, + }, + { + title: '表达式', + key: 'expression', + width: 300, + render: (_: any, record: ColumnConfig) => { + if (record.autoIncrement) return 自增列(跳过); + if (record.foreignKey) { + return ; + } + return ( + handleExpressionChange(record.columnName, e.target.value)} + /> + ); + }, + }, + ]; + + const previewColumns = previewData.length > 0 + ? Object.keys(previewData[0]).map(key => ({ title: key, dataIndex: key, key, width: 120, ellipsis: true })) + : []; + + return ( + <> + setOpen(false)} + width={1100} + footer={[ + , + , + , + ]} + > +
+ + + + +
+

列配置

+
+ + + + +
+
+ + + + {showPreview && ( +
+

预览数据(前10行)

+
String(index)} + pagination={false} + size="small" + scroll={{ x: true, y: 150 }} + /> + + )} + + + + + {generating ? '生成中...' : '关闭'} + , + ]} + width={600} + closable={false} + > +
+
+
任务: {taskName}
+
+
+ {logs.map((log, index) => ( +
{log}
+ ))} +
+ +
+ 已生成: {Math.floor(rowCount * taskProgress / 100)} / {rowCount} 行 +
+
+
+ + ); +}; + +export default DataGenerationModal; diff --git a/chat2db-client/src/components/DataTransferModal/index.tsx b/chat2db-client/src/components/DataTransferModal/index.tsx new file mode 100644 index 000000000..6f8db470f --- /dev/null +++ b/chat2db-client/src/components/DataTransferModal/index.tsx @@ -0,0 +1,383 @@ +import React, { memo, useEffect, useRef, useState } from 'react'; +import { Alert, Button, Descriptions, Input, Modal, Progress, Select, Steps, Table, message } from 'antd'; +import type { ColumnsType } from 'antd/es/table'; +import connectionService from '@/service/connection'; +import sqlService from '@/service/sql'; +import taskService from '@/service/task'; +import { getConnectionList, useConnectionStore } from '@/pages/main/store/connection'; +import { IDatabaseItem, ISchemaItem } from '@/typings'; +import { IConnectionListItem } from '@/typings/connection'; +import { setOpenDataTransferModal } from '@/pages/main/workspace/store/modal'; + +export interface IDataTransferModalParams { + dataSourceId: number; + databaseName?: string; + schemaName?: string; + tableNames?: string[]; + executedCallback?: () => void; +} + +interface ITableRow { + name: string; + comment?: string; + rowCount?: number; +} + +const DataTransferModal = memo(() => { + const connectionList = useConnectionStore((s) => s.connectionList); + const [open, setOpen] = useState(false); + const [params, setParams] = useState(null); + const [currentStep, setCurrentStep] = useState(0); + const [sourceTableData, setSourceTableData] = useState([]); + const [selectedTableNames, setSelectedTableNames] = useState([]); + const [tableLoading, setTableLoading] = useState(false); + const [searchKey, setSearchKey] = useState(''); + const [targetDataSourceId, setTargetDataSourceId] = useState(); + const [targetDatabaseName, setTargetDatabaseName] = useState(); + const [targetSchemaName, setTargetSchemaName] = useState(); + const [targetDatabases, setTargetDatabases] = useState([]); + const [targetSchemas, setTargetSchemas] = useState([]); + const [targetDbLoading, setTargetDbLoading] = useState(false); + const [targetSchemaLoading, setTargetSchemaLoading] = useState(false); + const [transferring, setTransferring] = useState(false); + const [transferProgress, setTransferProgress] = useState(0); + const [logs, setLogs] = useState([]); + const pollingRef = useRef(null); + const lastTaskContentRef = useRef(''); + const executedCallbackRef = useRef(); + + useEffect(() => { + setOpenDataTransferModal((modalParams: IDataTransferModalParams) => { + setParams(modalParams); + executedCallbackRef.current = modalParams.executedCallback; + setSelectedTableNames(modalParams.tableNames || []); + setTargetDataSourceId(undefined); + setTargetDatabaseName(undefined); + setTargetSchemaName(undefined); + setTargetDatabases([]); + setTargetSchemas([]); + setCurrentStep(modalParams.tableNames?.length ? 1 : 0); + setLogs([]); + lastTaskContentRef.current = ''; + setTransferProgress(0); + setOpen(true); + }); + }, []); + + useEffect(() => { + if (!connectionList) { + getConnectionList(); + } + }, [connectionList]); + + useEffect(() => { + if (open && params && currentStep === 0) { + loadSourceTables(); + } + }, [open, params, currentStep, searchKey]); + + useEffect(() => { + if (!targetDataSourceId) { + setTargetDatabases([]); + return; + } + setTargetDbLoading(true); + setTargetDatabaseName(undefined); + setTargetSchemaName(undefined); + setTargetSchemas([]); + connectionService.getDatabaseList({ dataSourceId: targetDataSourceId }) + .then((res) => setTargetDatabases(Array.isArray(res) ? res : [])) + .catch(() => message.error('加载目标数据库失败')) + .finally(() => setTargetDbLoading(false)); + }, [targetDataSourceId]); + + useEffect(() => { + if (!targetDataSourceId || !targetDatabaseName) { + setTargetSchemas([]); + return; + } + setTargetSchemaLoading(true); + setTargetSchemaName(undefined); + connectionService.getSchemaList({ dataSourceId: targetDataSourceId, databaseName: targetDatabaseName }) + .then((res) => setTargetSchemas(Array.isArray(res) ? res : [])) + .catch(() => message.error('加载目标 Schema 失败')) + .finally(() => setTargetSchemaLoading(false)); + }, [targetDataSourceId, targetDatabaseName]); + + const loadSourceTables = () => { + if (!params) { + return; + } + setTableLoading(true); + sqlService.getTableList({ + dataSourceId: params.dataSourceId, + databaseName: params.databaseName || '', + schemaName: params.schemaName, + searchKey, + pageNo: 1, + pageSize: 1000, + }) + .then((res) => { + setSourceTableData((res.data || []).map((table) => ({ + name: table.name, + comment: table.comment, + rowCount: table.rowCount, + }))); + }) + .finally(() => setTableLoading(false)); + }; + + const addLog = (log: string) => { + setLogs((prev) => [...prev, `${new Date().toLocaleString()}: ${log}`]); + }; + + const handleStartTransfer = async () => { + if (!params || !targetDataSourceId || !targetDatabaseName || selectedTableNames.length === 0) { + message.warning('请选择源表和目标库'); + return; + } + + setTransferring(true); + setCurrentStep(3); + setLogs([]); + setTransferProgress(0); + addLog('start------'); + + try { + const taskId = await taskService.transferData({ + sourceDataSourceId: params.dataSourceId, + sourceDatabaseName: params.databaseName, + sourceSchemaName: params.schemaName, + targetDataSourceId, + targetDatabaseName, + targetSchemaName, + tableNames: selectedTableNames, + }); + addLog(`Task created: ${taskId}`); + startPolling(taskId); + } catch (error: any) { + addLog(`Error: ${error.message}`); + message.error(error.message || '数据传输失败'); + setTransferring(false); + } + }; + + const startPolling = (taskId: number) => { + if (pollingRef.current) { + clearInterval(pollingRef.current); + } + + pollingRef.current = setInterval(async () => { + try { + const task = await taskService.getTask({ id: taskId }); + const processedCount = parseInt(task.taskProgress || '0', 10); + setTransferProgress(processedCount); + if (task.content && task.content !== lastTaskContentRef.current) { + lastTaskContentRef.current = task.content; + addLog(task.content); + } + if (task.taskStatus === 'FINISH') { + clearInterval(pollingRef.current!); + pollingRef.current = null; + addLog('Transfer completed successfully'); + message.success('数据传输成功'); + setTransferring(false); + executedCallbackRef.current?.(); + } else if (task.taskStatus === 'ERROR') { + clearInterval(pollingRef.current!); + pollingRef.current = null; + addLog(`Error: ${task.content || '数据传输失败'}`); + message.error(task.content || '数据传输失败'); + setTransferring(false); + } + } catch (error: any) { + clearInterval(pollingRef.current!); + pollingRef.current = null; + addLog(`Polling error: ${error.message}`); + message.error('数据传输失败'); + setTransferring(false); + } + }, 1000); + }; + + const handleClose = () => { + if (pollingRef.current) { + clearInterval(pollingRef.current); + pollingRef.current = null; + } + if (!transferring) { + setOpen(false); + } + }; + + const columns: ColumnsType = [ + { title: 'Table name', dataIndex: 'name', key: 'name' }, + { title: 'Row Count', dataIndex: 'rowCount', key: 'rowCount', width: 120 }, + { title: 'Comment', dataIndex: 'comment', key: 'comment' }, + ]; + + const renderStepContent = () => { + if (!params) { + return null; + } + + if (currentStep === 0) { + return ( + <> + +
setSelectedTableNames(keys as string[]), + }} + /> + + ); + } + + if (currentStep === 1) { + return ( +
+ + ({ label: database.name, value: database.name }))} + /> + {targetSchemas.length > 0 && ( + { + const newMappings = [...fieldMappings]; + const index = newMappings.findIndex((m) => m.sourceField === record.sourceField); + if (index >= 0) { + newMappings[index].targetField = value; + // 更新主键标识 + if (previewData) { + const targetCol = previewData.tableColumns.find((col) => col.name === value); + newMappings[index].primaryKey = !!targetCol?.primaryKey; + } + setFieldMappings(newMappings); + } + }} + options={previewData?.tableColumns.map((col) => ({ + label: `${col.name}${col.primaryKey ? ' (PK)' : ''}`, + value: col.name, + }))} + /> + ), + }, + { + title: i18n('workspace.table.import.fieldMapping.primaryKey'), + dataIndex: 'primaryKey', + key: 'primaryKey', + width: 80, + render: (text: boolean) => ( + + {text ? '✓' : ''} + + ), + }, + ]; + + // 获取步骤标题 + const getStepTitle = () => { + switch (currentStep) { + case 0: + return i18n('workspace.table.import.step.selectFile'); + case 1: + return i18n('workspace.table.import.step.fieldMapping'); + case 2: + return i18n('workspace.table.import.step.importMode'); + case 3: + return i18n('workspace.table.import.step.importProgress'); + default: + return ''; + } + }; + + // 渲染步骤内容 + const renderStepContent = () => { + switch (currentStep) { + case 0: + return ( +
+
+ +
+ {params?.tableName} +
+
+
+ +
+ )} + + ); + + case 2: + { + const hasPrimaryKey = fieldMappings.some((m) => m.primaryKey); + return ( +
+
+ {i18n('workspace.table.import.mode.description')} +
+ setImportMode(e.target.value)} + style={{ display: 'flex', flexDirection: 'column', gap: 12 }} + > + {i18n('workspace.table.import.mode.insert')} + + {i18n('workspace.table.import.mode.update')} + {!hasPrimaryKey && ( + + ({i18n('workspace.table.import.mode.pkRequired')}) + + )} + + + {i18n('workspace.table.import.mode.upsert')} + {!hasPrimaryKey && ( + + ({i18n('workspace.table.import.mode.pkRequired')}) + + )} + + {i18n('workspace.table.import.mode.insertIgnore')} + + {i18n('workspace.table.import.mode.delete')} + {!hasPrimaryKey && ( + + ({i18n('workspace.table.import.mode.pkRequired')}) + + )} + + {i18n('workspace.table.import.mode.replace')} + +
+ ); + } + + case 3: + return ( +
+
+
{i18n('workspace.table.import.progress.taskName')}: Import {params?.tableName}
+
+
+ {logs.map((log, index) => ( +
{log}
+ ))} +
+ 0 ? Math.min(importProgress, 100) : 0} status="active" /> +
+ {i18n('workspace.table.import.progress.rows')}: {importProgress} +
+
+ ); + + default: + return null; + } + }; + + // 获取底部按钮 + const getFooterButtons = () => { + if (currentStep === 0) { + return [ + , + , + ]; + } else if (currentStep === 1) { + return [ + , + , + ]; + } else if (currentStep === 2) { + const startButton = importMode === 'REPLACE' ? ( + + + + ) : ( + + ); + return [ + , + startButton, + ]; + } else { + return [ + , + ]; + } + }; + + return !!params && ( + <> + + + + + + + + + {renderStepContent()} + + + ); +}; + +export default ImportDataModal; diff --git a/chat2db-client/src/components/LayoutBasic/index.tsx b/chat2db-client/src/components/LayoutBasic/index.tsx index 0df9af234..b1d71e949 100644 --- a/chat2db-client/src/components/LayoutBasic/index.tsx +++ b/chat2db-client/src/components/LayoutBasic/index.tsx @@ -7,7 +7,7 @@ interface IProps{ function LayoutBasic(props: IProps) { return
-
+
; } diff --git a/chat2db-client/src/components/Loading/LazyLoading/index.tsx b/chat2db-client/src/components/Loading/LazyLoading/index.tsx index a0c3ef0f7..ae41ce461 100644 --- a/chat2db-client/src/components/Loading/LazyLoading/index.tsx +++ b/chat2db-client/src/components/Loading/LazyLoading/index.tsx @@ -7,8 +7,8 @@ interface IProps { className?: string; } -export default memo(function LazyLoading({ className }) { +export default memo(({ className }) => { return
- +
}) diff --git a/chat2db-client/src/components/Loading/Loading/index.tsx b/chat2db-client/src/components/Loading/Loading/index.tsx index a70f91b41..db3a3fd70 100644 --- a/chat2db-client/src/components/Loading/Loading/index.tsx +++ b/chat2db-client/src/components/Loading/Loading/index.tsx @@ -8,27 +8,27 @@ interface IProps { } // TODO: 首屏以及懒加载Loading效果 -export default memo(function PageLoading(props: IProps) { +export default memo((props: IProps) => { const { className } = props; return
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
diff --git a/chat2db-client/src/components/Loading/LoadingGracile/index.tsx b/chat2db-client/src/components/Loading/LoadingGracile/index.tsx index e7b0f178c..443b6eda8 100644 --- a/chat2db-client/src/components/Loading/LoadingGracile/index.tsx +++ b/chat2db-client/src/components/Loading/LoadingGracile/index.tsx @@ -7,21 +7,21 @@ interface IProps { className?: any; } -export default memo(function LoadingGracile(props: IProps) { +export default memo((props: IProps) => { const { className } = props; return
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
}); diff --git a/chat2db-client/src/components/Loading/LoadingLiquid/index.tsx b/chat2db-client/src/components/Loading/LoadingLiquid/index.tsx index f94238edd..38afbbf1c 100644 --- a/chat2db-client/src/components/Loading/LoadingLiquid/index.tsx +++ b/chat2db-client/src/components/Loading/LoadingLiquid/index.tsx @@ -7,17 +7,17 @@ interface IProps { className?: any; } -export default memo(function LoadingLiquid(props: IProps) { +export default memo((props: IProps) => { const { className } = props; return
- - - - - - - + + + + + + +
@@ -27,7 +27,8 @@ export default memo(function LoadingLiquid(props: IProps) { 0 1 0 0 0 0 0 1 0 0 0 0 0 20 -10 - " /> + " + />
diff --git a/chat2db-client/src/components/Modal/BaseModal/index.tsx b/chat2db-client/src/components/Modal/BaseModal/index.tsx index 351920654..249f8c108 100644 --- a/chat2db-client/src/components/Modal/BaseModal/index.tsx +++ b/chat2db-client/src/components/Modal/BaseModal/index.tsx @@ -52,7 +52,7 @@ const Modal = memo(() => { onCancel={() => { setOpen(false); }} - destroyOnClose={true} + destroyOnHidden={true} {...footer} > {modalData.content} diff --git a/chat2db-client/src/components/MonacoEditor/index.less b/chat2db-client/src/components/MonacoEditor/index.less index 28c012a4a..d2160ec24 100644 --- a/chat2db-client/src/components/MonacoEditor/index.less +++ b/chat2db-client/src/components/MonacoEditor/index.less @@ -1,3 +1,80 @@ .editorContainer { height: 100%; } + +.aiDiffWidget { + min-width: 360px; + max-width: min(760px, 70vw); + margin-top: 6px; + overflow: hidden; + color: var(--color-text, #1f2328); + font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; + font-size: 12px; + line-height: 18px; + background: var(--color-bg, #fff); + border: 1px solid var(--color-border-secondary, #d0d7de); + border-radius: 6px; + box-shadow: 0 8px 24px rgba(31, 35, 40, 0.12); +} + +.aiDiffHeader { + padding: 6px 10px; + color: var(--color-text-secondary, #57606a); + font-family: inherit; + font-size: 11px; + line-height: 16px; + background: var(--color-bg-subtle, #f6f8fa); + border-bottom: 1px solid var(--color-border-secondary, #d0d7de); +} + +.aiDiffBody { + max-height: 260px; + overflow: auto; +} + +.aiDiffRemoveLine, +.aiDiffAddLine, +.aiDiffSameLine { + display: grid; + grid-template-columns: 24px minmax(0, 1fr); + width: max-content; + min-width: 100%; + min-height: 20px; + white-space: pre; +} + +.aiDiffRemoveLine { + background: rgba(248, 81, 73, 0.12); +} + +.aiDiffAddLine { + background: rgba(46, 160, 67, 0.14); +} + +.aiDiffSameLine { + color: var(--color-text-secondary, #57606a); + background: transparent; +} + +.aiDiffMarker { + padding: 1px 8px; + user-select: none; + opacity: 0.78; +} + +.aiDiffCode { + min-width: 0; + padding: 1px 10px 1px 0; +} + +.aiDiffOriginalRange { + background: rgba(248, 81, 73, 0.12); +} + +.statementLocateHighlight { + background: rgba(24, 144, 255, 0.12); +} + +.statementErrorLineHighlight { + background: rgba(255, 77, 79, 0.18); +} diff --git a/chat2db-client/src/components/MonacoEditor/index.tsx b/chat2db-client/src/components/MonacoEditor/index.tsx index f8cebfa45..f4542061f 100644 --- a/chat2db-client/src/components/MonacoEditor/index.tsx +++ b/chat2db-client/src/components/MonacoEditor/index.tsx @@ -5,6 +5,8 @@ import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; import { DatabaseTypeCode, EditorThemeType } from '@/constants'; import { editorDefaultOptions } from './monacoEditorConfig'; import { IQuickInputService } from 'monaco-editor/esm/vs/platform/quickinput/common/quickInput'; +import { IBoundInfo } from '@/typings'; +import { initSqlAutocomplete, ISqlAutocompleteDisposable } from './syntax-parser/plugin/monaco-plugin/sql-autocomplete'; import styles from './index.less'; @@ -17,12 +19,197 @@ export type IAppendValue = { range?: IRangeType; }; +export interface ISelectedContentInfo { + text: string; + startLine: number; + endLine: number; +} + const databaseTypeList = Object.keys(DatabaseTypeCode).map((d) => ({ type: d, id: d, label: d, })); +const vinRegExp = /^[A-HJ-NPR-Z0-9]{17}$/i; +type SqlQuote = "'" | '"' | '`' | '['; + +const getFirstExcelColumn = (line: string) => { + const [firstColumn] = line.split('\t'); + return firstColumn.trim().replace(/^["']|["']$/g, ''); +}; + +// Converts Excel-copied VIN rows into SQL string literals for IN clauses. +const buildVinSqlValueList = (clipboardText: string) => { + const vinList = clipboardText + .split(/\r\n|\r|\n/) + .map(getFirstExcelColumn) + .filter(Boolean); + const invalidVinList = vinList.filter((vin) => !vinRegExp.test(vin)); + + if (!vinList.length || invalidVinList.length) { + return null; + } + + return vinList.map((vin) => `'${vin.toUpperCase().replace(/'/g, "''")}'`).join(', '); +}; + +// Finds the innermost unclosed parenthesis before the cursor while ignoring SQL strings and comments. +const getOpenInParenOffset = (sql: string, cursorOffset: number) => { + const parenStack: number[] = []; + let quote: SqlQuote | null = null; + let inLineComment = false; + let inBlockComment = false; + + for (let index = 0; index < cursorOffset; index++) { + const char = sql[index]; + const next = sql[index + 1]; + + if (inLineComment) { + if (char === '\n' || char === '\r') { + inLineComment = false; + } + continue; + } + + if (inBlockComment) { + if (char === '*' && next === '/') { + inBlockComment = false; + index++; + } + continue; + } + + if (quote) { + if (quote === "'" && char === "'" && next === "'") { + index++; + continue; + } + if (quote === '"' && char === '"' && next === '"') { + index++; + continue; + } + if (quote === '`' && char === '`' && next === '`') { + index++; + continue; + } + if ( + (quote === "'" && char === "'") || + (quote === '"' && char === '"') || + (quote === '`' && char === '`') || + (quote === '[' && char === ']') + ) { + quote = null; + } + continue; + } + + if (char === '-' && next === '-') { + inLineComment = true; + index++; + continue; + } + + if (char === '/' && next === '*') { + inBlockComment = true; + index++; + continue; + } + + if (char === "'" || char === '"' || char === '`' || char === '[') { + quote = char as SqlQuote; + continue; + } + + if (char === '(') { + parenStack.push(index); + continue; + } + + if (char === ')') { + parenStack.pop(); + } + } + + return parenStack[parenStack.length - 1]; +}; + +const isCursorInsideInParens = (editor: IEditorIns) => { + const model = editor.getModel(); + const position = editor.getPosition(); + if (!model || !position) { + return false; + } + + const sql = model.getValue(); + const cursorOffset = model.getOffsetAt(position); + const openParenOffset = getOpenInParenOffset(sql, cursorOffset); + if (openParenOffset === undefined) { + return false; + } + + const beforeOpenParen = sql.slice(0, openParenOffset); + return /\bin\s*$/i.test(beforeOpenParen); +}; + +export const registerVinPasteTransform = (editor: IEditorIns) => { + const domNode = editor.getDomNode(); + if (!domNode) { + return undefined; + } + + const pasteHandler = (event: ClipboardEvent) => { + const eventTarget = event.target; + if (!(eventTarget instanceof Node) || !domNode.contains(eventTarget)) { + return; + } + + const clipboardText = event.clipboardData?.getData('text/plain') || ''; + const vinSqlValueList = buildVinSqlValueList(clipboardText); + const isInsideInParens = isCursorInsideInParens(editor); + if (!vinSqlValueList || !isInsideInParens) { + return; + } + + const position = editor.getPosition(); + const selection = + editor.getSelection() || + (position + ? new monaco.Selection(position.lineNumber, position.column, position.lineNumber, position.column) + : null); + if (!selection) { + return; + } + + event.preventDefault(); + event.stopPropagation(); + + const endPosition = { + lineNumber: selection.startLineNumber, + column: selection.startColumn + vinSqlValueList.length, + }; + editor.executeEdits( + 'vinPasteTransform', + [ + { + range: selection, + text: vinSqlValueList, + forceMoveMarkers: true, + }, + ], + () => [ + new monaco.Selection(endPosition.lineNumber, endPosition.column, endPosition.lineNumber, endPosition.column), + ], + ); + editor.focus(); + }; + + document.addEventListener('paste', pasteHandler, true); + return () => { + document.removeEventListener('paste', pasteHandler, true); + }; +}; + interface IProps { id: string; language?: string; @@ -35,12 +222,23 @@ interface IProps { didMount?: (editor: IEditorIns) => any; shortcutKey?: (editor, monaco, isActive: boolean) => void; focusChange?: (isActive: boolean) => void; + boundInfo?: IBoundInfo; + aiCompletion?: { + id: string; + text: string; + range: monaco.IRange; + replaceRange?: monaco.IRange; + originalText?: string; + } | null; + onContentChange?: (event: IEditorContentChangeEvent) => void; } export interface IExportRefFunction { getCurrentSelectContent: () => string; + getCurrentSelectInfo: () => ISelectedContentInfo | null; getAllContent: () => string; setValue: (text: any, range?: IRangeType) => void; + locateStatement: (startLine: number, endLine?: number, errorLine?: number) => void; // toFocus: () => void; } @@ -54,9 +252,16 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { defaultValue, appendValue, shortcutKey, + boundInfo, + aiCompletion, + onContentChange, } = props; const editorRef = useRef(); const quickInputCommand = useRef(); + const sqlAutocompleteDisposable = useRef(null); + const aiCompletionWidgetRef = useRef(null); + const aiCompletionDecorationsRef = useRef([]); + const statementDecorationsRef = useRef([]); const [appTheme] = useTheme(); const [isActive, setIsActive] = React.useState(false); @@ -100,8 +305,24 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { }); createAction(editorIns); + editorIns.onDidChangeModelContent((event) => { + onContentChange?.(event); + }); + + // Initialize SQL autocomplete if boundInfo is provided + if (boundInfo && language === 'sql') { + sqlAutocompleteDisposable.current = initSqlAutocomplete({ + monaco, + editor: editorIns, + boundInfo, + }); + } return () => { + if (sqlAutocompleteDisposable.current) { + sqlAutocompleteDisposable.current.dispose(); + } + clearAiCompletionPreview(editorRef.current); if (props.needDestroy) { editorRef.current && editorRef.current.dispose(); } @@ -127,7 +348,6 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { // }; }, []); - useEffect(() => { if (editorRef.current) { // eg: @@ -137,6 +357,14 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { } }, [editorRef.current, isActive]); + useEffect(() => { + if (!editorRef.current) { + return; + } + + renderAiCompletionPreview(editorRef.current, aiCompletion || null); + }, [aiCompletion]); + useEffect(() => { // 监听浏览器窗口大小变化,重新渲染编辑器 const resize = () => { @@ -155,10 +383,37 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { } }, [options?.theme]); + // Reinitialize SQL autocomplete when boundInfo changes + useEffect(() => { + if (!boundInfo || language !== 'sql' || !editorRef.current) { + return; + } + + // Dispose old autocomplete + if (sqlAutocompleteDisposable.current) { + sqlAutocompleteDisposable.current.dispose(); + } + + // Initialize new autocomplete + sqlAutocompleteDisposable.current = initSqlAutocomplete({ + monaco, + editor: editorRef.current, + boundInfo, + }); + + return () => { + if (sqlAutocompleteDisposable.current) { + sqlAutocompleteDisposable.current.dispose(); + } + }; + }, [boundInfo, language]); + useImperativeHandle(ref, () => ({ getCurrentSelectContent, + getCurrentSelectInfo, getAllContent, setValue, + locateStatement, // toFocus, })); @@ -172,6 +427,40 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { appendMonacoValue(editorRef.current, text, range); }; + const locateStatement = (startLine: number, endLine?: number, errorLine?: number) => { + const editor = editorRef.current; + const model = editor?.getModel(); + if (!editor || !model || !startLine) { + return; + } + const modelLastLine = model.getLineCount(); + const safeStartLine = Math.max(1, Math.min(startLine, modelLastLine)); + const safeEndLine = Math.max(safeStartLine, Math.min(endLine || safeStartLine, modelLastLine)); + const targetLine = Math.max(safeStartLine, Math.min(errorLine || safeStartLine, safeEndLine)); + const endColumn = model.getLineMaxColumn(safeEndLine); + const range = new monaco.Range(safeStartLine, 1, safeEndLine, endColumn); + + statementDecorationsRef.current = editor.deltaDecorations(statementDecorationsRef.current, [ + { + range, + options: { + isWholeLine: true, + className: styles.statementLocateHighlight, + }, + }, + { + range: new monaco.Range(targetLine, 1, targetLine, model.getLineMaxColumn(targetLine)), + options: { + isWholeLine: true, + className: styles.statementErrorLineHighlight, + }, + }, + ]); + editor.setSelection(range); + editor.revealLineInCenterIfOutsideViewport(targetLine); + editor.focus(); + }; + // const toFocus = () => { // editorRef.current?.focus(); // }; @@ -181,13 +470,25 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { * @returns */ const getCurrentSelectContent = () => { + return getCurrentSelectInfo()?.text || ''; + }; + + const getCurrentSelectInfo = (): ISelectedContentInfo | null => { const selection = editorRef.current?.getSelection(); if (!selection || selection.isEmpty()) { - return ''; - } else { - const selectedText = editorRef.current?.getModel()?.getValueInRange(selection); - return selectedText || ''; + return null; } + + const selectedText = editorRef.current?.getModel()?.getValueInRange(selection); + if (!selectedText) { + return null; + } + + return { + text: selectedText, + startLine: selection.getStartPosition().lineNumber, + endLine: selection.getEndPosition().lineNumber, + }; }; /** 获取文本所有内容 */ @@ -232,6 +533,169 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { }); }; + const clearAiCompletionPreview = (editor?: IEditorIns) => { + if (!editor) { + return; + } + + if (aiCompletionWidgetRef.current) { + editor.removeContentWidget(aiCompletionWidgetRef.current); + aiCompletionWidgetRef.current = null; + } + aiCompletionDecorationsRef.current = editor.deltaDecorations(aiCompletionDecorationsRef.current, []); + }; + + type AiDiffLineType = 'same' | 'remove' | 'add'; + + interface IAiDiffLine { + type: AiDiffLineType; + line: string; + } + + const normalizeSqlDiffLine = (line: string) => { + const compactLine = line.trim().replace(/\s+/g, ' '); + return compactLine.toLowerCase(); + }; + + const buildLineDiff = (originalLines: string[], suggestionLines: string[]) => { + const normalizedOriginalLines = originalLines.map(normalizeSqlDiffLine); + const normalizedSuggestionLines = suggestionLines.map(normalizeSqlDiffLine); + const rowCount = originalLines.length + 1; + const columnCount = suggestionLines.length + 1; + const lcsLengths = Array.from({ length: rowCount }, () => Array(columnCount).fill(0)); + + for (let rowIndex = originalLines.length - 1; rowIndex >= 0; rowIndex -= 1) { + for (let columnIndex = suggestionLines.length - 1; columnIndex >= 0; columnIndex -= 1) { + if (normalizedOriginalLines[rowIndex] === normalizedSuggestionLines[columnIndex]) { + lcsLengths[rowIndex][columnIndex] = lcsLengths[rowIndex + 1][columnIndex + 1] + 1; + } else { + lcsLengths[rowIndex][columnIndex] = Math.max( + lcsLengths[rowIndex + 1][columnIndex], + lcsLengths[rowIndex][columnIndex + 1], + ); + } + } + } + + const diffLines: IAiDiffLine[] = []; + let originalIndex = 0; + let suggestionIndex = 0; + while (originalIndex < originalLines.length && suggestionIndex < suggestionLines.length) { + if (normalizedOriginalLines[originalIndex] === normalizedSuggestionLines[suggestionIndex]) { + diffLines.push({ type: 'same', line: originalLines[originalIndex] }); + originalIndex += 1; + suggestionIndex += 1; + } else if (lcsLengths[originalIndex + 1][suggestionIndex] >= lcsLengths[originalIndex][suggestionIndex + 1]) { + diffLines.push({ type: 'remove', line: originalLines[originalIndex] }); + originalIndex += 1; + } else { + diffLines.push({ type: 'add', line: suggestionLines[suggestionIndex] }); + suggestionIndex += 1; + } + } + + while (originalIndex < originalLines.length) { + diffLines.push({ type: 'remove', line: originalLines[originalIndex] }); + originalIndex += 1; + } + + while (suggestionIndex < suggestionLines.length) { + diffLines.push({ type: 'add', line: suggestionLines[suggestionIndex] }); + suggestionIndex += 1; + } + + return diffLines; + }; + + const getRemovedLineDecorations = (replaceRange: monaco.IRange, diffLines: IAiDiffLine[]) => { + const decorations: monaco.editor.IModelDeltaDecoration[] = []; + let lineNumber = replaceRange.startLineNumber; + + diffLines.forEach((diffLine) => { + if (diffLine.type === 'add') { + return; + } + + if (diffLine.type === 'remove') { + decorations.push({ + range: new monaco.Range(lineNumber, 1, lineNumber, 1), + options: { + className: styles.aiDiffOriginalRange, + isWholeLine: true, + }, + }); + } + + lineNumber += 1; + }); + + return decorations; + }; + + const createDiffLine = (line: string, type: AiDiffLineType) => { + const row = document.createElement('div'); + row.className = + type === 'remove' ? styles.aiDiffRemoveLine : type === 'add' ? styles.aiDiffAddLine : styles.aiDiffSameLine; + + const markerNode = document.createElement('span'); + markerNode.className = styles.aiDiffMarker; + markerNode.textContent = type === 'remove' ? '-' : type === 'add' ? '+' : ' '; + + const codeNode = document.createElement('span'); + codeNode.className = styles.aiDiffCode; + codeNode.textContent = line || ' '; + + row.appendChild(markerNode); + row.appendChild(codeNode); + return row; + }; + + const renderAiCompletionPreview = (editor: IEditorIns, completion: IProps['aiCompletion']) => { + clearAiCompletionPreview(editor); + if (!completion) { + return; + } + + const replaceRange = completion.replaceRange || completion.range; + const widgetNode = document.createElement('div'); + widgetNode.className = styles.aiDiffWidget; + widgetNode.tabIndex = -1; + + const headerNode = document.createElement('div'); + headerNode.className = styles.aiDiffHeader; + headerNode.textContent = 'AI SQL suggestion'; + widgetNode.appendChild(headerNode); + + const bodyNode = document.createElement('div'); + bodyNode.className = styles.aiDiffBody; + + const originalLines = (completion.originalText || '').split(/\r?\n/); + const suggestionLines = completion.text.split(/\r?\n/); + const diffLines = buildLineDiff(originalLines, suggestionLines); + diffLines.forEach(({ line, type }) => bodyNode.appendChild(createDiffLine(line, type))); + widgetNode.appendChild(bodyNode); + + const widget: monaco.editor.IContentWidget = { + getId: () => `chat2db.aiDiff.${completion.id}`, + getDomNode: () => widgetNode, + getPosition: () => ({ + position: { + lineNumber: replaceRange.endLineNumber, + column: replaceRange.endColumn, + }, + preference: [monaco.editor.ContentWidgetPositionPreference.BELOW], + }), + }; + + aiCompletionWidgetRef.current = widget; + editor.addContentWidget(widget); + aiCompletionDecorationsRef.current = editor.deltaDecorations( + aiCompletionDecorationsRef.current, + getRemovedLineDecorations(replaceRange, diffLines), + ); + editor.revealLineInCenterIfOutsideViewport(replaceRange.endLineNumber); + }; + return
; } diff --git a/chat2db-client/src/components/MonacoEditor/syntax-parser/parser/utils.ts b/chat2db-client/src/components/MonacoEditor/syntax-parser/parser/utils.ts index 5f64859ef..86b2236cd 100644 --- a/chat2db-client/src/components/MonacoEditor/syntax-parser/parser/utils.ts +++ b/chat2db-client/src/components/MonacoEditor/syntax-parser/parser/utils.ts @@ -34,10 +34,42 @@ export function tailCallOptimize(f: T): T { } as any; } +function findNearestTokenBeforeCursor(obj: any, cursorIndex: number, path?: string): { path: string; token: any; distance: number } | null { + // eslint-disable-next-line no-param-reassign + path = path || ''; + let nearest: { path: string; token: any; distance: number } | null = null; + + // eslint-disable-next-line guard-for-in + for (const key in obj) { + if (obj[key] && obj[key].token === true && obj[key].position) { + const tokenEnd = obj[key].position[1] + 1; + if (tokenEnd <= cursorIndex) { + const distance = cursorIndex - tokenEnd; + if (!nearest || distance < nearest.distance) { + nearest = { + path: path === '' ? key : `${path}.${key}`, + token: obj[key], + distance, + }; + } + } + } + if (typeof obj[key] === 'object' && obj[key] !== null) { + const childNearest = findNearestTokenBeforeCursor(obj[key], cursorIndex, path === '' ? key : `${path}.${key}`); + if (childNearest && (!nearest || childNearest.distance < nearest.distance)) { + nearest = childNearest; + } + } + } + return nearest; +} + export function getPathByCursorIndexFromAst(obj: any, cursorIndex: number, path?: string) { // eslint-disable-next-line no-param-reassign path = path || ''; let fullpath = ''; + + // 首先尝试找到光标所在的 token // eslint-disable-next-line guard-for-in for (const key in obj) { if ( @@ -51,9 +83,20 @@ export function getPathByCursorIndexFromAst(obj: any, cursorIndex: number, path? } return `${path}.${key}`; } - if (typeof obj[key] === 'object') { + if (typeof obj[key] === 'object' && obj[key] !== null) { fullpath = getPathByCursorIndexFromAst(obj[key], cursorIndex, path === '' ? key : `${path}.${key}`) || fullpath; } } + + // 如果找不到光标所在的 token,尝试找最近的 token + if (!fullpath) { + const nearest = findNearestTokenBeforeCursor(obj, cursorIndex, path); + if (nearest && nearest.distance <= 5) { + // 如果光标距离最近的 token 结束位置不超过 5 个字符,返回该路径 + // 这可以帮助处理光标在空格后面的情况 + return nearest.path; + } + } + return fullpath; } diff --git a/chat2db-client/src/components/MonacoEditor/syntax-parser/plugin/monaco-plugin/default-opts.ts b/chat2db-client/src/components/MonacoEditor/syntax-parser/plugin/monaco-plugin/default-opts.ts index de7629d34..12a4d930f 100644 --- a/chat2db-client/src/components/MonacoEditor/syntax-parser/plugin/monaco-plugin/default-opts.ts +++ b/chat2db-client/src/components/MonacoEditor/syntax-parser/plugin/monaco-plugin/default-opts.ts @@ -2,7 +2,7 @@ /* eslint-disable no-useless-constructor */ import * as _ from 'lodash'; import { IMatching, IParseResult } from '../..'; -import { ITableInfo, ICompletionItem, IStatement, ICursorInfo } from '../sql-parser'; +import { ITableInfo, IJoinTableInfo, ICompletionItem, IStatement, ICursorInfo } from '../sql-parser'; export type IMonacoVersion = '0.13.2' | '0.15.6'; @@ -29,7 +29,21 @@ export class DefaultOpts { return { label: name, insertText: name, - sortText: `A${name}`, + sortText: `Z${name}`, // 表名排在最后(Z 开头) + kind: this.monaco.languages.CompletionItemKind.Folder, + }; + }), + ); + }; + + public onSuggestJoinTables?: (joinInfo?: ICursorInfo) => Promise = joinInfo => { + // 默认实现:返回所有表(子类可以覆盖) + return Promise.resolve( + ['dt', 'b2b', 'tmall'].map(name => { + return { + label: name, + insertText: name, + sortText: `Z${name}`, // 表名排在最后(Z 开头) kind: this.monaco.languages.CompletionItemKind.Folder, }; }), @@ -62,6 +76,10 @@ export class DefaultOpts { .filter(matching => { return matching.type === 'string'; }) + .filter(matching => { + // 过滤掉空值或 cursor + return matching.value && matching.value.toString().trim() !== ''; + }) .map(matching => { const value = /[a-zA-Z]+/.test(matching.value.toString()) ? _.upperCase(matching.value.toString()) @@ -72,7 +90,7 @@ export class DefaultOpts { documentation: 'documentation', detail: 'detail', kind: this.monaco.languages.CompletionItemKind.Keyword, - sortText: `W${matching.value}`, + sortText: `W${value}`, }; }); }; diff --git a/chat2db-client/src/components/MonacoEditor/syntax-parser/plugin/monaco-plugin/dialects/index.ts b/chat2db-client/src/components/MonacoEditor/syntax-parser/plugin/monaco-plugin/dialects/index.ts new file mode 100644 index 000000000..f66b28883 --- /dev/null +++ b/chat2db-client/src/components/MonacoEditor/syntax-parser/plugin/monaco-plugin/dialects/index.ts @@ -0,0 +1,152 @@ +import { DatabaseTypeCode } from '@/constants'; +import mysql from '@/constants/IntelliSense/mysql'; +import oracle from '@/constants/IntelliSense/oracle'; +import postgresql from '@/constants/IntelliSense/pgsql'; +import redis from '@/constants/IntelliSense/redis'; +import sqlserver from '@/constants/IntelliSense/sqlserver'; +import { ILegacyDialectCompletion, ISqlDialectCompletion } from './types'; + +const genericSqlCompletion: ISqlDialectCompletion = { + type: 'GENERIC', + keywords: [ + 'SELECT', + 'FROM', + 'WHERE', + 'JOIN', + 'LEFT JOIN', + 'RIGHT JOIN', + 'INNER JOIN', + 'GROUP BY', + 'ORDER BY', + 'HAVING', + 'LIMIT', + 'AND', + 'OR', + 'NOT', + 'IN', + 'EXISTS', + 'BETWEEN', + 'LIKE', + 'AS', + 'DISTINCT', + 'NULL', + 'IS NULL', + 'IS NOT NULL', + 'ASC', + 'DESC', + 'UNION', + 'UNION ALL', + 'CASE', + 'WHEN', + 'THEN', + 'ELSE', + 'END', + 'WITH', + ], + functions: [ + 'COUNT', + 'SUM', + 'AVG', + 'MAX', + 'MIN', + 'CAST', + 'CONVERT', + 'TRIM', + 'SUBSTRING', + 'LENGTH', + 'UPPER', + 'LOWER', + 'CONCAT', + 'REPLACE', + 'COALESCE', + 'NULLIF', + 'ABS', + 'CEIL', + 'FLOOR', + 'ROUND', + 'NOW', + 'CURRENT_DATE', + 'CURRENT_TIME', + 'CURRENT_TIMESTAMP', + ], + dataTypes: [], + variables: [], + operators: [], +}; + +const legacyDialectCompletions = [mysql, postgresql, oracle, sqlserver, redis] as ILegacyDialectCompletion[]; + +const mysqlCompatibleTypes = new Set([ + DatabaseTypeCode.MYSQL, + DatabaseTypeCode.MARIADB, + DatabaseTypeCode.H2, + DatabaseTypeCode.OCEANBASE, + 'TIDB', +]); + +const postgresqlCompatibleTypes = new Set([DatabaseTypeCode.POSTGRESQL, DatabaseTypeCode.KINGBASE, 'GAUSS']); + +const sqlserverCompatibleTypes = new Set([DatabaseTypeCode.SQLSERVER, 'SYBASE']); + +const oracleCompatibleTypes = new Set([DatabaseTypeCode.ORACLE, DatabaseTypeCode.DM, 'DAMENG']); + +const normalizeWords = (words?: string[]) => { + const normalizedMap = new Map(); + + (words || []).forEach((word) => { + const trimmedWord = word?.trim(); + if (!trimmedWord) { + return; + } + + const normalizedKey = trimmedWord.toUpperCase(); + if (!normalizedMap.has(normalizedKey)) { + normalizedMap.set(normalizedKey, trimmedWord); + } + }); + + return Array.from(normalizedMap.values()); +}; + +const normalizeLegacyCompletion = (completion: ILegacyDialectCompletion): ISqlDialectCompletion => { + return { + type: completion.type, + keywords: normalizeWords(completion.keywords), + functions: normalizeWords(completion.functions), + dataTypes: [], + variables: [], + operators: [], + }; +}; + +const findLegacyCompletion = (databaseType?: string) => { + const normalizedType = databaseType?.toUpperCase(); + + if (mysqlCompatibleTypes.has(normalizedType || '')) { + return mysql; + } + + if (postgresqlCompatibleTypes.has(normalizedType || '')) { + return postgresql; + } + + if (sqlserverCompatibleTypes.has(normalizedType || '')) { + return sqlserver; + } + + if (oracleCompatibleTypes.has(normalizedType || '')) { + return oracle; + } + + return legacyDialectCompletions.find((completion) => completion.type === normalizedType); +}; + +export const getDialectCompletion = (databaseType?: DatabaseTypeCode | string): ISqlDialectCompletion => { + const legacyCompletion = findLegacyCompletion(databaseType); + + if (legacyCompletion) { + return normalizeLegacyCompletion(legacyCompletion); + } + + return genericSqlCompletion; +}; diff --git a/chat2db-client/src/components/MonacoEditor/syntax-parser/plugin/monaco-plugin/dialects/types.ts b/chat2db-client/src/components/MonacoEditor/syntax-parser/plugin/monaco-plugin/dialects/types.ts new file mode 100644 index 000000000..79920ce6f --- /dev/null +++ b/chat2db-client/src/components/MonacoEditor/syntax-parser/plugin/monaco-plugin/dialects/types.ts @@ -0,0 +1,16 @@ +import { DatabaseTypeCode } from '@/constants'; + +export interface ILegacyDialectCompletion { + type: DatabaseTypeCode; + keywords?: string[]; + functions?: string[]; +} + +export interface ISqlDialectCompletion { + type: DatabaseTypeCode | 'GENERIC'; + keywords: string[]; + functions: string[]; + dataTypes: string[]; + variables: string[]; + operators: string[]; +} diff --git a/chat2db-client/src/components/MonacoEditor/syntax-parser/plugin/monaco-plugin/index.ts b/chat2db-client/src/components/MonacoEditor/syntax-parser/plugin/monaco-plugin/index.ts index 3906d011a..37bdcc58d 100644 --- a/chat2db-client/src/components/MonacoEditor/syntax-parser/plugin/monaco-plugin/index.ts +++ b/chat2db-client/src/components/MonacoEditor/syntax-parser/plugin/monaco-plugin/index.ts @@ -6,10 +6,12 @@ import * as _ from 'lodash'; import { IParseResult } from '../..'; import { DefaultOpts, IMonacoVersion, IParserType } from './default-opts'; -import * as MyWorker from './parser.worker'; +import { createParserWorker } from './worker-factory'; +import { mysqlParser } from '../sql-parser'; import { ICompletionItem, ITableInfo, + IJoinTableInfo, reader, ICursorInfo, } from '../sql-parser'; @@ -31,94 +33,43 @@ export function monacoSqlAutocomplete( ); } + // Register completion provider // Get parser info and show error. let currentParserPromise: any = null; let editVersion = 0; - - editor.onDidChangeModelContent((event: any) => { - editVersion++; - const currentEditVersion = editVersion; - - currentParserPromise = new Promise((resolve) => { - setTimeout(() => { - const model = editor.getModel(); - - asyncParser( - editor.getValue(), - model.getOffsetAt(editor.getPosition()), - opts.parserType, - ).then((parseResult) => { - resolve(parseResult); - - if (currentEditVersion !== editVersion) { - return; - } - - opts.onParse(parseResult); - - if (parseResult.error) { - const newReason = - parseResult.error.reason === 'incomplete' - ? `Incomplete, expect next input: \n${parseResult.error.suggestions - .map((each: any) => { - return each.value; - }) - .join('\n')}` - : `Wrong input, expect: \n${parseResult.error.suggestions - .map((each: any) => { - return each.value; - }) - .join('\n')}`; - - const errorPosition = parseResult.error.token - ? { - startLineNumber: model.getPositionAt( - parseResult.error.token.position[0], - ).lineNumber, - startColumn: model.getPositionAt( - parseResult.error.token.position[0], - ).column, - endLineNumber: model.getPositionAt( - parseResult.error.token.position[1], - ).lineNumber, - endColumn: - model.getPositionAt(parseResult.error.token.position[1]) - .column + 1, - } - : { - startLineNumber: 0, - startColumn: 0, - endLineNumber: 0, - endColumn: 0, - }; - - model.getPositionAt(parseResult.error.token); - - monaco.editor.setModelMarkers(model, opts.language, [ - { - ...errorPosition, - message: newReason, - severity: getSeverityByVersion( - monaco, - opts.monacoEditorVersion, - ), - }, - ]); - } else { - monaco.editor.setModelMarkers(editor.getModel(), opts.language, []); - } - }); - }); - }); - }); - - monaco.languages.registerCompletionItemProvider(opts.language, { + + // Initialize parser promise with current editor content + const initParserPromise = () => { + const model = editor.getModel(); + if (model) { + currentParserPromise = asyncParser( + editor.getValue(), + model.getOffsetAt(editor.getPosition()), + opts.parserType, + ); + } + }; + + // Initialize on first load + initParserPromise(); + + const completionProvider = monaco.languages.registerCompletionItemProvider(opts.language, { triggerCharacters: ' $.:{}=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''), provideCompletionItems: async () => { const currentEditVersion = editVersion; + + // If currentParserPromise is null, initialize it + if (!currentParserPromise) { + initParserPromise(); + } + const parseResult: IParseResult = await currentParserPromise; + if (!parseResult) { + return returnCompletionItemsByVersion([], opts.monacoEditorVersion); + } + if (currentEditVersion !== editVersion) { return returnCompletionItemsByVersion([], opts.monacoEditorVersion); } @@ -130,6 +81,27 @@ export function monacoSqlAutocomplete( const parserSuggestion = opts.pipeKeywords(parseResult.nextMatchings); + // 当 cursorInfo 为 null 但解析失败时,尝试从 SELECT 语句获取字段 + if (!cursorInfo && parseResult.ast && parseResult.error) { + const fallbackFields = await reader.getFieldsFromStatement( + parseResult.ast, + [], // 空的 cursorKeyPath + opts.onSuggestTableFields, + ); + + if (fallbackFields && fallbackFields.length > 0) { + const uniqueFallbackFields = _.uniqBy(fallbackFields, 'label'); + const functionNames = await opts.onSuggestFunctionName(''); + const result = uniqueFallbackFields.concat(functionNames).concat(parserSuggestion); + return returnCompletionItemsByVersion(result, opts.monacoEditorVersion); + } + + return returnCompletionItemsByVersion( + parserSuggestion, + opts.monacoEditorVersion, + ); + } + if (!cursorInfo) { return returnCompletionItemsByVersion( parserSuggestion, @@ -145,9 +117,12 @@ export function monacoSqlAutocomplete( opts.onSuggestTableFields, ); + // 去重字段(避免重复) + const uniqueFields = _.uniqBy(cursorRootStatementFields, 'label'); + // group.fieldName const groups = _.groupBy( - cursorRootStatementFields.filter((cursorRootStatementField) => { + uniqueFields.filter((cursorRootStatementField) => { return cursorRootStatementField.groupPickerName !== null; }), 'groupPickerName', @@ -157,19 +132,22 @@ export function monacoSqlAutocomplete( cursorInfo.token.value, ); + const result = uniqueFields + .concat(functionNames) // 函数名 + .concat(parserSuggestion) // SQL 关键字 + .concat( + groups + ? Object.keys(groups).map((groupName) => { + return opts.onSuggestFieldGroup(groupName); + }) + : [], + ); + return returnCompletionItemsByVersion( - cursorRootStatementFields - .concat(parserSuggestion) - .concat(functionNames) - .concat( - groups - ? Object.keys(groups).map((groupName) => { - return opts.onSuggestFieldGroup(groupName); - }) - : [], - ), + result, opts.monacoEditorVersion, ); + case 'tableFieldAfterGroup': // 字段 . 后面的部分 const cursorRootStatementFieldsAfter = @@ -179,17 +157,41 @@ export function monacoSqlAutocomplete( opts.onSuggestTableFields, ); + // 去重并过滤 + const uniqueFieldsAfter = _.uniqBy(cursorRootStatementFieldsAfter, 'label'); + const filteredFields = uniqueFieldsAfter + .filter((cursorRootStatementField: any) => { + return ( + cursorRootStatementField.groupPickerName === + (cursorInfo as ICursorInfo<{ groupName: string }>).groupName + ); + }) + .filter((field: any) => { + // 过滤掉无效的补全项 + return field && field.label && field.label.trim() !== '' && field.insertText && field.insertText.trim() !== ''; + }); + + // 字段排在最前面,关键字排在后面 + const sortedFields = [ + ...filteredFields, // 字段(sortText: B*) + ...parserSuggestion.filter(item => item.insertText && item.insertText.trim() !== ''), // SQL 关键字(sortText: W*) + ]; + return returnCompletionItemsByVersion( - cursorRootStatementFieldsAfter - .filter((cursorRootStatementField: any) => { - return ( - cursorRootStatementField.groupPickerName === - (cursorInfo as ICursorInfo<{ groupName: string }>).groupName - ); - }) - .concat(parserSuggestion), + sortedFields, opts.monacoEditorVersion, ); + + case 'joinTable': + const joinTableNames = await opts.onSuggestJoinTables( + cursorInfo as ICursorInfo, + ); + + return returnCompletionItemsByVersion( + joinTableNames.concat(parserSuggestion), + opts.monacoEditorVersion, + ); + case 'tableName': const tableNames = await opts.onSuggestTableNames( cursorInfo as ICursorInfo, @@ -206,11 +208,100 @@ export function monacoSqlAutocomplete( parserSuggestion, opts.monacoEditorVersion, ); - } +} }, }); + + // Listen for content changes and update parser promise + editor.onDidChangeModelContent((event: any) => { + editVersion++; + const currentEditVersion = editVersion; + + currentParserPromise = new Promise((resolve) => { + setTimeout(() => { + const model = editor.getModel(); + + asyncParser( + editor.getValue(), + model.getOffsetAt(editor.getPosition()), + opts.parserType, + ).then((parseResult) => { + resolve(parseResult); + + if (currentEditVersion !== editVersion) { + return; + } + + opts.onParse(parseResult); - monaco.languages.registerHoverProvider(opts.language, { + if (parseResult.error) { + // Check if input only contains comments and whitespace + // Handle both \n and \r\n line endings, and use [\s\S] to match all characters including \r + const textWithoutComments = editor.getValue().replace(/((?:#|--)[\s\S]*?(?:\r?\n|$)|\/\*[\s\S]*?(?:\*\/|$))/g, '').trim(); + + // Only show error if there's actual SQL content (not just comments) + if (textWithoutComments.length > 0) { + const newReason = + parseResult.error.reason === 'incomplete' + ? `Incomplete, expect next input: \n${parseResult.error.suggestions + .map((each: any) => { + return each.value; + }) + .join('\n')}` + : `Wrong input, expect: \n${parseResult.error.suggestions + .map((each: any) => { + return each.value; + }) + .join('\n')}`; + + const errorPosition = parseResult.error.token + ? { + startLineNumber: model.getPositionAt( + parseResult.error.token.position[0], + ).lineNumber, + startColumn: model.getPositionAt( + parseResult.error.token.position[0], + ).column, + endLineNumber: model.getPositionAt( + parseResult.error.token.position[1], + ).lineNumber, + endColumn: + model.getPositionAt(parseResult.error.token.position[1]) + .column + 1, + } + : { + startLineNumber: 0, + startColumn: 0, + endLineNumber: 0, + endColumn: 0, + }; + + model.getPositionAt(parseResult.error.token); + + monaco.editor.setModelMarkers(model, opts.language, [ + { + ...errorPosition, + message: newReason, + severity: getSeverityByVersion( + monaco, + opts.monacoEditorVersion, + ), + }, + ]); + } else { + // Clear error markers for comment-only input + monaco.editor.setModelMarkers(editor.getModel(), opts.language, []); + } + } else { + monaco.editor.setModelMarkers(editor.getModel(), opts.language, []); + } + }); + }); + }); + }); + + // Register hover provider and store it for disposal + const hoverProviderDisposable = monaco.languages.registerHoverProvider(opts.language, { provideHover: async (model: any, position: any) => { const parseResult: IParseResult = await asyncParser( editor.getValue(), @@ -272,42 +363,80 @@ export function monacoSqlAutocomplete( }; }, }); + + // Return disposable object + return { + dispose: () => { + completionProvider.dispose(); + hoverProviderDisposable.dispose(); + }, + }; } // 实例化一个 worker -const worker: Worker = new (MyWorker as any)(); +// 注意:开发环境下使用同步解析避免 HMR 问题 +// let worker: Worker | null = null; +// try { +// worker = createParserWorker(); +// } catch (error) { +// console.warn('Failed to create worker, will use fallback:', error); +// } +const worker: Worker | null = null; // 暂时禁用 worker + +const parserIndex = 0; -let parserIndex = 0; +// 防抖:记录上一次补全时间,避免频繁触发 +const lastCompletionTime = 0; +const COMPLETION_DEBOUNCE = 200; // 200ms 防抖 const asyncParser = async ( text: string, index: number, parserType: IParserType, -) => { - parserIndex++; - const currentParserIndex = parserIndex; - - let resolve: any = null; - let reject: any = null; - - const promise = new Promise((promiseResolve, promiseReject) => { - resolve = promiseResolve; - reject = promiseReject; - }); - - worker.postMessage({ text, index, parserType }); - - worker.onmessage = (event) => { - if (currentParserIndex === parserIndex) { - resolve(event.data); - } else { - reject(); - } - }; - - return promise as Promise; +): Promise => { + // 开发环境直接使用同步解析 + try { + const result = mysqlParser(text, index); + return Promise.resolve(result); + } catch (error) { + console.error('[Parser] 解析错误:', error); + return Promise.reject(error); + } }; +// const asyncParser = async ( +// text: string, +// index: number, +// parserType: IParserType, +// ): Promise => { +// // 如果 worker 可用,使用异步解析 +// if (worker) { +// parserIndex++; +// const currentParserIndex = parserIndex; +// +// return new Promise((resolve, reject) => { +// worker!.postMessage({ text, index, parserType }); +// +// worker!.onmessage = (event) => { +// if (currentParserIndex === parserIndex) { +// resolve(event.data); +// } else { +// reject(); +// } +// }; +// }); +// } else { +// // 回退方案:同步解析 +// try { +// const result = mysqlParser(text, index); +// return Promise.resolve(result); +// } catch (error) { +// console.error('Parser error:', error); +// return Promise.reject(error); +// } +// } +// }; + function returnCompletionItemsByVersion( value: ICompletionItem[], monacoVersion: IMonacoVersion, diff --git a/chat2db-client/src/components/MonacoEditor/syntax-parser/plugin/monaco-plugin/parser.worker.ts b/chat2db-client/src/components/MonacoEditor/syntax-parser/plugin/monaco-plugin/parser.worker.ts index 86c0795e1..ecf196d57 100644 --- a/chat2db-client/src/components/MonacoEditor/syntax-parser/plugin/monaco-plugin/parser.worker.ts +++ b/chat2db-client/src/components/MonacoEditor/syntax-parser/plugin/monaco-plugin/parser.worker.ts @@ -3,8 +3,12 @@ import { mysqlParser } from '../sql-parser'; // eslint-disable-next-line no-restricted-globals const ctx: Worker = self as any; -ctx.onmessage = event => { - ctx.postMessage(mysqlParser(event.data.text, event.data.index)); +ctx.onmessage = (event: MessageEvent) => { + const { text, index, parserType } = event.data; + const result = mysqlParser(text, index); + ctx.postMessage(result); }; -export default null as any; +// 保持 worker 活跃 +export {}; + diff --git a/chat2db-client/src/components/MonacoEditor/syntax-parser/plugin/monaco-plugin/sql-autocomplete.ts b/chat2db-client/src/components/MonacoEditor/syntax-parser/plugin/monaco-plugin/sql-autocomplete.ts new file mode 100644 index 000000000..e7aabdb52 --- /dev/null +++ b/chat2db-client/src/components/MonacoEditor/syntax-parser/plugin/monaco-plugin/sql-autocomplete.ts @@ -0,0 +1,693 @@ +import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; +import { monacoSqlAutocomplete } from './index'; +import { DatabaseTypeCode } from '@/constants'; +import redisService from '@/service/redis'; +import sqlService, { IForeignKeyVO } from '@/service/sql'; +import { IBoundInfo } from '@/typings/workspace'; +import { ICompletionItem, ITableInfo, IJoinTableInfo, ICursorInfo } from '../sql-parser/base/define'; +import { getDialectCompletion } from './dialects'; + +export interface ISqlAutocompleteOptions { + monaco: typeof monaco; + editor: monaco.editor.IStandaloneCodeEditor; + boundInfo: IBoundInfo; + parserType?: 'mysql' | 'odps' | 'blink' | 'dsql' | 'grail' | 'emcsql'; +} + +export interface ISqlAutocompleteDisposable { + dispose: () => void; +} + +// 字段缓存 +const fieldCache = new Map(); +const redisKeyCache = new Map(); +const REDIS_KEY_CACHE_TTL = 30000; + +const redisCommandSnippets: Record = { + APPEND: 'APPEND ${1:key} ${2:value}', + AUTH: 'AUTH ${1:password}', + BITCOUNT: 'BITCOUNT ${1:key}', + BLPOP: 'BLPOP ${1:key} ${2:timeout}', + BRPOP: 'BRPOP ${1:key} ${2:timeout}', + DEL: 'DEL ${1:key}', + DECR: 'DECR ${1:key}', + DECRBY: 'DECRBY ${1:key} ${2:decrement}', + EXISTS: 'EXISTS ${1:key}', + EXPIRE: 'EXPIRE ${1:key} ${2:seconds}', + GET: 'GET ${1:key}', + GETBIT: 'GETBIT ${1:key} ${2:offset}', + GETRANGE: 'GETRANGE ${1:key} ${2:start} ${3:end}', + HDEL: 'HDEL ${1:key} ${2:field}', + HEXISTS: 'HEXISTS ${1:key} ${2:field}', + HGET: 'HGET ${1:key} ${2:field}', + HGETALL: 'HGETALL ${1:key}', + HKEYS: 'HKEYS ${1:key}', + HLEN: 'HLEN ${1:key}', + HMGET: 'HMGET ${1:key} ${2:field}', + HSET: 'HSET ${1:key} ${2:field} ${3:value}', + HVALS: 'HVALS ${1:key}', + INCR: 'INCR ${1:key}', + INCRBY: 'INCRBY ${1:key} ${2:increment}', + KEYS: 'KEYS ${1:pattern}', + LINDEX: 'LINDEX ${1:key} ${2:index}', + LLEN: 'LLEN ${1:key}', + LPOP: 'LPOP ${1:key}', + LPUSH: 'LPUSH ${1:key} ${2:value}', + LRANGE: 'LRANGE ${1:key} ${2:start} ${3:stop}', + LREM: 'LREM ${1:key} ${2:count} ${3:value}', + LSET: 'LSET ${1:key} ${2:index} ${3:value}', + LTRIM: 'LTRIM ${1:key} ${2:start} ${3:stop}', + MGET: 'MGET ${1:key}', + MSET: 'MSET ${1:key} ${2:value}', + PERSIST: 'PERSIST ${1:key}', + PEXPIRE: 'PEXPIRE ${1:key} ${2:milliseconds}', + PUBLISH: 'PUBLISH ${1:channel} ${2:message}', + RENAME: 'RENAME ${1:key} ${2:newkey}', + RENAMENX: 'RENAMENX ${1:key} ${2:newkey}', + RPOP: 'RPOP ${1:key}', + RPUSH: 'RPUSH ${1:key} ${2:value}', + SADD: 'SADD ${1:key} ${2:member}', + SCARD: 'SCARD ${1:key}', + SDIFF: 'SDIFF ${1:key}', + SET: 'SET ${1:key} ${2:value}', + SETEX: 'SETEX ${1:key} ${2:seconds} ${3:value}', + SETNX: 'SETNX ${1:key} ${2:value}', + SINTER: 'SINTER ${1:key}', + SISMEMBER: 'SISMEMBER ${1:key} ${2:member}', + SMEMBERS: 'SMEMBERS ${1:key}', + SREM: 'SREM ${1:key} ${2:member}', + STRLEN: 'STRLEN ${1:key}', + SUBSCRIBE: 'SUBSCRIBE ${1:channel}', + SUNION: 'SUNION ${1:key}', + TTL: 'TTL ${1:key}', + TYPE: 'TYPE ${1:key}', + ZADD: 'ZADD ${1:key} ${2:score} ${3:member}', + ZCARD: 'ZCARD ${1:key}', + ZCOUNT: 'ZCOUNT ${1:key} ${2:min} ${3:max}', + ZRANGE: 'ZRANGE ${1:key} ${2:start} ${3:stop}', + ZRANK: 'ZRANK ${1:key} ${2:member}', + ZREM: 'ZREM ${1:key} ${2:member}', + ZSCORE: 'ZSCORE ${1:key} ${2:member}', +}; + +const redisSubcommands: Record = { + CLIENT: ['GETNAME', 'ID', 'INFO', 'KILL', 'LIST', 'PAUSE', 'REPLY', 'SETNAME', 'UNBLOCK'], + CLUSTER: [ + 'ADDSLOTS', + 'COUNT-FAILURE-REPORTS', + 'COUNTKEYSINSLOT', + 'DELSLOTS', + 'FAILOVER', + 'FORGET', + 'GETKEYSINSLOT', + 'INFO', + 'KEYSLOT', + 'MEET', + 'NODES', + 'REPLICATE', + 'RESET', + 'SAVECONFIG', + 'SET-CONFIG-EPOCH', + 'SETSLOT', + 'SLAVES', + 'SLOTS', + ], + COMMAND: ['COUNT', 'GETKEYS', 'INFO'], + CONFIG: ['GET', 'REWRITE', 'RESETSTAT', 'SET'], + DEBUG: ['OBJECT', 'SEGFAULT'], + PUBSUB: ['CHANNELS', 'NUMPAT', 'NUMSUB'], + SCRIPT: ['EXISTS', 'FLUSH', 'KILL', 'LOAD'], + SLOWLOG: ['GET', 'LEN', 'RESET'], +}; + +const mapDatabaseTypeToParser = (databaseType: string): ISqlAutocompleteOptions['parserType'] => { + const typeMap: Record = { + MYSQL: 'mysql', + POSTGRESQL: 'mysql', + ORACLE: 'mysql', + SQLSERVER: 'mysql', + H2: 'mysql', + MARIADB: 'mysql', + TIDB: 'mysql', + DAMENG: 'mysql', + KINGBASE: 'mysql', + VERTICAL: 'mysql', + SQLITE: 'mysql', + PRESTO: 'mysql', + TRINO: 'mysql', + CLICKHOUSE: 'mysql', + DLC: 'mysql', + DB2: 'mysql', + SYBASE: 'mysql', + INFLUXDB: 'mysql', + MONGODB: 'mysql', + REDIS: 'mysql', + ELASTICSEARCH: 'mysql', + HIVE: 'mysql', + SPARK: 'mysql', + IMPALA: 'mysql', + KYLESE: 'mysql', + OCEANBASE: 'mysql', + GAUSS: 'mysql', + TDENGINE: 'mysql', + TABLESTORE: 'mysql', + }; + return typeMap[databaseType?.toUpperCase()] || 'mysql'; +}; + +const cleanIdentifier = (name: string): string => { + if (!name) return ''; + return name.replace(/^[`'"[\]]+|[`'"[\]]+$/g, ''); +}; + +const matchesInputValue = (word: string, inputValue?: string) => { + const trimmedInputValue = inputValue?.trim(); + if (!trimmedInputValue) { + return true; + } + + return word.toLowerCase().startsWith(trimmedInputValue.toLowerCase()); +}; + +const noParenthesesFunctionNames = new Set([ + 'CURRENT_DATE', + 'CURRENT_ROLE', + 'CURRENT_TIME', + 'CURRENT_TIMESTAMP', + 'CURRENT_USER', + 'LOCALTIME', + 'LOCALTIMESTAMP', + 'SESSION_USER', + 'SYSDATE', + 'SYSTEM_USER', +]); + +const shouldAppendFunctionParentheses = (functionName: string) => { + const normalizedFunctionName = functionName.toUpperCase(); + return /^[A-Z_][A-Z0-9_]*$/.test(normalizedFunctionName) && !noParenthesesFunctionNames.has(normalizedFunctionName); +}; + +const getFunctionInsertText = (functionName: string) => { + if (!shouldAppendFunctionParentheses(functionName)) { + return functionName; + } + + return `${functionName}($0)`; +}; + +const isRedisDatabase = (databaseType?: DatabaseTypeCode | string) => { + return databaseType?.toUpperCase() === DatabaseTypeCode.REDIS; +}; + +const getRedisKeySearchText = (inputValue?: string) => { + const trimmedInputValue = inputValue?.trim(); + return trimmedInputValue ? `${trimmedInputValue}*` : '*'; +}; + +const getRedisLineContext = (model: monaco.editor.ITextModel, position: monaco.Position) => { + const lineBeforeCursor = model.getLineContent(position.lineNumber).slice(0, position.column - 1); + const word = model.getWordUntilPosition(position); + const commandMatch = lineBeforeCursor.trimStart().match(/^(\S+)/); + const tokens = lineBeforeCursor + .trim() + .split(/\s+/) + .filter(Boolean); + + return { + command: commandMatch?.[1]?.toUpperCase() || '', + isCommandPosition: tokens.length <= 1 && !/\s$/.test(lineBeforeCursor), + inputValue: word.word, + range: new monaco.Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn), + tokenCount: tokens.length, + }; +}; + +const getRedisKeySuggestions = async ( + boundInfo: IBoundInfo, + inputValue: string, + range: monaco.Range, +): Promise => { + if (!boundInfo.dataSourceId) { + return []; + } + + const cacheKey = [boundInfo.dataSourceId, boundInfo.databaseName || '', getRedisKeySearchText(inputValue)].join('_'); + const cachedData = redisKeyCache.get(cacheKey); + if (cachedData && cachedData.expiresAt > Date.now()) { + return cachedData.data.map((item) => buildRedisKeySuggestion(item, range)); + } + + try { + const data = await redisService.getKeyList({ + dataSourceId: boundInfo.dataSourceId, + databaseName: boundInfo.databaseName, + searchKey: getRedisKeySearchText(inputValue), + count: 50, + }); + const filteredData = (data || []).filter((item) => matchesInputValue(item.name, inputValue)); + redisKeyCache.set(cacheKey, { + data: filteredData, + expiresAt: Date.now() + REDIS_KEY_CACHE_TTL, + }); + return filteredData.map((item) => buildRedisKeySuggestion(item, range)); + } catch (error) { + console.error('[Redis 补全 - API] 获取 key 失败:', error); + return []; + } +}; + +const buildRedisKeySuggestion = (item: any, range: monaco.Range): monaco.languages.CompletionItem => { + const keyName = item.name || ''; + return { + label: keyName, + insertText: keyName, + sortText: `B${keyName}`, + kind: monaco.languages.CompletionItemKind.Value, + detail: item.type ? `(Redis key) ${item.type}` : '(Redis key)', + documentation: + item.ttl === undefined || item.ttl === null ? `key: ${keyName}` : `key: ${keyName}, ttl: ${item.ttl}`, + range, + }; +}; + +const initRedisAutocomplete = (options: ISqlAutocompleteOptions): ISqlAutocompleteDisposable => { + const { monaco: monacoIns, boundInfo } = options; + const redisCompletion = getDialectCompletion(DatabaseTypeCode.REDIS); + + const provider = monacoIns.languages.registerCompletionItemProvider('sql', { + triggerCharacters: ' abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:*'.split(''), + provideCompletionItems: async (model, position) => { + const { command, isCommandPosition, inputValue, range, tokenCount } = getRedisLineContext(model, position); + + if (isCommandPosition) { + return { + suggestions: redisCompletion.keywords + .filter((redisCommand) => matchesInputValue(redisCommand, inputValue)) + .map((redisCommand) => ({ + label: redisCommand, + insertText: redisCommandSnippets[redisCommand] || redisCommand, + insertTextRules: redisCommandSnippets[redisCommand] + ? monacoIns.languages.CompletionItemInsertTextRule.InsertAsSnippet + : undefined, + sortText: `A${redisCommand}`, + kind: monacoIns.languages.CompletionItemKind.Keyword, + detail: '(Redis command)', + range, + })), + }; + } + + if (redisSubcommands[command] && tokenCount <= 2) { + return { + suggestions: redisSubcommands[command] + .filter((subcommand) => matchesInputValue(subcommand, inputValue)) + .map((subcommand) => ({ + label: subcommand, + insertText: subcommand, + sortText: `A${subcommand}`, + kind: monacoIns.languages.CompletionItemKind.Keyword, + detail: `(Redis ${command} subcommand)`, + range, + })), + }; + } + + const keySuggestions = await getRedisKeySuggestions(boundInfo, inputValue, range); + return { + suggestions: keySuggestions, + }; + }, + }); + + return { + dispose: () => { + provider.dispose(); + }, + }; +}; + +export const initSqlAutocomplete = (options: ISqlAutocompleteOptions): ISqlAutocompleteDisposable => { + const { editor, boundInfo, parserType } = options; + + if (isRedisDatabase(boundInfo.databaseType)) { + return initRedisAutocomplete(options); + } + + const effectiveParserType = parserType || mapDatabaseTypeToParser(boundInfo.databaseType || 'MYSQL'); + + // Register completion provider and store the disposable + const completionProviderDisposable = monacoSqlAutocomplete(monaco, editor, { + language: 'sql', + parserType: effectiveParserType, + + onSuggestTableNames: async (cursorInfo?: ICursorInfo) => { + try { + const data = await sqlService.getAllTableList({ + dataSourceId: boundInfo.dataSourceId, + databaseName: boundInfo.databaseName, + schemaName: boundInfo.schemaName, + }); + + const parentName = boundInfo.schemaName || boundInfo.databaseName || ''; + + return data.map((table) => { + const name = table.name; + const label = parentName ? `${name} (${parentName})` : name; + return { + label, + insertText: name, + sortText: `Z${name}`, + kind: monaco.languages.CompletionItemKind.Struct as any, + detail: `(表) ${table.comment || ''}`, + documentation: table.comment || `表: ${name}`, + }; + }); + } catch (error) { + console.error('[SQL 补全 - API] 获取表名失败:', error); + return []; + } + }, + + onSuggestJoinTables: async (cursorInfo?: ICursorInfo) => { + try { + const joinInfo = cursorInfo?.joinTableInfo; + if (!joinInfo || !joinInfo.currentTable) { + console.warn('[SQL 补全 - JOIN] 无当前表信息,返回所有表'); + return await sqlService + .getAllTableList({ + dataSourceId: boundInfo.dataSourceId, + databaseName: boundInfo.databaseName, + schemaName: boundInfo.schemaName, + }) + .then((data) => { + const parentName = boundInfo.schemaName || boundInfo.databaseName || ''; + return data.map((table) => { + const name = table.name; + const alias = name + .split(/[_\s]+/) + .map((word) => word.charAt(0).toLowerCase()) + .join(''); + const label = parentName ? `${name} (${parentName})` : name; + return { + label, + insertText: `${name} ${alias}`, + sortText: `Z${name}`, + kind: monaco.languages.CompletionItemKind.Struct as any, + detail: `(表) ${table.comment || ''}`, + documentation: table.comment || `表: ${name}`, + }; + }); + }); + } + + const currentTableName = cleanIdentifier(joinInfo.currentTable.tableName?.value); + const currentTableAlias = + cleanIdentifier(joinInfo.currentTableAlias) || + currentTableName + .split(/[_\s]+/) + .map((word) => word.charAt(0).toLowerCase()) + .join(''); + if (!currentTableName) { + console.warn('[SQL 补全 - JOIN] 当前表名为空,返回所有表'); + return []; + } + + console.log('[SQL 补全 - JOIN] 当前表:', currentTableName); + console.log('[SQL 补全 - JOIN] 已关联表数量:', joinInfo.joinedTables?.length || 0); + + // 获取当前表的外键关系 + const foreignKeys = await sqlService.getForeignKeyList({ + dataSourceId: boundInfo.dataSourceId, + databaseName: boundInfo.databaseName || '', + schemaName: boundInfo.schemaName, + tableName: currentTableName, + }); + + console.log('[SQL 补全 - JOIN] 外键数量:', foreignKeys.length); + + // 过滤掉已经在 JOIN 中使用的表 + const joinedTableNames = new Set( + (joinInfo.joinedTables || []).map((t) => cleanIdentifier(t.tableName?.value)).filter(Boolean), + ); + + // 获取所有表用于填充未找到外键时的默认列表 + const allTables = await sqlService.getAllTableList({ + dataSourceId: boundInfo.dataSourceId, + databaseName: boundInfo.databaseName, + schemaName: boundInfo.schemaName, + }); + + const parentName = boundInfo.schemaName || boundInfo.databaseName || ''; + + // 生成表别名:取首字母,如果冲突则加数字 + const generateAlias = (tableName: string, usedAliases: Set): string => { + let alias = tableName + .split(/[_\s]+/) + .map((word) => word.charAt(0).toLowerCase()) + .join(''); + if (usedAliases.has(alias)) { + let i = 1; + while (usedAliases.has(`${alias}${i}`)) { + i++; + } + alias = `${alias}${i}`; + } + usedAliases.add(alias); + return alias; + }; + + // 收集已使用的别名 + const usedAliases = new Set(); + usedAliases.add(currentTableAlias); + + // 如果有外键,优先返回通过外键关联的表(带完整 ON 条件) + if (foreignKeys.length > 0) { + const relatedTableItems: any[] = []; + const processedTables = new Set(); + + // 收集所有关联的表(通过外键引用的表) + for (const fk of foreignKeys as IForeignKeyVO[]) { + const refTable = cleanIdentifier(fk.referencedTable); + if (!refTable || joinedTableNames.has(refTable) || processedTables.has(refTable)) { + continue; + } + + processedTables.add(refTable); + const alias = generateAlias(refTable, usedAliases); + + // 使用 snippet 格式:表名 别名 ON 当前表.外键列 = 关联表.主键列 + const snippetText = + `${refTable} ${alias} ON ` + + `${currentTableAlias}.${fk.columnName} = ${alias}.${fk.referencedColumnName}`; + + const label = parentName ? `${refTable} (${parentName})` : refTable; + relatedTableItems.push({ + label, + insertText: snippetText, + insertTextRules: 4, // Monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet + sortText: `A${refTable}`, // A 开头优先显示 + kind: monaco.languages.CompletionItemKind.Struct as any, + detail: `(关联) ${fk.columnName} → ${fk.referencedColumnName}`, + documentation: fk.comment || `通过 ${fk.columnName} 关联到 ${refTable}.${fk.referencedColumnName}`, + }); + } + + console.log('[SQL 补全 - JOIN] 关联表数量:', relatedTableItems.length); + + // 然后返回其他未关联的表(Z 开头,排在后面) + const unrelatedTableItems = allTables + .filter((t) => !processedTables.has(t.name) && !joinedTableNames.has(t.name)) + .map((table) => { + const name = table.name; + const alias = generateAlias(name, usedAliases); + const label = parentName ? `${name} (${parentName})` : name; + return { + label, + insertText: `${name} ${alias}`, + sortText: `Z${name}`, + kind: monaco.languages.CompletionItemKind.Struct as any, + detail: `(表) ${table.comment || ''}`, + documentation: table.comment || `表: ${name}`, + }; + }); + + return [...relatedTableItems, ...unrelatedTableItems]; + } + + // 如果没有外键,返回所有表(排除已 JOIN 的) + console.log('[SQL 补全 - JOIN] 无外键,返回所有表'); + return allTables + .filter((t) => !joinedTableNames.has(t.name)) + .map((table) => { + const name = table.name; + const alias = generateAlias(name, usedAliases); + const label = parentName ? `${name} (${parentName})` : name; + return { + label, + insertText: `${name} ${alias}`, + sortText: `Z${name}`, + kind: monaco.languages.CompletionItemKind.Struct as any, + detail: `(表) ${table.comment || ''}`, + documentation: table.comment || `表: ${name}`, + }; + }); + } catch (error) { + console.error('[SQL 补全 - JOIN] 获取 JOIN 表失败:', error); + return []; + } + }, + + onSuggestTableFields: async (tableInfo?: ITableInfo, cursorValue?: string, rootStatement?: any) => { + const rawTableName = tableInfo?.tableName?.value; + const tableName = cleanIdentifier(rawTableName); + + const cacheKey = `${boundInfo.dataSourceId}_${boundInfo.databaseName || ''}_${ + boundInfo.schemaName || '' + }_${tableName}`; + if (fieldCache.has(cacheKey)) { + const cachedData = fieldCache.get(cacheKey); + return cachedData.map((column) => { + const name = column.name; + const label = tableName ? `${name} (${tableName})` : name; + const dataType = column.columnType || column.dataType || ''; + return { + label, + insertText: name, + sortText: `A${name}`, + kind: monaco.languages.CompletionItemKind.Field as any, + detail: `(字段) ${dataType}`, + documentation: column.comment || `字段: ${name}, 类型: ${dataType}`, + }; + }); + } + + try { + if (!tableName) { + console.warn('[SQL 补全 - API] 表名为空,返回空数组'); + return []; + } + + const data = await sqlService.getColumnList({ + dataSourceId: boundInfo.dataSourceId, + databaseName: boundInfo.databaseName || '', + schemaName: boundInfo.schemaName, + tableName, + }); + + fieldCache.set(cacheKey, data); + + return data.map((column) => { + const name = column.name; + const label = tableName ? `${name} (${tableName})` : name; + const dataType = column.columnType || column.dataType || ''; + return { + label, + insertText: name, + sortText: `A${name}`, + kind: monaco.languages.CompletionItemKind.Field as any, + detail: `(字段) ${dataType}`, + documentation: column.comment || `字段: ${name}, 类型: ${dataType}`, + }; + }); + } catch (error) { + console.error('[SQL 补全 - API] 获取表字段失败:', error); + return []; + } + }, + + onSuggestFunctionName: async (inputValue?: string) => { + const dialectCompletion = getDialectCompletion(boundInfo.databaseType); + + const keywordItems = dialectCompletion.keywords + .filter((kw) => matchesInputValue(kw, inputValue)) + .map((kw) => ({ + label: kw, + insertText: kw, + sortText: `C${kw}`, + kind: monaco.languages.CompletionItemKind.Keyword as any, + detail: '(关键字)', + })); + + const functionItems = dialectCompletion.functions + .filter((func) => matchesInputValue(func, inputValue)) + .map((func) => ({ + label: func, + insertText: getFunctionInsertText(func), + insertTextRules: shouldAppendFunctionParentheses(func) + ? monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet + : undefined, + sortText: `D${func}`, + kind: monaco.languages.CompletionItemKind.Function as any, + detail: '(函数)', + })); + + const dataTypeItems = dialectCompletion.dataTypes + .filter((dataType) => matchesInputValue(dataType, inputValue)) + .map((dataType) => ({ + label: dataType, + insertText: dataType, + sortText: `E${dataType}`, + kind: monaco.languages.CompletionItemKind.TypeParameter as any, + detail: '(数据类型)', + })); + + return [...keywordItems, ...functionItems, ...dataTypeItems]; + }, + + onSuggestFieldGroup: (tableNameOrAlias?: string) => { + const label = tableNameOrAlias || ''; + return { + label, + insertText: label, + sortText: `E${label}`, + kind: monaco.languages.CompletionItemKind.Class as any, + detail: '(表别名)', + }; + }, + + onHoverTableField: async (fieldName?: string, extra?: ICompletionItem) => { + const docs: { value: string }[] = []; + if (fieldName) { + docs.push({ value: `**Field:** ${fieldName}` }); + } + if (extra?.detail) { + docs.push({ value: `**Type:** ${extra.detail}` }); + } + if (extra?.documentation) { + docs.push({ value: `**Comment:** ${extra.documentation}` }); + } + return docs; + }, + + onHoverTableName: async (cursorInfo?: ICursorInfo) => { + return [{ value: `**Table:** ${cursorInfo?.token?.value || ''}` }]; + }, + + onHoverFunctionName: async (functionName?: string) => { + return [{ value: `**Function:** ${functionName || ''}` }]; + }, + }); + + return { + dispose: () => { + // Dispose the completion provider to avoid duplicate registrations + if (completionProviderDisposable) { + completionProviderDisposable.dispose(); + } + // 清理缓存(可选,根据 boundInfo 清理或不清理) + // fieldCache.clear(); + }, + }; +}; + +// 缓存清理函数 +export const clearFieldCache = () => { + fieldCache.clear(); + console.log('[SQL 补全 - API] 缓存已清理'); +}; + +export const disposeSqlAutocomplete = (disposable?: ISqlAutocompleteDisposable) => { + if (disposable) { + disposable.dispose(); + } +}; diff --git a/chat2db-client/src/components/MonacoEditor/syntax-parser/plugin/monaco-plugin/worker-factory.ts b/chat2db-client/src/components/MonacoEditor/syntax-parser/plugin/monaco-plugin/worker-factory.ts new file mode 100644 index 000000000..a6199d2a9 --- /dev/null +++ b/chat2db-client/src/components/MonacoEditor/syntax-parser/plugin/monaco-plugin/worker-factory.ts @@ -0,0 +1,13 @@ +// Worker factory to avoid webpack import issues +export const createParserWorker = () => { + try { + // 使用 webpack 的 worker-loader 方式 + const worker = new Worker( + new URL('./parser.worker.js', import.meta.url) + ); + return worker; + } catch (error) { + console.warn('[Worker] Failed to create worker:', error); + return null; + } +}; diff --git a/chat2db-client/src/components/MonacoEditor/syntax-parser/plugin/sql-parser/base/define.ts b/chat2db-client/src/components/MonacoEditor/syntax-parser/plugin/sql-parser/base/define.ts index ada3126b1..a18daf2df 100644 --- a/chat2db-client/src/components/MonacoEditor/syntax-parser/plugin/sql-parser/base/define.ts +++ b/chat2db-client/src/components/MonacoEditor/syntax-parser/plugin/sql-parser/base/define.ts @@ -34,6 +34,12 @@ export interface ITableInfo { namespace: IToken; } +export interface IJoinTableInfo { + currentTable: ITableInfo & { tableName: { value: string } }; + currentTableAlias?: string; + joinedTables: (ITableInfo & { tableName: { value: string } })[]; +} + export interface ICompletionItem { label: string; kind?: string; @@ -51,7 +57,8 @@ export type CursorType = | 'namespace' | 'namespaceOne' | 'functionName' - | 'tableFieldAfterGroup'; + | 'tableFieldAfterGroup' + | 'joinTable'; export type ICursorInfo = { token: IToken; diff --git a/chat2db-client/src/components/MonacoEditor/syntax-parser/plugin/sql-parser/base/parser.ts b/chat2db-client/src/components/MonacoEditor/syntax-parser/plugin/sql-parser/base/parser.ts index 64cf3fdb1..c5a83bca4 100644 --- a/chat2db-client/src/components/MonacoEditor/syntax-parser/plugin/sql-parser/base/parser.ts +++ b/chat2db-client/src/components/MonacoEditor/syntax-parser/plugin/sql-parser/base/parser.ts @@ -39,7 +39,7 @@ export const dataType = () => { }; export const setValue = () => { - return chain(wordSym, '=', [stringSym, numberSym])(); + return chain(stringOrWord, '=', [stringSym, numberSym])(); }; export const setValueList = () => { diff --git a/chat2db-client/src/components/MonacoEditor/syntax-parser/plugin/sql-parser/base/reader.ts b/chat2db-client/src/components/MonacoEditor/syntax-parser/plugin/sql-parser/base/reader.ts index a558bf0a6..81095aa21 100644 --- a/chat2db-client/src/components/MonacoEditor/syntax-parser/plugin/sql-parser/base/reader.ts +++ b/chat2db-client/src/components/MonacoEditor/syntax-parser/plugin/sql-parser/base/reader.ts @@ -5,14 +5,19 @@ import { ICompletionItem, ICursorInfo, IGetFieldsByTableName, + IJoinTableInfo, ISelectStatement, ISource, IStatement, IStatements, + ITableInfo, } from './define'; export async function getCursorInfo(rootStatement: IStatements, keyPath: string[]) { + // console.log('[Reader] getCursorInfo - keyPath:', keyPath); + if (!rootStatement) { + // console.log('[Reader] getCursorInfo - rootStatement 为空'); return null; } @@ -20,13 +25,32 @@ export async function getCursorInfo(rootStatement: IStatements, keyPath: string[ const cursorKey = keyPath.slice().pop(); const parentStatement = _.get(rootStatement, keyPath.slice(0, keyPath.length - 1)); + // console.log('[Reader] getCursorInfo - cursorValue:', cursorValue); + // console.log('[Reader] getCursorInfo - cursorKey:', cursorKey); + // console.log('[Reader] getCursorInfo - parentStatement:', parentStatement); + if (!parentStatement) { + // console.log('[Reader] getCursorInfo - parentStatement 为空'); return null; } - return (await judgeStatement(parentStatement, async typePlusVariant => { + const result = await judgeStatement(parentStatement, async typePlusVariant => { + // console.log('[Reader] getCursorInfo - typePlusVariant:', typePlusVariant); + switch (typePlusVariant) { case 'identifier.tableName': + // console.log('[Reader] getCursorInfo - 识别为表名'); + // 检查是否在 JOIN 后面 + const joinContext = findJoinContext(rootStatement, keyPath); + if (joinContext) { + // console.log('[Reader] getCursorInfo - 识别为 JOIN 后的表名'); + return { + type: 'joinTable', + variant: cursorKey, + token: cursorValue, + joinTableInfo: joinContext, + }; + } return { type: 'tableName', variant: cursorKey, @@ -35,6 +59,7 @@ export async function getCursorInfo(rootStatement: IStatements, keyPath: string[ }; case 'identifier.column': if (cursorKey === 'name') { + // console.log('[Reader] getCursorInfo - 识别为普通字段'); return { type: 'tableField', token: cursorValue, @@ -43,19 +68,25 @@ export async function getCursorInfo(rootStatement: IStatements, keyPath: string[ return null; case 'identifier.columnAfterGroup': + // console.log('[Reader] getCursorInfo - 识别为表名限定字段,groupName:', parentStatement.groupName.value); return { type: 'tableFieldAfterGroup', token: cursorValue, groupName: parentStatement.groupName.value, }; case 'function': + // console.log('[Reader] getCursorInfo - 识别为函数'); return { type: 'functionName', token: cursorValue, }; default: + // console.log('[Reader] getCursorInfo - 未知类型:', typePlusVariant); } - })) as ICursorInfo; + }); + + // console.log('[Reader] getCursorInfo - 最终结果:', result); + return result as ICursorInfo; } export function findNearestStatement( @@ -93,21 +124,107 @@ export function findNearestStatement( return null; } +export function findJoinContext( + rootStatement: IStatements, + keyPath: string[], +): IJoinTableInfo | null { + if (!rootStatement || keyPath.length < 2) { + return null; + } + + // 查找当前的 tableSource 语句 + const currentStatement = _.get(rootStatement, keyPath.slice(0, keyPath.length - 1)); + + // 检查父级是否为 join 语句 + const parentJoin = findNearestStatement(rootStatement, keyPath, stmt => { + return stmt?.variant === 'join'; + }); + + if (!parentJoin || parentJoin.variant !== 'join') { + return null; + } + + // 获取最近的 SELECT 语句 + const selectStatement = findNearestStatement(rootStatement, keyPath, stmt => { + return stmt?.variant === 'select'; + }); + + if (!selectStatement) { + return null; + } + + // 获取当前 FROM 子句中所有的表 + const sources = _.get(selectStatement, 'from.sources', []); + const joinedTables: ITableInfo[] = []; + + // 收集所有已加入的表 + for (const source of sources) { + const mainTable = _.get(source, 'source.name') as ITableInfo | undefined; + if (mainTable) { + joinedTables.push(mainTable); + } + + // 收集 joins 中的表 + const joins = _.get(source, 'joins', []) || []; + for (const join of joins) { + const joinTable = _.get(join, 'join.name') as ITableInfo | undefined; + if (joinTable) { + joinedTables.push(joinTable); + } + } + } + + // 获取主表(FROM 后的第一个表)及其别名 + const mainSource = _.get(sources, '[0]'); + const currentTable = _.get(mainSource, 'source.name') as ITableInfo | undefined; + const currentTableAlias = _.get(mainSource, 'source.alias.value') as string | undefined; + + if (!currentTable) { + return null; + } + + return { + currentTable, + currentTableAlias, + joinedTables, + }; +} + export async function getFieldsFromStatement( rootStatement: IStatements, cursorKeyPath: string[], getFieldsByTableName: IGetFieldsByTableName, ) { + // console.log('[Reader] getFieldsFromStatement - 开始执行'); + const cursorInfo = await getCursorInfo(rootStatement, cursorKeyPath); - const cursorRootStatement = findNearestStatement(rootStatement, cursorKeyPath); + let cursorRootStatement = findNearestStatement(rootStatement, cursorKeyPath); + + // console.log('[Reader] getFieldsFromStatement - cursorRootStatement:', cursorRootStatement?.variant); + + // 如果 cursorKeyPath 为空,尝试从 rootStatement 中找到 SELECT 语句 + if (!cursorRootStatement && rootStatement) { + // 检查 rootStatement 是否是 statements 数组 + if (_.isArray(rootStatement) && rootStatement.length > 0) { + // 找到第一个 SELECT 语句 + const selectStatement = rootStatement.find(stmt => stmt?.variant === 'select'); + if (selectStatement) { + cursorRootStatement = selectStatement; + } + } else if (rootStatement?.variant === 'select') { + cursorRootStatement = rootStatement; + } + } if (!cursorRootStatement) { + // console.log('[Reader] getFieldsFromStatement - cursorRootStatement 为空,返回空数组'); return []; } switch (cursorRootStatement.variant) { // Select statement case 'select': + // console.log('[Reader] getFieldsFromStatement - SELECT 语句模式'); return getFieldsByFromClauses( cursorRootStatement, _.get(cursorRootStatement, 'from.sources', []), @@ -117,6 +234,7 @@ export async function getFieldsFromStatement( // Join statement // 字段是 source 表的(自带 + join 的表) case 'join': + // console.log('[Reader] getFieldsFromStatement - JOIN 语句模式'); const parentCursorKeyPath = cursorKeyPath.slice(); parentCursorKeyPath.pop(); @@ -124,6 +242,8 @@ export async function getFieldsFromStatement( return eachStatement.variant === 'select'; }); + // console.log('[Reader] getFieldsFromStatement - 父级 SELECT:', parentSelectStatement?.variant); + return getFieldsByFromClauses( parentSelectStatement, _.get(parentSelectStatement, 'from.sources', []), @@ -131,6 +251,7 @@ export async function getFieldsFromStatement( getFieldsByTableName, ); default: + // console.log('[Reader] getFieldsFromStatement - 未知 variant:', cursorRootStatement.variant); } return []; @@ -160,6 +281,8 @@ async function getFieldsByFromClause( getFieldsByTableName: IGetFieldsByTableName, ): Promise { return judgeStatement(fromStatement, async typePlusVariant => { + // console.log('[Reader] getFieldsByFromClause - typePlusVariant:', typePlusVariant); + switch (typePlusVariant) { case 'statement.tableSource': // ignore joins @@ -177,20 +300,29 @@ async function getFieldsByFromClause( getFieldsByTableName, ) : []; + // console.log('[Reader] getFieldsByFromClause - tableSource 字段数:', tableSourceFields.length, 'join 字段数:', joinsFields.length); return tableSourceFields.concat(joinsFields); case 'statement.join': return getFieldsByFromClause(rootStatement, (fromStatement as any).join, cursorInfo, getFieldsByTableName); case 'identifier.table': const itFromStatement = fromStatement as ISource; - let originFields = await getFieldsByTableName(itFromStatement.name, cursorInfo.token.value, rootStatement); + // console.log('[Reader] getFieldsByFromClause - 处理表标识符,表信息:', itFromStatement.name); + // console.log('[Reader] getFieldsByFromClause - 别名:', itFromStatement.alias); + + // 当 cursorInfo 为 null 时,使用空字符串作为 cursorValue + const cursorValue = cursorInfo?.token?.value || ''; + let originFields = await getFieldsByTableName(itFromStatement.name, cursorValue, rootStatement); const tableNames: string[] = _.get(itFromStatement, 'name.tableNames', []); + // console.log('[Reader] getFieldsByFromClause - 原始字段数:', originFields.length); + let groupPickerName: string = null; const tableNameAlias: string = _.get(itFromStatement, 'alias.value'); - // 如果有 alias,直接作为 groupPickerName + // 如果有 alias,直接作为 groupPickerName if (tableNameAlias) { + // console.log('[Reader] getFieldsByFromClause - 使用别名作为分组名:', tableNameAlias); groupPickerName = tableNameAlias; } else { // 实现的 tableNames 数量 @@ -206,7 +338,10 @@ async function getFieldsByFromClause( // 如果 existKeyCount 大于 1,则不提供 groupPickerName if (existKeyCount > 1) { + // console.log('[Reader] getFieldsByFromClause - 多个表名有值,不提供分组名'); groupPickerName = null; + } else { + // console.log('[Reader] getFieldsByFromClause - 使用表名作为分组名:', groupPickerName); } } @@ -222,10 +357,14 @@ async function getFieldsByFromClause( originFieldName: originField.label, }; }); + + // console.log('[Reader] getFieldsByFromClause - 处理后的字段示例:', originFields[0]); return originFields; case 'statement.select': const ssFromStatement = fromStatement as ISelectStatement; + // console.log('[Reader] getFieldsByFromClause - 处理子查询'); + let statementSelectFields: ICompletionItem[] = []; const fields = await getFieldsByFromClauses( @@ -235,10 +374,14 @@ async function getFieldsByFromClause( getFieldsByTableName, ); + // console.log('[Reader] getFieldsByFromClause - 子查询字段数:', fields.length); + // If select *, return all fields if (ssFromStatement.result.length === 1 && ssFromStatement.result[0].name.value === '*') { + // console.log('[Reader] getFieldsByFromClause - SELECT * 模式,返回所有字段'); statementSelectFields = fields.slice(); } else { + // console.log('[Reader] getFieldsByFromClause - SELECT 指定字段模式'); statementSelectFields = fields .map(field => { const selectedField = ssFromStatement.result.find(result => { @@ -279,8 +422,11 @@ async function getFieldsByFromClause( .slice(); } + // console.log('[Reader] getFieldsByFromClause - 筛选后字段数:', statementSelectFields.length); + // If has alias, change if (_.has(ssFromStatement, 'alias.value')) { + // console.log('[Reader] getFieldsByFromClause - 子查询有别名:', _.get(ssFromStatement, 'alias.value')); statementSelectFields = statementSelectFields.map(statementSelectField => { return { ...statementSelectField, @@ -291,6 +437,7 @@ async function getFieldsByFromClause( return statementSelectFields; default: + // console.log('[Reader] getFieldsByFromClause - 未知类型:', typePlusVariant); return null; } }); diff --git a/chat2db-client/src/components/MonacoEditor/syntax-parser/plugin/sql-parser/mysql/lexer.ts b/chat2db-client/src/components/MonacoEditor/syntax-parser/plugin/sql-parser/mysql/lexer.ts index 36067ac97..a0b214fa4 100644 --- a/chat2db-client/src/components/MonacoEditor/syntax-parser/plugin/sql-parser/mysql/lexer.ts +++ b/chat2db-client/src/components/MonacoEditor/syntax-parser/plugin/sql-parser/mysql/lexer.ts @@ -9,8 +9,8 @@ export const sqlTokenizer = createLexer([ { type: 'comment', regexes: [ - /^((?:#|--).*?(?:\n|$))/, // # -- - /^(\/\*[^]*?(?:\*\/|$))/, // /* */ + /^((?:#|--)[\s\S]*?(?:\r?\n|$))/, // # -- + /^(\/\*[\s\S]*?(?:\*\/|$))/, // /* */ ], ignore: true, }, diff --git a/chat2db-client/src/components/MonacoEditor/syntax-parser/plugin/sql-parser/mysql/parser.ts b/chat2db-client/src/components/MonacoEditor/syntax-parser/plugin/sql-parser/mysql/parser.ts index 289765a84..d7951c663 100644 --- a/chat2db-client/src/components/MonacoEditor/syntax-parser/plugin/sql-parser/mysql/parser.ts +++ b/chat2db-client/src/components/MonacoEditor/syntax-parser/plugin/sql-parser/mysql/parser.ts @@ -260,7 +260,7 @@ const tableOption = () => { }; const primaryKeyList = () => { - return chain(wordSym, optional(',', primaryKeyList))(); + return chain(stringOrWord, optional(',', primaryKeyList))(); }; const tableName = () => { @@ -297,7 +297,14 @@ const createViewStatement = () => { // ----------------------------------- Insert statement ----------------------------------- const insertStatement = () => { - return chain('insert', optional('ignore'), 'into', tableName, optional(selectFieldsInfo), [selectStatement])(ast => { + return chain( + 'insert', + optional('ignore'), + 'into', + tableName, + optional(selectFieldsInfo), + [selectStatement, valuesClause], + )(ast => { return { type: 'statement', variant: 'insert', @@ -311,12 +318,24 @@ const insertStatement = () => { }); }; +const valuesClause = () => { + return chain('values', valueList, many(',', valueList))(); +}; + +const valueList = () => { + return chain('(', valueItem, many(',', valueItem), ')')(); +}; + +const valueItem = () => { + return chain([stringSym, numberSym, wordSym, chain('null')()])(); +}; + const selectFieldsInfo = () => { return chain('(', selectFields, ')')(); }; const selectFields = () => { - return chain(wordSym, many(',', wordSym))(); + return chain(stringOrWord, many(',', stringOrWord))(); }; // ----------------------------------- groupBy ----------------------------------- @@ -370,7 +389,7 @@ const limitClause = () => { // ----------------------------------- Function ----------------------------------- const functionChain = () => { - return chain([castFunction, normalFunction, ifFunction])(ast => { + return chain([castFunction, normalFunction, ifFunction, groupConcatFunction])(ast => { return ast[0]; }); }; @@ -385,6 +404,27 @@ const ifFunction = () => { }); }; +const groupConcatFunction = () => { + return chain( + 'group_concat', + '(', + optional('distinct'), + functionFields, + optional(chain('order', 'by', orderByExpressionList)()), + optional(chain('separator', stringSym)()), + ')', + )(ast => { + return { + type: 'function', + name: 'group_concat', + args: ast[3], + distinct: ast[2], + orderBy: ast[4], + separator: ast[5], + }; + }); +}; + const castFunction = () => { return chain('cast', '(', field, 'as', dataType, ')')(ast => { return { @@ -442,7 +482,7 @@ const variableAssignment = () => { }; const variableLeftValue = () => { - return chain(wordSym, many('.', wordSym))(); + return chain(stringOrWord, many('.', stringOrWord))(); }; // ----------------------------------- Expression ----------------------------------- @@ -576,7 +616,7 @@ const dotStringOrWordOrNumber = () => { return chain('.', [ stringSym, numberSym, - chain(wordSym)(ast => { + chain(stringOrWord)(ast => { return { type: 'identifier', variant: 'columnAfterGroup', diff --git a/chat2db-client/src/components/Popularize/index.tsx b/chat2db-client/src/components/Popularize/index.tsx index b955fe658..dd17185e5 100644 --- a/chat2db-client/src/components/Popularize/index.tsx +++ b/chat2db-client/src/components/Popularize/index.tsx @@ -11,7 +11,7 @@ interface IProps { } const url = 'http://oss.sqlgpt.cn/static/chat2db-wechat.jpg?x-oss-process=image/auto-orient,1/resize,m_lfit,w_256/quality,Q_80/format,webp'; -export default memo(function Popularize(props) { +export default memo((props) => { const { className } = props; const renderTip = () => { diff --git a/chat2db-client/src/components/SearchResult/components/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/components/TableBox/index.tsx index dc24967c6..0f77e6631 100644 --- a/chat2db-client/src/components/SearchResult/components/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/components/TableBox/index.tsx @@ -14,17 +14,17 @@ import styles from './index.less'; // 工具函数 import { compareStrings } from '@/utils/sort'; -import { downloadFile } from '@/utils/file'; import { transformInputValue } from '../../utils'; // 类型定义 -import { CRUD } from '@/constants'; +import { CRUD, DatabaseTypeCode } from '@/constants'; import { TableDataType } from '@/constants/table'; import { IManageResultData, IResultConfig } from '@/typings/database'; import { ExportSizeEnum, ExportTypeEnum } from '@/typings/resultTable'; // api import sqlService, { IExportParams, IExecuteSqlParams } from '@/service/sql'; +import taskService, { ITask } from '@/service/task'; // store import { setFocusedContent } from '@/store/common/copyFocusedContent'; @@ -40,7 +40,7 @@ import MyPagination from '../Pagination'; import StatusBar from '../StatusBar'; import RightClickMenu, { AllSupportedMenusType } from '../RightClickMenu'; -// 自定义hooks +// 自定义 hooks import useCurdTableData from '../../hooks/useCurdTableData'; import useMultipleSelect from '../../hooks/useMultipleSelect'; import usePasteData from '../../hooks/usePasteData'; @@ -146,8 +146,9 @@ export default function TableBox(props: ITableProps) { const [tableLoading, setTableLoading] = useState(false); // 列宽数组 const [columnResize, setColumnResize] = useState([0]); - // 表格的宽度 - // const [tableBoxWidth, setTableBoxWidth] = useState(0); + const [exportProgress, setExportProgress] = useState(0); + const [exportModalVisible, setExportModalVisible] = useState(false); + const exportPollingRef = React.useRef(null); const handleExportSQLResult = async (exportType: ExportTypeEnum, exportSize: ExportSizeEnum) => { const params: IExportParams = { @@ -157,7 +158,62 @@ export default function TableBox(props: ITableProps) { exportType, exportSize, }; - downloadFile(window._BaseURL + '/api/rdb/dml/export', params); + setExportProgress(0); + setExportModalVisible(true); + + try { + const taskId = await taskService.exportResultData(params); + startExportPolling(taskId); + } catch (error) { + message.error(i18n('common.text.exportFailed')); + setExportModalVisible(false); + } + }; + + const startExportPolling = (taskId: number) => { + if (exportPollingRef.current) { + clearInterval(exportPollingRef.current); + } + + exportPollingRef.current = setInterval(async () => { + try { + const task: ITask = await taskService.getTask({ id: taskId }); + if (task) { + const processedCount = parseInt(task.taskProgress || '0', 10); + setExportProgress(processedCount); + + if (task.taskStatus === 'FINISH') { + clearInterval(exportPollingRef.current!); + exportPollingRef.current = null; + downloadExportFile(taskId); + } else if (task.taskStatus === 'ERROR') { + clearInterval(exportPollingRef.current!); + exportPollingRef.current = null; + message.error(i18n('common.text.exportFailed')); + setExportModalVisible(false); + } + } + } catch (error) { + clearInterval(exportPollingRef.current!); + exportPollingRef.current = null; + message.error(i18n('common.text.exportFailed')); + setExportModalVisible(false); + } + }, 500); + }; + + const downloadExportFile = (taskId: number) => { + const downloadUrl = `${window._BaseURL}/api/task/download/${taskId}`; + const link = document.createElement('a'); + link.href = downloadUrl; + link.style.display = 'none'; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + + setTimeout(() => { + setExportModalVisible(false); + }, 1000); }; useEffect(() => { @@ -205,7 +261,6 @@ export default function TableBox(props: ITableProps) { { label: i18n('workspace.table.export.all.csv'), key: '1', - // icon: , onClick: () => { handleExportSQLResult(ExportTypeEnum.CSV, ExportSizeEnum.ALL); }, @@ -213,7 +268,6 @@ export default function TableBox(props: ITableProps) { { label: i18n('workspace.table.export.all.insert'), key: '2', - // icon: , onClick: () => { handleExportSQLResult(ExportTypeEnum.INSERT, ExportSizeEnum.ALL); }, @@ -221,7 +275,6 @@ export default function TableBox(props: ITableProps) { { label: i18n('workspace.table.export.cur.csv'), key: '3', - // icon: , onClick: () => { handleExportSQLResult(ExportTypeEnum.CSV, ExportSizeEnum.CURRENT_PAGE); }, @@ -229,7 +282,6 @@ export default function TableBox(props: ITableProps) { { label: i18n('workspace.table.export.cur.insert'), key: '4', - // icon: , onClick: () => { handleExportSQLResult(ExportTypeEnum.INSERT, ExportSizeEnum.CURRENT_PAGE); }, @@ -404,6 +456,11 @@ export default function TableBox(props: ITableProps) { }; const onClickTotalBtn = async () => { + if (props.executeSqlParams?.databaseType === DatabaseTypeCode.REDIS) { + const total = Number(queryResultData.fuzzyTotal || queryResultData.dataList?.length || 0); + setPaginationConfig({ ...paginationConfig, total }); + return total; + } const res = await sqlService.getDMLCount({ sql: queryResultData.originalSql, ...(props.executeSqlParams || {}), @@ -1108,13 +1165,31 @@ export default function TableBox(props: ITableProps) { return (
{renderContent()} + { + if (exportPollingRef.current) { + clearInterval(exportPollingRef.current); + exportPollingRef.current = null; + } + setExportModalVisible(false); + }} + width={400} + > +
+ {i18n('workspace.table.export.progress.rows')}: {exportProgress} +
+
@@ -1131,7 +1206,7 @@ export default function TableBox(props: ITableProps) { title={initError ? i18n('common.button.executionError') : i18n('editTable.title.sqlPreview')} open={viewUpdateDataSqlModal} footer={false} - destroyOnClose={true} + destroyOnHidden={true} onCancel={() => { setViewUpdateDataSqlModal(false); setUpdateDataSql(''); diff --git a/chat2db-client/src/components/SearchResult/index.less b/chat2db-client/src/components/SearchResult/index.less index ebe068507..8673852bf 100644 --- a/chat2db-client/src/components/SearchResult/index.less +++ b/chat2db-client/src/components/SearchResult/index.less @@ -108,3 +108,55 @@ } } } + +.errorContainer { + height: 100%; + display: flex; + flex-direction: column; + padding: 20px; + overflow: auto; + align-items: center; + justify-content: flex-start; + + .errorContent { + display: flex; + flex-direction: column; + align-items: center; + gap: 16px; + max-width: 800px; + width: 100%; + padding-top: 20px; + } + + .errorMessage { + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + gap: 12px; + padding: 20px; + background: var(--color-error-bg, rgba(255, 77, 79, 0.1)); + border-radius: 8px; + border: 1px solid var(--color-error-border, rgba(255, 77, 79, 0.3)); + } + + .errorIcon { + font-size: 48px; + color: var(--color-error); + } + + .errorText { + width: 100%; + font-size: 14px; + color: var(--color-error); + text-align: center; + white-space: pre-wrap; + word-break: break-word; + line-height: 1.6; + } + + .aiFixButton { + flex-shrink: 0; + margin-top: 8px; + } +} diff --git a/chat2db-client/src/components/SearchResult/index.tsx b/chat2db-client/src/components/SearchResult/index.tsx index 65daa8e1f..14753b8f8 100644 --- a/chat2db-client/src/components/SearchResult/index.tsx +++ b/chat2db-client/src/components/SearchResult/index.tsx @@ -13,7 +13,6 @@ import React, { import classnames from 'classnames'; import Tabs, { ITabItem } from '@/components/Tabs'; import Iconfont from '@/components/Iconfont'; -import StateIndicator from '@/components/StateIndicator'; // import Output from '@/components/Output'; import { IManageResultData, IResultConfig } from '@/typings'; import TableBox from './components/TableBox'; @@ -21,9 +20,10 @@ import StatusBar from './components/StatusBar'; import styles from './index.less'; import EmptyImg from '@/assets/img/empty.svg'; import i18n from '@/i18n'; -import sqlServer, { IExecuteSqlParams } from '@/service/sql'; +import sqlServer, { IExecuteSqlParams, ICreateVirtualFKParams } from '@/service/sql'; import { v4 as uuidV4 } from 'uuid'; -import { Spin } from 'antd'; +import { Spin, Modal, message, Button } from 'antd'; +import { setPendingAiChat, setCurrentWorkspaceExtend } from '@/pages/main/workspace/store/common'; interface IProps { className?: string; @@ -32,6 +32,7 @@ interface IProps { concealTabHeader?: boolean; viewTable?: boolean; isActive?: boolean; + onLocateStatement?: (result: IManageResultData) => void; } const defaultResultConfig: IResultConfig = { @@ -42,7 +43,7 @@ const defaultResultConfig: IResultConfig = { }; export interface ISearchResultRef { - handleExecuteSQL: (sql: string) => void; + handleExecuteSQL: (sql: string, options?: { scriptStartLine?: number }) => void; } interface IContext { @@ -60,6 +61,36 @@ export default forwardRef((props: IProps, ref: ForwardedRef) = const controllerRef = useRef(); const [activeTabId, setActiveTabId] = useState(''); const [notChangedSql, setNotChangedSql] = useState(''); + const executeSqlParamsRef = useRef(executeSqlParams); + + useEffect(() => { + executeSqlParamsRef.current = executeSqlParams; + }, [executeSqlParams]); + + const handleAiFix = useCallback((originalSql: string, errorMessage: string) => { + const params = executeSqlParamsRef.current; + if (!params) { + message.warning(i18n('common.message.noConnection')); + return; + } + + setPendingAiChat({ + dataSourceId: params.dataSourceId, + databaseName: params.databaseName, + schemaName: params.schemaName, + tableNames: params.tableName ? [params.tableName] : null, + message: `请修复以下SQL错误:\n\n错误信息:${errorMessage}\n\n原始SQL:\n${originalSql}`, + promptType: 'SQL_FIX', + ext: JSON.stringify({ + originalSql, + errorMessage, + }), + onSqlFixed: (fixedSql) => { + console.log('[SearchResult] SQL fixed:', fixedSql); + }, + }); + setCurrentWorkspaceExtend('ai'); + }, []); useEffect(() => { if (sql) { @@ -75,12 +106,13 @@ export default forwardRef((props: IProps, ref: ForwardedRef) = * 执行SQL * @param sql */ - const handleExecuteSQL = (_sql: string) => { + const handleExecuteSQL = (_sql: string, options?: { scriptStartLine?: number }) => { setTableLoading(true); const api = viewTable ? sqlServer.viewTable : sqlServer.executeSql; const executeSQLParams: IExecuteSqlParams = { sql: _sql, + scriptStartLine: options?.scriptStartLine || 1, tableName: executeSqlParams?.tableName, ...defaultResultConfig, ...executeSqlParams, @@ -102,6 +134,51 @@ export default forwardRef((props: IProps, ref: ForwardedRef) = if(!notChangedSql){ setNotChangedSql(_sql); } + + // 检查是否有虚拟外键建议 + const allSuggestions: IVirtualFkSuggestion[] = []; + res.forEach((item) => { + if (item.vkSuggestions && item.vkSuggestions.length > 0) { + allSuggestions.push(...item.vkSuggestions); + } + }); + + if (allSuggestions.length > 0) { + const suggestionText = allSuggestions + .map((s) => `${s.sourceTable}.${s.sourceColumn} → ${s.targetTable}.${s.targetColumn}`) + .join('\n'); + + Modal.confirm({ + title: i18n('workspace.erDiagram.suggestionHint'), + content: ( +
+                {suggestionText}
+              
+ ), + width: 600, + okText: i18n('common.button.confirm'), + cancelText: i18n('common.button.cancel'), + onOk: async () => { + try { + for (const s of allSuggestions) { + const params: ICreateVirtualFKParams = { + dataSourceId: executeSqlParams.dataSourceId, + databaseName: executeSqlParams.databaseName, + schemaName: executeSqlParams.schemaName, + tableName: s.sourceTable, + columnName: s.sourceColumn, + referencedTable: s.targetTable, + referencedColumnName: s.targetColumn, + }; + await sqlServer.createVirtualForeignKey(params); + } + message.success(i18n('workspace.erDiagram.inferVirtualFkSuccess', allSuggestions.length)); + } catch (error) { + message.error(i18n('workspace.erDiagram.inferVirtualFkError')); + } + }, + }); + } }) .finally(() => { setTableLoading(false); @@ -111,11 +188,15 @@ export default forwardRef((props: IProps, ref: ForwardedRef) = const onChange = useCallback((uuid) => { // activeTabIdRef.current = uuid; setActiveTabId(uuid); - }, []); + const currentResult = resultDataList?.find((item) => item.uuid === uuid); + if (currentResult) { + props.onLocateStatement?.(currentResult); + } + }, [resultDataList, props.onLocateStatement]); const renderResult = (queryResultData) => { function renderSuccessResult() { - const needTable = queryResultData?.headerList?.length > 1; + const needTable = queryResultData?.headerList?.length > 0; return (
@@ -149,12 +230,28 @@ export default forwardRef((props: IProps, ref: ForwardedRef) = {queryResultData.success ? ( renderSuccessResult() ) : ( - +
+
+
+ +
{queryResultData.message}
+
+ + +
+
)} ); diff --git a/chat2db-client/src/components/Tabs/index.tsx b/chat2db-client/src/components/Tabs/index.tsx index 4517da889..cfeeac7d4 100644 --- a/chat2db-client/src/components/Tabs/index.tsx +++ b/chat2db-client/src/components/Tabs/index.tsx @@ -10,12 +10,16 @@ import styles from './index.less'; export interface ITabItem { prefixIcon?: string | React.ReactNode; label: React.ReactNode; + // 编辑标题时的初始纯文本值;当 label 为 React 节点时必须提供 + name?: string; key: number | string; popover?: string | React.ReactNode; children?: React.ReactNode; editableName?: boolean; canClosed?: boolean; styles?: React.CSSProperties; + // 是否支持生成标题 + canGenerateTitle?: boolean; } export interface IOnchangeProps { @@ -25,6 +29,12 @@ export interface IOnchangeProps { const MAX_TABS = 20; +function getInitialEditValue(t: ITabItem): string { + if (typeof t.name === 'string') return t.name; + if (typeof t.label === 'string') return t.label; + return ''; +} + interface IProps { className?: string; items?: ITabItem[]; @@ -32,11 +42,15 @@ interface IProps { onChange?: (key: string | number | null) => void; onEdit?: (action: 'add' | 'remove', data?: ITabItem[], list?: ITabItem[]) => void; hideAdd?: boolean; - editableNameOnBlur?: (option: ITabItem) => void; + editableNameOnBlur?: (option: ITabItem, value: string) => void; concealTabHeader?: boolean; // 最后一个tab不能关闭 lastTabCannotClosed?: boolean; destroyInactiveTabPane?: boolean; + // 生成标题的回调 + onGenerateTitle?: (option: ITabItem) => void; + // 是否正在生成标题 + generatingTitleKey?: number | string | null; } export default memo((props) => { @@ -51,10 +65,13 @@ export default memo((props) => { editableNameOnBlur, concealTabHeader, destroyInactiveTabPane = false, + onGenerateTitle, + generatingTitleKey, } = props; const [internalTabs, setInternalTabs] = useState([]); const [internalActiveTab, setInternalActiveTab] = useState(null); const [editingTab, setEditingTab] = useState(); + const [editingValue, setEditingValue] = useState(''); const tabListBoxRef = useRef(null); const tabsNavRef = useRef(null); const isNumberKey = useRef(false); @@ -155,19 +172,16 @@ export default memo((props) => { const onDoubleClick = (t: ITabItem) => { if (t.editableName) { + setEditingValue(getInitialEditValue(t)); setEditingTab(t.key); } }; const renderTabItem = (t: ITabItem, index: number) => { - function inputOnChange(value: string) { - internalTabs[index].label = value; - setInternalTabs([...internalTabs]); - } - function onBlur() { - editableNameOnBlur?.(t); + editableNameOnBlur?.(t, editingValue); setEditingTab(undefined); + setEditingValue(''); } function showClosed() { @@ -180,7 +194,7 @@ export default memo((props) => { return true; } - const closeTabsMenu = [ + const closeTabsMenu: any[] = [ { label: i18n('common.button.close'), key: 'close', @@ -204,6 +218,21 @@ export default memo((props) => { }, ]; + // 如果支持生成标题,添加生成标题选项 + if (t.canGenerateTitle && onGenerateTitle) { + closeTabsMenu.push({ + type: 'divider', + }); + closeTabsMenu.push({ + label: generatingTitleKey === t.key ? i18n('common.text.generatingTitle') : i18n('common.text.generateTitle'), + key: 'generateTitle', + disabled: generatingTitleKey === t.key, + onClick: () => { + onGenerateTitle(t); + }, + }); + } + return ( @@ -216,9 +245,9 @@ export default memo((props) => { > {t.key === editingTab ? ( { - inputOnChange(e.target.value); + setEditingValue(e.target.value); }} className={styles.input} autoFocus diff --git a/chat2db-client/src/components/TaskCenter/index.less b/chat2db-client/src/components/TaskCenter/index.less new file mode 100644 index 000000000..fea309dfb --- /dev/null +++ b/chat2db-client/src/components/TaskCenter/index.less @@ -0,0 +1,51 @@ +.taskCenter { + display: flex; + flex-direction: column; + height: 100%; + background-color: var(--color-bg-container); +} + +.header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px 24px; + border-bottom: 1px solid var(--color-border-secondary); +} + +.title { + margin: 0; + font-size: 16px; + font-weight: 500; + color: var(--color-text); +} + +.actions { + display: flex; + align-items: center; + gap: 8px; +} + +.content { + flex: 1; + overflow: auto; + padding: 16px 24px; +} + +.taskName { + display: block; + width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.progressCell { + width: 100%; + + :global { + .ant-progress { + margin-bottom: 0; + } + } +} diff --git a/chat2db-client/src/components/TaskCenter/index.tsx b/chat2db-client/src/components/TaskCenter/index.tsx new file mode 100644 index 000000000..f4efd02fd --- /dev/null +++ b/chat2db-client/src/components/TaskCenter/index.tsx @@ -0,0 +1,227 @@ +import React, { useEffect, useMemo, useRef, useState } from 'react'; + +import { ClearOutlined, DownloadOutlined, SyncOutlined } from '@ant-design/icons'; +import { Button, Empty, Popconfirm, Progress, Table, Tag, Tooltip, message } from 'antd'; +import { ColumnsType } from 'antd/es/table'; + +import i18n from '@/i18n'; +import { getConnectionList, useConnectionStore } from '@/pages/main/store/connection'; +import taskService, { ITask } from '@/service/task'; + +import styles from './index.less'; + +const statusMap: Record = { + INIT: { color: 'default', text: i18n('workspace.taskCenter.status.pending') }, + PROCESSING: { color: 'processing', text: i18n('workspace.taskCenter.status.running') }, + RUNNING: { color: 'processing', text: i18n('workspace.taskCenter.status.running') }, + FINISH: { color: 'success', text: i18n('workspace.taskCenter.status.finish') }, + ERROR: { color: 'error', text: i18n('workspace.taskCenter.status.error') }, +}; + +const typeMap: Record = { + DOWNLOAD_TABLE_DATA: i18n('workspace.taskCenter.type.export'), + UPLOAD_TABLE_DATA: i18n('workspace.taskCenter.type.import'), + EXECUTE_SQL: i18n('workspace.taskCenter.type.executeSql'), + GENERATE_DATA: i18n('workspace.taskCenter.type.generateData'), + DOWNLOAD_TABLE_STRUCTURE: i18n('workspace.taskCenter.type.exportSchema'), + TRANSFER_TABLE_DATA: i18n('workspace.taskCenter.type.transfer'), +}; + +const TaskCenter: React.FC = () => { + const [tasks, setTasks] = useState([]); + const [loading, setLoading] = useState(false); + const [cleanupLoading, setCleanupLoading] = useState(false); + const connectionList = useConnectionStore((state) => state.connectionList); + const pollingRef = useRef(null); + + const dataSourceNameMap = useMemo(() => { + return new Map((connectionList || []).map((connection) => [connection.id, connection.alias])); + }, [connectionList]); + + const fetchTasks = async () => { + setLoading(true); + try { + const result: any = await taskService.getTaskList({}); + if (result?.data && Array.isArray(result.data)) { + setTasks(result.data); + } else if (Array.isArray(result)) { + setTasks(result); + } else { + setTasks([]); + } + } catch (error) { + console.error('Failed to fetch tasks:', error); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + fetchTasks(); + getConnectionList(); + pollingRef.current = setInterval(fetchTasks, 3000); + return () => { + if (pollingRef.current) { + clearInterval(pollingRef.current); + } + }; + }, []); + + const handleDownload = (taskId: number) => { + const downloadUrl = `${window._BaseURL}/api/task/download/${taskId}`; + const link = document.createElement('a'); + link.href = downloadUrl; + link.style.display = 'none'; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + }; + + const handleCleanup = async () => { + setCleanupLoading(true); + try { + const count = await taskService.cleanupTasks({}); + message.success(i18n('workspace.taskCenter.cleanupSuccess', count || 0)); + await fetchTasks(); + } catch (error) { + console.error('Failed to cleanup tasks:', error); + } finally { + setCleanupLoading(false); + } + }; + + const columns: ColumnsType = [ + { + title: i18n('workspace.taskCenter.table'), + dataIndex: 'taskName', + key: 'taskName', + ellipsis: true, + render: (text) => ( + + {text || '-'} + + ), + }, + { + title: i18n('workspace.taskCenter.type'), + dataIndex: 'taskType', + key: 'taskType', + width: 120, + render: (type) => {typeMap[type] || type}, + }, + { + title: i18n('workspace.taskCenter.status'), + dataIndex: 'taskStatus', + key: 'taskStatus', + width: 100, + render: (status) => { + const config = statusMap[status] || { color: 'default', text: status }; + return {config.text}; + }, + }, + { + title: i18n('workspace.taskCenter.progress'), + dataIndex: 'taskProgress', + key: 'taskProgress', + width: 150, + render: (progress: string, record: ITask) => { + const percent = record.taskStatus === 'FINISH' ? 100 : parseInt(progress || '0', 10); + return ( +
+ +
+ ); + }, + }, + { + title: i18n('workspace.taskCenter.database'), + dataIndex: 'databaseName', + key: 'databaseName', + width: 120, + ellipsis: true, + render: (text) => text || '-', + }, + { + title: i18n('workspace.taskCenter.dataSource'), + dataIndex: 'dataSourceId', + key: 'dataSourceId', + width: 100, + ellipsis: true, + render: (id) => (id ? dataSourceNameMap.get(id) || `#${id}` : '-'), + }, + { + title: i18n('workspace.taskCenter.download'), + key: 'action', + width: 100, + render: (_, record: ITask) => ( + + ), + }, + ]; + + return ( +
+
+

{i18n('workspace.taskCenter.title')}

+
+ + + + +
+
+
+
, + }} + /> + + + ); +}; + +export default TaskCenter; diff --git a/chat2db-client/src/components/UploadDriver/index.tsx b/chat2db-client/src/components/UploadDriver/index.tsx index b685016e7..f298fcd19 100644 --- a/chat2db-client/src/components/UploadDriver/index.tsx +++ b/chat2db-client/src/components/UploadDriver/index.tsx @@ -14,7 +14,7 @@ interface IProps { jdbcDriverClass: string | undefined; } -export default memo(function UploadDriver(props) { +export default memo((props) => { const { className, databaseType = DatabaseTypeCode.MYSQL, formChange, jdbcDriverClass } = props; const [formData, setFormData] = useState({ dbType: databaseType, diff --git a/chat2db-client/src/constants/common.ts b/chat2db-client/src/constants/common.ts index b6f2339be..307e47a56 100644 --- a/chat2db-client/src/constants/common.ts +++ b/chat2db-client/src/constants/common.ts @@ -10,11 +10,13 @@ export enum DatabaseTypeCode { SQLITE = 'SQLITE', MARIADB = 'MARIADB', CLICKHOUSE = 'CLICKHOUSE', - DM = "DM", - OCEANBASE = "OCEANBASE", - PRESTO = "PRESTO", - HIVE = "HIVE", - KINGBASE = "KINGBASE", + DLC = 'DLC', + DM = 'DM', + OCEANBASE = 'OCEANBASE', + PRESTO = 'PRESTO', + HIVE = 'HIVE', + KINGBASE = 'KINGBASE', + PHOENIX = 'PHOENIX', // 添加 Phoenix } export enum ConsoleStatus { @@ -30,7 +32,7 @@ export enum OSType { export enum ConnectionKind { Private = 'PRIVATE', - Shared = 'SHARED' + Shared = 'SHARED', } // 通用的增删改查枚举 diff --git a/chat2db-client/src/constants/database.ts b/chat2db-client/src/constants/database.ts index ba7c3c75c..b28b8da20 100644 --- a/chat2db-client/src/constants/database.ts +++ b/chat2db-client/src/constants/database.ts @@ -2,8 +2,9 @@ import mysqlLogo from '@/assets/img/databaseImg/mysql.png'; import redisLogo from '@/assets/img/databaseImg/redis.png'; import h2Logo from '@/assets/img/databaseImg/h2.png'; import moreDBLogo from '@/assets/img/databaseImg/other.png'; +import phoenixLogo from '@/assets/img/databaseImg/phoenixLogo.png'; import { IDatabase } from '@/typings'; -import { DatabaseTypeCode } from '@/constants' +import { DatabaseTypeCode } from '@/constants'; export enum ConnectionEnvType { DAILY = 'DAILY', @@ -69,6 +70,12 @@ export const databaseMap: { // port: 8123, icon: '\ue8f4', }, + [DatabaseTypeCode.DLC]: { + name: 'Tencent DLC', + img: moreDBLogo, + code: DatabaseTypeCode.DLC, + icon: '\ue60b', + }, [DatabaseTypeCode.DM]: { name: 'DM', img: moreDBLogo, @@ -94,7 +101,7 @@ export const databaseMap: { name: 'OceanBase', img: moreDBLogo, code: DatabaseTypeCode.OCEANBASE, - // port: 2883, + // port: 2883, icon: '\ue982', }, [DatabaseTypeCode.HIVE]: { @@ -118,13 +125,21 @@ export const databaseMap: { // port: 27017, icon: '\uec21', }, - // [DatabaseTypeCode.REDIS]: { - // name: 'Redis', - // img: moreDBLogo, - // code: DatabaseTypeCode.REDIS, - // // port: 6379, - // icon: '\ue6a2', - // }, + [DatabaseTypeCode.REDIS]: { + name: 'Redis', + img: redisLogo, + code: DatabaseTypeCode.REDIS, + // port: 6379, + icon: '\ue6a2', + }, + [DatabaseTypeCode.PHOENIX]: { + // 添加 Phoenix + name: 'Phoenix', + img: phoenixLogo, // 确保你有 phoenixLogo 的定义 + code: DatabaseTypeCode.PHOENIX, + // port: 8765, // 根据需要添加端口 + icon: '\ue712', // 根据需要选择合适的图标 + }, }; export const databaseTypeList = Object.keys(databaseMap).map((keys) => { diff --git a/chat2db-client/src/constants/tree.ts b/chat2db-client/src/constants/tree.ts index 82ee247a9..470c156b1 100644 --- a/chat2db-client/src/constants/tree.ts +++ b/chat2db-client/src/constants/tree.ts @@ -19,8 +19,16 @@ export enum TreeNodeType { FUNCTION = 'function', // 函数 PROCEDURES = 'procedures', // procedure组 PROCEDURE = 'procedure', // procedure - TRIGGERS = 'triggers', // trigger组 - TRIGGER = 'trigger', // trigger + TRIGGERS = 'triggers', // trigger组 + TRIGGER = 'trigger', // trigger + V_KEYS = 'vKeys', + V_KEY = 'vKey', + DEPRECATED_TABLES = 'deprecatedTables', + DEPRECATED_TABLE = 'deprecatedTable', + REDIS_DATA = 'redisData', + REDIS_QUERY = 'redisQuery', + REDIS_MONITOR = 'redisMonitor', + REDIS_BACKUP = 'redisBackup', } // 树右键支持的功能 @@ -45,4 +53,20 @@ export enum OperationColumn { CreateSchema = 'createSchema', // 新建schema CreateDatabase = 'createDatabase', // 新建database ViewAllTable = 'viewAllTable', // 查看所有的表 + ViewERDiagram = 'viewERDiagram', // 查看 ER 图 + ViewTableRelation = 'viewTableRelation', // 查看表关系 + DeleteVirtualKey = 'deleteVirtualKey', // 删除虚拟外键 + TruncateTable = 'truncateTable', // 截断表 + ImportData = 'importData', // 导入数据 + ExportData = 'exportData', // 导出数据 + DataTransfer = 'dataTransfer', // 数据传输 + ExportSchemaDoc = 'exportSchemaDoc', // 导出数据结构 + ExecuteSqlStatement = 'executeSqlStatement', // 执行sql语句 + DeprecatedTable = 'deprecatedTable', // 废弃表 + RestoreTable = 'restoreTable', // 恢复废弃表 + GenerateData = 'generateData', // 生成数据 + SchemaDiff = 'schemaDiff', // 结构对比 + DeleteDatabase = 'deleteDatabase', // 删除数据库 + OpenRedisData = 'openRedisData', // 打开 Redis 数据视图 + OpenRedisMonitor = 'openRedisMonitor', // 打开 Redis 命令监控 } diff --git a/chat2db-client/src/constants/workspace.ts b/chat2db-client/src/constants/workspace.ts index c3fac86e3..2469287dd 100644 --- a/chat2db-client/src/constants/workspace.ts +++ b/chat2db-client/src/constants/workspace.ts @@ -15,6 +15,10 @@ export enum WorkspaceTabType { CreateTable = 'createTable', EditTableData = 'editTableData', ViewAllTable = 'viewAllTable', + ViewERDiagram = 'viewERDiagram', // 添加查看 ER 图的类型 + SchemaDiff = 'schemaDiff', + RedisData = 'redisData', + RedisMonitor = 'redisMonitor', } // 工作台Tab的类型对应的一些配置 @@ -50,5 +54,17 @@ export const workspaceTabConfig: { [WorkspaceTabType.ViewAllTable]: { icon: '\ue611' }, + [WorkspaceTabType.ViewERDiagram]: { + icon: '\ue611' + }, + [WorkspaceTabType.SchemaDiff]: { + icon: '\ue6f3' + }, + [WorkspaceTabType.RedisData]: { + icon: '\ue618' + }, + [WorkspaceTabType.RedisMonitor]: { + icon: '\ue611' + }, } diff --git a/chat2db-client/src/i18n/en-us/chat.ts b/chat2db-client/src/i18n/en-us/chat.ts index 80079d764..f30615f97 100644 --- a/chat2db-client/src/i18n/en-us/chat.ts +++ b/chat2db-client/src/i18n/en-us/chat.ts @@ -1,10 +1,21 @@ export default { 'chat.input.remain': '{1} remaining', - 'chat.input.tableSelect.placeholder': 'Please choose tables', - 'chat.input.tableSelect.error.TooManyTable': 'You can only select up to 8 tables', - 'chat.input.remain.dialog.tips': - 'Subscribe our official WeChat account, send 推广 to get more chances to experience.', - 'chat.input.syncTable.tips': 'The automatically synchronize all table structures to the AI context', - 'chat.input.remain.tooltip': 'The manually selected table will be synchronized to the AI context', - 'chat.input.syncTable.tempTips': '🎉Update: Automatically synchronize all table structures to the AI context', + 'chat.input.tableSelect.placeholder': 'Select tables', + 'chat.input.tableSelect.error.TooManyTable': 'Select at most 8 tables', + 'chat.input.remain.dialog.tips': 'Follow the official account, send "promotion" to get more experience', + 'chat.input.syncTable.tips': 'Auto-sync all table structures to AI context (apply for Chat2DBAI whitelist, Chat2DBAI only)', + 'chat.input.remain.tooltip': 'Manually selected tables will be synced to AI context', + 'chat.input.syncTable.tempTips': 'Auto-sync all table structures to AI context', + 'chat.sidebar.title': 'Conversations', + 'chat.sidebar.new': 'New Chat', + 'chat.sidebar.empty': 'No conversations yet, click new to start', + 'chat.sidebar.delete': 'Delete', + 'chat.sidebar.rename': 'Rename', + 'chat.sidebar.collapse': 'Collapse sidebar', + 'chat.sidebar.expand': 'Expand sidebar', + 'chat.sidebar.delete.confirm': 'Delete this conversation?', + 'chat.sidebar.rename.placeholder': 'Enter a new title', + 'chat.header.newChat': 'New Chat', + 'chat.header.titleFallback': 'New Chat', + 'chat.switch.confirm': 'A response is generating. Switch anyway?', }; diff --git a/chat2db-client/src/i18n/en-us/common.ts b/chat2db-client/src/i18n/en-us/common.ts index 5eef2d3e0..08808489e 100644 --- a/chat2db-client/src/i18n/en-us/common.ts +++ b/chat2db-client/src/i18n/en-us/common.ts @@ -27,6 +27,8 @@ export default { 'common.button.execute': 'Run', "common.button.import": 'Import SQL', 'common.button.format': 'Format', + 'common.button.guess': 'Guess', + 'common.button.aiFix': 'AI Fix', 'common.message.successfulConfig': 'Successful configuration', 'common.text.successful': 'successful', 'common.text.failure': 'failure', @@ -70,6 +72,12 @@ export default { 'Follow the wechat public account and send "AI" to get the ApiKey for free, and give away the number of experiences.', 'common.text.wechatPopularize': 'You can also send "promotion" to get more experiences for free.', 'common.text.export': 'Export', + 'common.text.exportFailed': 'Export failed', + 'common.text.importFailed': 'Import failed', + 'common.text.importColumnMismatch': 'File column count does not match table column count', + 'common.text.importSuccess': 'Import success', + 'common.text.importing': 'Importing...', + 'common.text.selectFile': 'Select file', 'common.notification.detail': 'More details', 'common.notification.solution': 'Solution', 'common.button.copyError': 'Copy error report', @@ -92,9 +100,12 @@ export default { 'common.button.cancelRequest': 'Cancel Request', 'common.button.executionError': 'Execution Error', 'common.text.affectedRows': 'Affected rows: {1}', - 'common.text.selectFile' : 'Select File', - 'common.text.noTableFoundUp' : 'No tables in this database', + 'common.text.selectFile': 'Select File', + 'common.text.noTableFoundUp': 'No tables in this database', 'common.text.noTableFoundDown': 'Switch databases at the top', + 'common.text.noTables': 'No tables to guess comments', + 'common.text.aiCommentGenerated': 'AI comments generated and saved to index', + 'common.text.allTablesHaveComments': 'All tables already have comments', 'common.title.preview': 'Preview', 'common.title.errorMessage': 'Error message', 'common.label.comment': 'Comment', @@ -120,4 +131,26 @@ export default { 'common.label.LocalFile': 'LocalFile', 'common.text.rename': 'Rename', 'common.title.info': 'Info', -}; + 'common.button.saveAll': 'Save All', + 'common.button.editAll': 'Edit All', + 'common.text.generateTitle': 'AI Generate Title', + 'common.text.generatingTitle': 'Generating Title...', + 'common.viewAllTable.batchDeprecated': 'Batch Deprecate Tables', + 'common.viewAllTable.noSelectedTables': 'Please select tables to deprecate first', + 'common.viewAllTable.batchDeprecatedConfirmTitle': 'Confirm Batch Deprecate Tables', + 'common.viewAllTable.batchDeprecatedConfirmContent': 'Are you sure to deprecate the selected {1} table(s)?', + 'common.viewAllTable.batchDeprecatedSuccess': 'Successfully deprecated {1} table(s)', + 'common.viewAllTable.batchDeprecatedPartialSuccess': 'Deprecated {1} table(s) successfully, {2} failed', + 'common.viewAllTable.batchOptimize': 'Batch Optimize Tables', + 'common.viewAllTable.noSelectedTablesForOptimize': 'Please select tables to optimize first', + 'common.viewAllTable.batchOptimizeConfirmTitle': 'Confirm Batch Optimize Tables', + 'common.viewAllTable.batchOptimizeConfirmContent': 'Are you sure to execute OPTIMIZE TABLE on the selected {1} table(s)?', + 'common.viewAllTable.batchOptimizeSuccess': 'Successfully optimized {1} table(s)', + 'common.viewAllTable.batchOptimizePartialSuccess': 'Optimized {1} table(s) successfully, {2} failed', + 'common.viewAllTable.batchAnalyze': 'Batch Analyze Tables', + 'common.viewAllTable.noSelectedTablesForAnalyze': 'Please select tables to analyze first', + 'common.viewAllTable.batchAnalyzeConfirmTitle': 'Confirm Batch Analyze Tables', + 'common.viewAllTable.batchAnalyzeConfirmContent': 'Are you sure to execute ANALYZE TABLE on the selected {1} table(s)?', + 'common.viewAllTable.batchAnalyzeSuccess': 'Successfully analyzed {1} table(s)', + 'common.viewAllTable.batchAnalyzePartialSuccess': 'Analyzed {1} table(s) successfully, {2} failed', +}; diff --git a/chat2db-client/src/i18n/en-us/connection.ts b/chat2db-client/src/i18n/en-us/connection.ts index 6773af007..1a8188538 100644 --- a/chat2db-client/src/i18n/en-us/connection.ts +++ b/chat2db-client/src/i18n/en-us/connection.ts @@ -16,6 +16,10 @@ export default { 'connection.button.addConnection': 'Add Connection', 'connection.button.connect': 'Connect', 'connection.button.remove': 'Remove', + 'connection.group.unknown': 'Ungrouped', + 'connection.sort.manual': 'Manual order', + 'connection.sort.asc': 'Sort ascending', + 'connection.sort.desc': 'Sort descending', 'connection.message.testConnectResult': 'Test connection is {1}', 'connection.message.testSshConnection': 'Test the ssh connection', 'connection.tableHeader.name': 'Name', diff --git a/chat2db-client/src/i18n/en-us/editTable.ts b/chat2db-client/src/i18n/en-us/editTable.ts index 04cc2404d..64e8feae7 100644 --- a/chat2db-client/src/i18n/en-us/editTable.ts +++ b/chat2db-client/src/i18n/en-us/editTable.ts @@ -35,4 +35,23 @@ export default { 'editTable.title.sqlPreview': 'SQL preview', 'editTable.button.addColumn': 'Add column', 'editTable.button.addIndex': 'Add Index', + 'editTable.tab.foreignKeyInfo': 'Foreign Key', + 'editTable.label.foreignKeyName': 'Foreign Key Name', + 'editTable.label.column': 'Column', + 'editTable.label.referencedTable': 'Referenced Table', + 'editTable.label.referencedColumn': 'Referenced Column', + 'editTable.label.updateRule': 'Update Rule', + 'editTable.label.deleteRule': 'Delete Rule', + 'editTable.button.addForeignKey': 'Add Foreign Key', + 'editTable.button.syncForeignKeys': 'Sync', + 'editTable.label.sourceType': 'Type', + 'editTable.tooltip.syncFK': 'Sync foreign keys from database', + 'editTable.tooltip.virtualFK': 'Virtual foreign key', + 'editTable.tooltip.realFK': 'Real foreign key', + 'editTable.message.syncFKWarning': 'Missing required table information', + 'editTable.message.syncFKSuccess': 'Synced: {1} added, {2} deleted, {3} unchanged', + 'editTable.message.syncFKError': 'Failed to sync foreign keys', + 'editTable.option.cascade': 'CASCADE', + 'editTable.option.setNull': 'SET NULL', + 'editTable.option.noAction': 'NO ACTION', }; diff --git a/chat2db-client/src/i18n/en-us/index.ts b/chat2db-client/src/i18n/en-us/index.ts index ca3ea0641..b91e1f5ec 100644 --- a/chat2db-client/src/i18n/en-us/index.ts +++ b/chat2db-client/src/i18n/en-us/index.ts @@ -10,6 +10,7 @@ import login from './login'; import editTable from './editTable'; import editTableData from './editTableData'; import sqlEditor from './sqlEditor' +import schemaDiff from './schemaDiff'; export default { lang: 'en', @@ -24,5 +25,6 @@ export default { ...login, ...editTable, ...editTableData, - ...sqlEditor + ...sqlEditor, + ...schemaDiff, }; diff --git a/chat2db-client/src/i18n/en-us/menu.ts b/chat2db-client/src/i18n/en-us/menu.ts index 74ab40b68..259232ef8 100644 --- a/chat2db-client/src/i18n/en-us/menu.ts +++ b/chat2db-client/src/i18n/en-us/menu.ts @@ -1,3 +1,3 @@ export default { 'menu.file' : 'File' -} \ No newline at end of file +} diff --git a/chat2db-client/src/i18n/en-us/schemaDiff.ts b/chat2db-client/src/i18n/en-us/schemaDiff.ts new file mode 100644 index 000000000..e17cb2136 --- /dev/null +++ b/chat2db-client/src/i18n/en-us/schemaDiff.ts @@ -0,0 +1,74 @@ +export default { + 'schemaDiff.title': 'Schema Diff', + 'schemaDiff.source': 'Source', + 'schemaDiff.target': 'Target', + 'schemaDiff.compare': 'Compare', + 'schemaDiff.comparing': 'Comparing...', + 'schemaDiff.compareOptions': 'Compare Options', + 'schemaDiff.compareColumn': 'Compare Columns', + 'schemaDiff.compareIndex': 'Compare Indexes', + 'schemaDiff.compareForeignKey': 'Compare Foreign Keys', + 'schemaDiff.compareTableOption': 'Compare Table Options', + 'schemaDiff.excludeDeprecated': 'Exclude Deprecated Tables', + 'schemaDiff.caseSensitive': 'Case Sensitive', + 'schemaDiff.ignoreCharsetAlias': 'Ignore Charset Aliases', + 'schemaDiff.ignoreIntegerDisplayWidth': 'Ignore Integer Display Width', + 'schemaDiff.ignoreAutoIncrement': 'Ignore Auto Increment', + 'schemaDiff.summary': 'Summary', + 'schemaDiff.totalTables': 'Total Tables', + 'schemaDiff.tablesAdded': 'Added', + 'schemaDiff.tablesRemoved': 'Removed', + 'schemaDiff.tablesModified': 'Modified', + 'schemaDiff.tablesUnchanged': 'Unchanged', + 'schemaDiff.excluded': 'Deprecated Excluded', + 'schemaDiff.columns': 'Columns', + 'schemaDiff.indexes': 'Indexes', + 'schemaDiff.foreignKeys': 'Foreign Keys', + 'schemaDiff.noChanges': 'No differences found', + 'schemaDiff.table': 'Table', + 'schemaDiff.diffType': 'Change Type', + 'schemaDiff.added': 'Added', + 'schemaDiff.removed': 'Removed', + 'schemaDiff.modified': 'Modified', + 'schemaDiff.unchanged': 'Unchanged', + 'schemaDiff.ddlPreview': 'DDL Preview', + 'schemaDiff.selectAll': 'Select All', + 'schemaDiff.deselectAll': 'Deselect All', + 'schemaDiff.migrate': 'Execute Migration', + 'schemaDiff.migrating': 'Executing...', + 'schemaDiff.migrateConfirm': 'Execute {count} DDL statements?', + 'schemaDiff.migrateConfirmMessage': '{count} statements will be executed against the target database. This action cannot be undone.', + 'schemaDiff.migrateResult': 'Migration Result', + 'schemaDiff.migrateSuccess': 'Migration Successful', + 'schemaDiff.migrateFail': 'Migration Failed', + 'schemaDiff.successCount': 'Success', + 'schemaDiff.failCount': 'Failed', + 'schemaDiff.continueOnError': 'Continue on Error', + 'schemaDiff.executeInTransaction': 'Execute in Transaction', + 'schemaDiff.selectSource': 'Select Source Datasource', + 'schemaDiff.selectTarget': 'Select Target Datasource', + 'schemaDiff.selectDatabase': 'Select Database', + 'schemaDiff.selectSchema': 'Select Schema', + 'schemaDiff.structuralView': 'Structural View', + 'schemaDiff.ddlView': 'DDL View', + 'schemaDiff.columnName': 'Column Name', + 'schemaDiff.columnType': 'Type', + 'schemaDiff.nullable': 'Nullable', + 'schemaDiff.defaultValue': 'Default Value', + 'schemaDiff.comment': 'Comment', + 'schemaDiff.operation': 'Operation', + 'schemaDiff.viewDiff': 'Diff', + 'schemaDiff.diffProperties': 'Diff Properties', + 'schemaDiff.diffField': 'Property', + 'schemaDiff.sourceValue': 'Source Value', + 'schemaDiff.targetValue': 'Target Value', + 'schemaDiff.tableOptions': 'Table Options', + 'schemaDiff.indexName': 'Index Name', + 'schemaDiff.indexType': 'Index Type', + 'schemaDiff.unique': 'Unique', + 'schemaDiff.foreignKeyName': 'FK Name', + 'schemaDiff.referencedTable': 'Referenced Table', + 'schemaDiff.referencedColumn': 'Referenced Column', + 'schemaDiff.loadDatabaseFail': 'Failed to load databases', + 'schemaDiff.loadSchemaFail': 'Failed to load schemas', +} diff --git a/chat2db-client/src/i18n/en-us/setting.ts b/chat2db-client/src/i18n/en-us/setting.ts index 59cce6f72..9a4196823 100644 --- a/chat2db-client/src/i18n/en-us/setting.ts +++ b/chat2db-client/src/i18n/en-us/setting.ts @@ -17,10 +17,6 @@ export default { 'setting.title.language': 'Language', 'setting.title.aiSource': 'AI Source', 'setting.tab.custom': 'Custom', - 'setting.tab.aiType.zhipu': 'ZhiPu AI', - 'setting.tab.aiType.baichuan': 'BaiChuan AI', - 'setting.tab.aiType.wenxin': 'WenXin AI', - 'setting.tab.aiType.tongyiqianwen': 'TongYiQianWen AI', 'setting.tab.aiType.custom.tips': "The API format is consistent with the OpenAI API format", 'setting.label.serviceAddress': 'Service Address', 'setting.button.apply': 'Apply', diff --git a/chat2db-client/src/i18n/en-us/team.ts b/chat2db-client/src/i18n/en-us/team.ts index 2ec485101..12b18c431 100644 --- a/chat2db-client/src/i18n/en-us/team.ts +++ b/chat2db-client/src/i18n/en-us/team.ts @@ -54,4 +54,4 @@ export default { 'team.team.addForm.description': 'Description', -} \ No newline at end of file +} diff --git a/chat2db-client/src/i18n/en-us/workspace.ts b/chat2db-client/src/i18n/en-us/workspace.ts index d81ca0556..2bc659590 100644 --- a/chat2db-client/src/i18n/en-us/workspace.ts +++ b/chat2db-client/src/i18n/en-us/workspace.ts @@ -3,6 +3,12 @@ export default { 'workspace.cascader.placeholder': 'Select Here', 'workspace.ai.input.placeholder': 'Enter your plain text statement here', 'workspace.title.savedConsole': 'Saved console', + 'workspace.savedConsole.importSql': 'Import SQL file', + 'workspace.savedConsole.exportSql': 'Export SQL file', + 'workspace.savedConsole.import.success': 'SQL file imported', + 'workspace.savedConsole.import.emptyFile': 'SQL file is empty', + 'workspace.savedConsole.import.onlySql': 'Only .sql files can be imported', + 'workspace.title.aiChat': 'AI Chat', 'workspace.menu.ViewDDL': 'View DDL', 'workspace.menu.deleteTable': 'Delete Table', 'workspace.menu.openTable': 'Open Table', @@ -13,9 +19,95 @@ export default { 'workspace.menu.editTableData': 'Edit Table Data', 'workspace.menu.queryConsole': 'Query console', 'workspace.menu.viewAllTable': 'View all table', + 'workspace.menu.viewERDiagram': 'View ER Diagram', + 'workspace.menu.viewTableRelation': 'View Table Relations', + 'workspace.erDiagram.refresh': 'Refresh', + 'workspace.erDiagram.hierarchical': 'Hierarchical', + 'workspace.erDiagram.forceLayout': 'Force', + 'workspace.erDiagram.inferVirtualFk': 'Infer Virtual FK', + 'workspace.erDiagram.inferVirtualFkSuccess': 'Successfully inferred {1} virtual foreign key(s)', + 'workspace.erDiagram.inferVirtualFkNoResult': 'No new virtual foreign keys found', + 'workspace.erDiagram.inferVirtualFkError': 'Failed to infer virtual foreign keys', + 'workspace.erDiagram.inferResult.title': 'Virtual FK Inference Result', + 'workspace.erDiagram.inferResult.added': 'Added ({1})', + 'workspace.erDiagram.inferResult.deleted': 'Removed ({1})', + 'workspace.erDiagram.confirmDeleteVirtualFk': 'Are you sure you want to delete the virtual foreign key "{1}"?', + 'workspace.erDiagram.suggestionHint': 'Detected potential table relationships based on your SQL:', + 'workspace.erDiagram.zoomIn': 'Zoom In', + 'workspace.erDiagram.zoomOut': 'Zoom Out', + 'workspace.erDiagram.fitView': 'Fit View', + 'workspace.erDiagram.export': 'Export PNG', + 'workspace.erDiagram.filterPlaceholder': 'Filter tables...', + 'workspace.erDiagram.showOnlyRelatedTables': 'Show only related tables', + 'workspace.erDiagram.legend': 'Legend', + 'workspace.erDiagram.realFk': 'Foreign Key', + 'workspace.erDiagram.virtualFk': 'Virtual Foreign Key', + 'workspace.erDiagram.loading': 'Loading ER diagram...', + 'workspace.erDiagram.noData': 'No table relationship data found', + 'workspace.erDiagram.copyTableName': 'Copy table name', + 'workspace.erDiagram.copyTableNameSuccess': 'Table name copied', + 'workspace.erDiagram.expandColumns': 'Expand columns', + 'workspace.erDiagram.collapseColumns': 'Collapse columns', + 'workspace.erDiagram.loadColumnsError': 'Failed to load columns', + 'workspace.erDiagram.createVirtualFk': 'Create virtual foreign key', + 'workspace.erDiagram.createVirtualFkSuccess': 'Virtual foreign key created', + 'workspace.erDiagram.createVirtualFkError': 'Failed to create virtual foreign key', + 'workspace.erDiagram.invalidVirtualFkConnection': + 'Drag from the field right green handle to the target field left green handle', + 'workspace.erDiagram.invalidVirtualFkSameField': 'Cannot create a virtual foreign key on the same field', + 'workspace.erDiagram.virtualFkConnectHint': + 'Create virtual FK: drag from the foreign-key field right green dot to the referenced field left green dot. Dragging field text does not create a relation.', + 'workspace.erDiagram.virtualFkSourceHandle': 'Drag from here: foreign key field', + 'workspace.erDiagram.virtualFkTargetHandle': 'Drop here: referenced field', + 'workspace.erDiagram.createJoinQuery': 'Create query', 'workspace.menu.createDatabase': 'Create database', + 'workspace.menu.deleteDatabase': 'Delete Database', 'workspace.menu.createSchema': 'Create schema', + 'workspace.menu.deleteVirtualKey': 'Delete Virtual Key', + 'workspace.menu.dataOperation': 'Data Operation', + 'workspace.menu.importData': 'Import Data', + 'workspace.menu.exportData': 'Export Data', + 'workspace.menu.generateData': 'Generate Data', + 'workspace.menu.exportSchemaDoc': 'Export Schema Doc', + 'workspace.menu.executeSqlStatement': 'Execute SQL Statement', + 'workspace.menu.truncateTable': 'Truncate Table', + 'workspace.menu.deprecatedTable': 'Deprecate Table', + 'workspace.menu.restoreTable': 'Restore Deprecated Table', 'workspace.menu.deleteTablePlaceHolder': 'Please enter the name of the table you want to delete', + 'workspace.tableRelation.masterTable': 'Master Table', + 'workspace.tableRelation.uniqueColumn': 'Unique Column', + 'workspace.tableRelation.childTable': 'Child Table', + 'workspace.tableRelation.relationColumn': 'Relation Field', + 'workspace.tableRelation.operation': 'Operation', + 'workspace.tableRelation.searchPlaceholder': 'Search table, field, source, or comment', + 'workspace.tableRelation.loadError': 'Failed to load table relations', + 'workspace.tableRelation.missingId': 'Missing foreign key ID, cannot delete', + 'workspace.tableRelation.deleteConfirmTitle': 'Delete Foreign Key', + 'workspace.tableRelation.deleteVirtualConfirmContent': 'Delete this virtual foreign key?', + 'workspace.tableRelation.deleteRealConfirmContent': + 'Delete this real foreign key record? The required DDL will be returned.', + 'workspace.tableRelation.realDeleteDDLTitle': 'Drop Real Foreign Key DDL', + 'workspace.tableRelation.deleteSuccess': 'Foreign key deleted', + 'workspace.tableRelation.createSuccess': 'Foreign key added', + 'workspace.tableRelation.createError': 'Failed to add foreign key', + 'workspace.tableRelation.updateSuccess': 'Virtual foreign key updated', + 'workspace.tableRelation.updateError': 'Failed to update virtual foreign key', + 'workspace.tableRelation.importVirtualFk': 'Import Virtual FK', + 'workspace.tableRelation.exportVirtualFk': 'Export Virtual FK', + 'workspace.tableRelation.importSuccess': 'Imported, created {1}, overwritten {2}', + 'workspace.tableRelation.importError': 'Failed to import virtual foreign keys. Check the JSON file.', + 'workspace.tableRelation.importEmpty': 'No importable virtual foreign keys found', + 'workspace.tableRelation.importInvalidTitle': 'Import does not match the current schema', + 'workspace.tableRelation.importInvalidContent': + '{1} relation(s) reference missing tables or fields. Import cancelled.', + 'workspace.tableRelation.exportSuccess': 'Virtual foreign keys exported', + 'workspace.tableRelation.exportEmpty': 'No virtual foreign keys to export', + 'workspace.tableRelation.noVirtualFk': 'No virtual foreign keys to delete', + 'workspace.tableRelation.deleteAllVirtualFk': 'Delete All Virtual FKs', + 'workspace.tableRelation.deleteAllVirtualConfirmTitle': 'Delete All Virtual FKs', + 'workspace.tableRelation.deleteAllVirtualConfirmContent': 'Delete {1} virtual foreign key(s) in the current list?', + 'workspace.tableRelation.deleteAllVirtualSuccess': 'All virtual foreign keys deleted', + 'workspace.tableRelation.deleteAllVirtualError': 'Failed to delete all virtual foreign keys', 'workspace.tips.affirmDeleteTable': 'The table name you entered is not the same as the table name you want to delete, please confirm again', 'workspace.table.total': 'Total', @@ -24,14 +116,116 @@ export default { 'workspace.table.export.cur.csv': 'Export result of current page set csv', 'workspace.table.export.all.insert': 'Export result set insert sql', 'workspace.table.export.cur.insert': 'Export result of current page set insert sql', + 'workspace.table.import.data': 'Import Data', + 'workspace.table.import.selectFile': 'Please select a file', + 'workspace.table.import.title': 'Import Data', + 'workspace.table.import.targetTable': 'Target Table', + 'workspace.table.import.fileType': 'File Type', + 'workspace.table.import.fileType.csv': 'CSV', + 'workspace.table.import.fileType.xlsx': 'XLSX', + 'workspace.table.import.fileType.xls': 'XLS', + 'workspace.table.import.fileType.sql': 'SQL', + 'workspace.table.import.uploadFile': 'Upload File', + 'workspace.table.import.uploadHint': 'Click or drag file here to upload', + 'workspace.table.import.start': 'Start', + 'workspace.table.import.cancel': 'Cancel', + 'workspace.table.import.progress.title': 'Importing...', + 'workspace.table.import.progress.log': 'Log Details', + 'workspace.table.import.progress.taskName': 'Task Name', + 'workspace.table.import.progress.startTime': 'Start Time', + 'workspace.table.import.progress.rows': 'Rows imported', + 'workspace.table.import.close': 'Close', + 'workspace.table.import.step.selectFile': 'Select File', + 'workspace.table.import.step.fieldMapping': 'Field Mapping', + 'workspace.table.import.step.importMode': 'Import Mode', + 'workspace.table.import.step.importProgress': 'Import Progress', + 'workspace.table.import.next': 'Next', + 'workspace.table.import.previous': 'Previous', + 'workspace.table.import.fieldMapping.title': 'Define Field Mapping', + 'workspace.table.import.fieldMapping.description': + 'You can define field mappings. Set mappings to specify the correspondence between source fields and target fields.', + 'workspace.table.import.fieldMapping.source': 'Source', + 'workspace.table.import.fieldMapping.targetTable': 'Target Table', + 'workspace.table.import.fieldMapping.sourceField': 'Source Field', + 'workspace.table.import.fieldMapping.targetField': 'Target Field', + 'workspace.table.import.fieldMapping.primaryKey': 'Primary Key', + 'workspace.table.import.fieldMapping.autoMatch': 'Auto Match', + 'workspace.table.import.fieldMapping.pleaseSelect': 'Please select', + 'workspace.table.import.fieldMapping.loading': 'Parsing file...', + 'workspace.table.import.mode.description': 'Please select an import mode', + 'workspace.table.import.mode.insert': 'Append (Add records to target table)', + 'workspace.table.import.mode.update': 'Update (Update existing records in target table)', + 'workspace.table.import.mode.upsert': 'Upsert (Update existing records, otherwise insert)', + 'workspace.table.import.mode.insertIgnore': 'Skip Existing (Skip if exists, otherwise insert)', + 'workspace.table.import.mode.delete': 'Delete (Delete existing records from target table)', + 'workspace.table.import.mode.replace': 'Replace (Clear all records, then import)', + 'workspace.table.import.mode.pkRequired': 'Primary key required', + 'workspace.table.import.mode.replaceConfirm': + 'This will clear all data in the target table and re-import. Are you sure?', + 'workspace.table.export.progress.title': 'Exporting...', + 'workspace.table.export.progress.rows': 'Rows exported', + 'workspace.table.export.title': 'Export Data', + 'workspace.table.export.sourceTable': 'Source Table', + 'workspace.table.export.fileType': 'Export Format', + 'workspace.table.export.sql': 'Export SQL', + 'workspace.table.export.start': 'Start Export', + 'workspace.table.export.cancel': 'Cancel', + 'workspace.table.export.success': 'Export successful', + 'workspace.table.export.failed': 'Export failed', + 'workspace.table.export.close': 'Close', + 'workspace.table.export.progress.log': 'Export Log', + 'workspace.table.export.progress.taskName': 'Task Name', + 'workspace.schemaDoc.export.title': 'Export Schema Doc', + 'workspace.schemaDoc.export.database': 'Database', + 'workspace.schemaDoc.export.fileType': 'Export Format', + 'workspace.schemaDoc.export.start': 'Start Export', + 'workspace.schemaDoc.export.cancel': 'Cancel', + 'workspace.schemaDoc.export.success': 'Export successful', + 'workspace.schemaDoc.export.failed': 'Export failed', + 'workspace.schemaDoc.export.close': 'Close', + 'workspace.schemaDoc.export.progress.log': 'Export Log', + 'workspace.schemaDoc.export.progress.taskName': 'Task Name', + 'workspace.schemaDoc.export.refreshLatest': 'Get Latest', + 'workspace.schemaDoc.export.refreshLatestTip': 'Fetch table structure from database in real-time (slower)', 'workspace.tree.view': 'View', 'workspace.tree.trigger': 'Trigger', 'workspace.tree.function': 'Function', 'workspace.tree.procedure': 'Procedure', + 'workspace.tree.expandDataSourceTree': 'Expand data source tree', 'workspace.tree.search.placeholder': 'Search in the expand node', 'workspace.tree.delete.tip': 'I understand that this operation is permanently deleted', 'workspace.tree.delete.table.tip': 'Are you sure you want to delete the table {1}?', + 'workspace.tree.delete.database.tip': + 'Are you sure you want to delete the database {1}? This operation cannot be undone.', 'workspace.tips.noConnection': 'You have not created a connection yet', 'workspace.tips.maxConsole': 'You can only open up to 20 consoles', 'workspace.tips.openExecutiveLogging': 'Open this executive logging', + 'workspace.tips.noSqlContent': 'No SQL content in current console', + 'workspace.tips.generateTitleFailed': 'Failed to generate title by AI, please try again', + 'workspace.tips.enterReleaseEnvironment': 'You have entered the production environment', + 'workspace.taskCenter.title': 'Task Center', + 'workspace.taskCenter.empty': 'No tasks yet', + 'workspace.taskCenter.type.export': 'Export', + 'workspace.taskCenter.type.import': 'Import', + 'workspace.taskCenter.type.executeSql': 'Execute SQL', + 'workspace.taskCenter.type.generateData': 'Generate Data', + 'workspace.taskCenter.type.exportSchema': 'Export Schema', + 'workspace.taskCenter.type.transfer': 'Transfer', + 'workspace.taskCenter.status.pending': 'Pending', + 'workspace.taskCenter.status.running': 'Running', + 'workspace.taskCenter.status.finish': 'Finished', + 'workspace.taskCenter.status.error': 'Failed', + 'workspace.taskCenter.download': 'Download', + 'workspace.taskCenter.refresh': 'Refresh', + 'workspace.taskCenter.cleanup': 'Clean Up', + 'workspace.taskCenter.cleanupConfirmTitle': 'Clean finished and failed tasks?', + 'workspace.taskCenter.cleanupConfirmDescription': + 'This will delete task records and related temporary download files.', + 'workspace.taskCenter.cleanupSuccess': 'Cleaned {1} tasks', + 'workspace.taskCenter.dataSource': 'Data Source', + 'workspace.taskCenter.database': 'Database', + 'workspace.taskCenter.table': 'Task Name', + 'workspace.taskCenter.progress': 'Progress', + 'workspace.taskCenter.type': 'Type', + 'workspace.taskCenter.status': 'Status', }; diff --git a/chat2db-client/src/i18n/ja-jp/chat.ts b/chat2db-client/src/i18n/ja-jp/chat.ts index 78e2e074f..cf1ffedcd 100644 --- a/chat2db-client/src/i18n/ja-jp/chat.ts +++ b/chat2db-client/src/i18n/ja-jp/chat.ts @@ -1,9 +1,21 @@ export default { 'chat.input.remain': '残り {1} 回', - 'chat.input.tableSelect.placeholder': 'テーブルを選択してください', - 'chat.input.tableSelect.error.TooManyTable': '最大で8つのテーブルを選択できます', - 'chat.input.remain.dialog.tips': '公式アカウントをフォローし、"プロモーション"を送信して体験回数を増やす', - 'chat.input.syncTable.tips': 'すべてのテーブル構造をAIコンテキストに自動同期(Chat2DBAIモデルのみで使用可能、グループ内でグループオーナーに連絡し、Chat2DBAIのホワイトリストに申請)', - 'chat.input.remain.tooltip': '手動で選択したテーブルの構造はAIコンテキストに同期されます', - 'chat.input.syncTable.tempTips': '🎉リリース:すべてのテーブル構造をAIコンテキストに自動同期', + 'chat.input.tableSelect.placeholder': 'テーブルを選択', + 'chat.input.tableSelect.error.TooManyTable': '最大8つのテーブルを選択', + 'chat.input.remain.dialog.tips': '公式アカウントをフォローし、"推广"を送信して体験回数を増やす', + 'chat.input.syncTable.tips': 'すべてのテーブル構造をAIコンテキストに自動同期(Chat2DBAIのみ)', + 'chat.input.remain.tooltip': '手動で選択されたテーブルはAIコンテキストに同期されます', + 'chat.input.syncTable.tempTips': 'すべてのテーブル構造をAIコンテキストに自動同期', + 'chat.sidebar.title': '会話', + 'chat.sidebar.new': '新規会話', + 'chat.sidebar.empty': '会話がありません、新規をクリックして開始', + 'chat.sidebar.delete': '削除', + 'chat.sidebar.rename': '名前変更', + 'chat.sidebar.collapse': 'サイドバーを折りたたむ', + 'chat.sidebar.expand': 'サイドバーを展開', + 'chat.sidebar.delete.confirm': 'この会話を削除しますか?', + 'chat.sidebar.rename.placeholder': '新しいタイトルを入力', + 'chat.header.newChat': '新しい会話', + 'chat.header.titleFallback': '新しい会話', + 'chat.switch.confirm': '応答を生成中です。切り替えますか?', }; diff --git a/chat2db-client/src/i18n/ja-jp/common.ts b/chat2db-client/src/i18n/ja-jp/common.ts index 93b63ea63..60095129d 100644 --- a/chat2db-client/src/i18n/ja-jp/common.ts +++ b/chat2db-client/src/i18n/ja-jp/common.ts @@ -119,4 +119,10 @@ export default { 'common.label.LocalFile': 'ローカルファイル', 'common.text.rename': '名前を変更', 'common.title.info': '情報', + 'common.viewAllTable.batchDeprecated': 'テーブルの一括非推奨化', + 'common.viewAllTable.noSelectedTables': '非推奨化するテーブルを選択してください', + 'common.viewAllTable.batchDeprecatedConfirmTitle': 'テーブルの一括非推奨化を確認', + 'common.viewAllTable.batchDeprecatedConfirmContent': '選択した {1} 個のテーブルを非推奨にしてもよろしいですか?', + 'common.viewAllTable.batchDeprecatedSuccess': '{1} 個のテーブルを非推奨化しました', + 'common.viewAllTable.batchDeprecatedPartialSuccess': '{1} 個のテーブルを非推奨化しました。{2} 個が失敗しました', }; diff --git a/chat2db-client/src/i18n/ja-jp/connection.ts b/chat2db-client/src/i18n/ja-jp/connection.ts index 6f2084921..ff0a18190 100644 --- a/chat2db-client/src/i18n/ja-jp/connection.ts +++ b/chat2db-client/src/i18n/ja-jp/connection.ts @@ -16,6 +16,10 @@ export default { 'connection.button.addConnection': '接続を追加', 'connection.button.connect': '接続', 'connection.button.remove': '接続を削除', + 'connection.group.unknown': '未分類', + 'connection.sort.manual': '手動並べ替え', + 'connection.sort.asc': '昇順で並べ替え', + 'connection.sort.desc': '降順で並べ替え', 'connection.message.testConnectResult': '接続テスト{1}', 'connection.message.testSshConnection': 'SSH接続をテスト', 'connection.tableHeader.name': '名前', diff --git a/chat2db-client/src/i18n/ja-jp/workspace.ts b/chat2db-client/src/i18n/ja-jp/workspace.ts index 6033e527f..bb12ef161 100644 --- a/chat2db-client/src/i18n/ja-jp/workspace.ts +++ b/chat2db-client/src/i18n/ja-jp/workspace.ts @@ -3,6 +3,11 @@ export default { 'workspace.cascader.placeholder': '選択してください', 'workspace.ai.input.placeholder': 'ここにプレーンテキストを入力してください', 'workspace.title.savedConsole': '保存されたコンソール', + 'workspace.savedConsole.importSql': 'SQLファイルをインポート', + 'workspace.savedConsole.exportSql': 'SQLファイルをエクスポート', + 'workspace.savedConsole.import.success': 'SQLファイルをインポートしました', + 'workspace.savedConsole.import.emptyFile': 'SQLファイルが空です', + 'workspace.savedConsole.import.onlySql': '.sqlファイルのみインポートできます', 'workspace.menu.ViewDDL': 'DDLを見る', 'workspace.menu.deleteTable': 'テーブルを削除', 'workspace.menu.openTable': 'テーブルを開く', @@ -14,7 +19,12 @@ export default { 'workspace.menu.queryConsole': '新しいクエリ', 'workspace.menu.viewAllTable': 'すべてのテーブルを見る', 'workspace.menu.createDatabase': 'データベースを作成', + 'workspace.menu.deleteDatabase': 'データベースを削除', 'workspace.menu.createSchema': 'スキーマを作成', + 'workspace.menu.exportSchemaDoc': 'データ構造をエクスポート', + 'workspace.menu.executeSqlStatement': 'SQL文を実行', + 'workspace.menu.deprecatedTable': 'テーブルを非推奨にする', + 'workspace.menu.restoreTable': '非推奨テーブルを復元', 'workspace.menu.deleteTablePlaceHolder': '削除するテーブル名を入力してください', 'workspace.tips.affirmDeleteTable': '入力したテーブル名と削除するテーブル名が一致しません。再確認してください', 'workspace.table.total': '合計', @@ -23,14 +33,24 @@ export default { 'workspace.table.export.cur.csv': '現在のページの結果セットをcsvでエクスポート', 'workspace.table.export.all.insert': '結果セットをinsert sqlでエクスポート', 'workspace.table.export.cur.insert': '現在のページの結果セットをinsert sqlでエクスポート', + 'workspace.table.export.progress.title': 'エクスポート中...', + 'workspace.table.export.progress.rows': 'エクスポート済み行数', 'workspace.tree.view': 'ビュー', 'workspace.tree.trigger': 'トリガー', 'workspace.tree.function': '関数', 'workspace.tree.procedure': 'ストアドプロシージャ', + 'workspace.tree.expandDataSourceTree': 'データソースツリーを展開', 'workspace.tree.search.placeholder': '展開されたノードで検索', 'workspace.tree.delete.tip': 'この操作は永久的に削除されることを理解しています', 'workspace.tree.delete.table.tip': 'テーブル{1}を削除してもよろしいですか?', + 'workspace.tree.delete.database.tip': 'データベース{1}を削除してもよろしいですか?この操作は元に戻せません。', 'workspace.tips.noConnection': 'まだ接続が作成されていません', 'workspace.tips.maxConsole': 'コンソールは最大20個まで開くことができます', 'workspace.tips.openExecutiveLogging': '実行ログを開く', + 'workspace.erDiagram.inferVirtualFk': '仮想FKを推測', + 'workspace.erDiagram.inferVirtualFkNoResult': '新しい仮想外部キーが見つかりませんでした', + 'workspace.erDiagram.inferVirtualFkError': '仮想外部キーの推測に失敗しました', + 'workspace.erDiagram.inferResult.title': '仮想FK推測結果', + 'workspace.erDiagram.inferResult.added': '追加 ({1})', + 'workspace.erDiagram.inferResult.deleted': '削除 ({1})', }; diff --git a/chat2db-client/src/i18n/tr-tr/chat.ts b/chat2db-client/src/i18n/tr-tr/chat.ts index 1cda385df..aaa33cff3 100644 --- a/chat2db-client/src/i18n/tr-tr/chat.ts +++ b/chat2db-client/src/i18n/tr-tr/chat.ts @@ -1,10 +1,21 @@ export default { - 'chat.input.remain': 'Kalan {1}', - 'chat.input.tableSelect.placeholder': 'Lütfen tabloları seçin', - 'chat.input.tableSelect.error.TooManyTable': 'En fazla 8 tablo seçebilirsiniz', - 'chat.input.remain.dialog.tips': - 'Resmi WeChat hesabımıza abone olun, daha fazla deneyim şansı için 推广 gönderin.', - 'chat.input.syncTable.tips': 'Otomatik olarak tüm tablo yapılarını AI bağlamına senkronize eder', - 'chat.input.remain.tooltip': 'Manuel olarak seçilen tablo, AI bağlamına senkronize edilecektir', - 'chat.input.syncTable.tempTips': '🎉Güncelleme: Otomatik olarak tüm tablo yapılarını AI bağlamına senkronize etme', + 'chat.input.remain': '{1} kalan', + 'chat.input.tableSelect.placeholder': 'Tabloları seçin', + 'chat.input.tableSelect.error.TooManyTable': 'En fazla 8 tablo seçin', + 'chat.input.remain.dialog.tips': 'Resmi hesabı takip edin, "promosyon" gönderin', + 'chat.input.syncTable.tips': 'Tüm tablo yapılarını AI bağlamına otomatik senkronize et', + 'chat.input.remain.tooltip': 'Manuel seçilen tablolar AI bağlamına senkronize edilir', + 'chat.input.syncTable.tempTips': 'Tüm tablo yapılarını AI bağlamına otomatik senkronize et', + 'chat.sidebar.title': 'Konuşmalar', + 'chat.sidebar.new': 'Yeni Konuşma', + 'chat.sidebar.empty': 'Henüz konuşma yok, başlamak için yeniye tıklayın', + 'chat.sidebar.delete': 'Sil', + 'chat.sidebar.rename': 'Yeniden Adlandır', + 'chat.sidebar.collapse': 'Kenar çubuğunu daralt', + 'chat.sidebar.expand': 'Kenar çubuğunu genişlet', + 'chat.sidebar.delete.confirm': 'Bu konuşmayı silmek istediğinizden emin misiniz?', + 'chat.sidebar.rename.placeholder': 'Yeni başlık girin', + 'chat.header.newChat': 'Yeni Sohbet', + 'chat.header.titleFallback': 'Yeni Sohbet', + 'chat.switch.confirm': 'Bir yanıt oluşturuluyor. Yine de geçiş yapılsın mı?', }; diff --git a/chat2db-client/src/i18n/tr-tr/common.ts b/chat2db-client/src/i18n/tr-tr/common.ts index 5889a8768..284744d10 100644 --- a/chat2db-client/src/i18n/tr-tr/common.ts +++ b/chat2db-client/src/i18n/tr-tr/common.ts @@ -120,4 +120,10 @@ export default { 'common.label.LocalFile': 'Yerel Dosya', 'common.text.rename': 'Yeniden Adlandır', 'common.title.info': 'Bilgi', + 'common.viewAllTable.batchDeprecated': 'Tabloları Toplu Kullanımdan Kaldır', + 'common.viewAllTable.noSelectedTables': 'Lütfen kullanımdan kaldırmak için tabloları seçin', + 'common.viewAllTable.batchDeprecatedConfirmTitle': 'Tabloları Toplu Kullanımdan Kaldırmayı Onayla', + 'common.viewAllTable.batchDeprecatedConfirmContent': 'Seçili {1} tabloyu kullanımdan kaldırmak istediğinizden emin misiniz?', + 'common.viewAllTable.batchDeprecatedSuccess': '{1} tablo başarıyla kullanımdan kaldırıldı', + 'common.viewAllTable.batchDeprecatedPartialSuccess': '{1} tablo başarıyla kullanımdan kaldırıldı, {2} başarısız oldu', }; diff --git a/chat2db-client/src/i18n/tr-tr/connection.ts b/chat2db-client/src/i18n/tr-tr/connection.ts index 54cc367f3..ca2bf757a 100644 --- a/chat2db-client/src/i18n/tr-tr/connection.ts +++ b/chat2db-client/src/i18n/tr-tr/connection.ts @@ -16,6 +16,10 @@ export default { 'connection.button.addConnection': 'Bağlantı Ekle', 'connection.button.connect': 'Bağlan', 'connection.button.remove': 'Kaldır', + 'connection.group.unknown': 'Gruplanmamış', + 'connection.sort.manual': 'Elle sırala', + 'connection.sort.asc': 'Artan sırala', + 'connection.sort.desc': 'Azalan sırala', 'connection.message.testConnectResult': 'Test bağlantısı {1}', 'connection.message.testSshConnection': 'SSH bağlantısını test et', 'connection.tableHeader.name': 'Adı', diff --git a/chat2db-client/src/i18n/tr-tr/team.ts b/chat2db-client/src/i18n/tr-tr/team.ts index d08e4d13c..6561be91b 100644 --- a/chat2db-client/src/i18n/tr-tr/team.ts +++ b/chat2db-client/src/i18n/tr-tr/team.ts @@ -51,4 +51,4 @@ export default { 'team.team.addForm.status.invalid': 'Geçersiz', 'team.team.addForm.description': 'Açıklama', }; - \ No newline at end of file + diff --git a/chat2db-client/src/i18n/tr-tr/workspace.ts b/chat2db-client/src/i18n/tr-tr/workspace.ts index f06a7bf56..083cc6a65 100644 --- a/chat2db-client/src/i18n/tr-tr/workspace.ts +++ b/chat2db-client/src/i18n/tr-tr/workspace.ts @@ -3,6 +3,11 @@ export default { 'workspace.cascader.placeholder': 'Buradan Seçin', 'workspace.ai.input.placeholder': 'Düz metin ifadenizi buraya girin', 'workspace.title.savedConsole': 'Kaydedilmiş konsol', + 'workspace.savedConsole.importSql': 'SQL dosyası içe aktar', + 'workspace.savedConsole.exportSql': 'SQL dosyası dışa aktar', + 'workspace.savedConsole.import.success': 'SQL dosyası içe aktarıldı', + 'workspace.savedConsole.import.emptyFile': 'SQL dosyası boş', + 'workspace.savedConsole.import.onlySql': 'Yalnızca .sql dosyaları içe aktarılabilir', 'workspace.menu.ViewDDL': 'DDL Görüntüle', 'workspace.menu.deleteTable': 'Tabloyu Sil', 'workspace.menu.openTable': 'Tabloyu Aç', @@ -14,21 +19,37 @@ export default { 'workspace.menu.queryConsole': 'Sorgu Konsolu', 'workspace.menu.viewAllTable': 'Tüm Tabloları Görüntüle', 'workspace.menu.createDatabase': 'Veritabanı Oluştur', + 'workspace.menu.deleteDatabase': 'Veritabanını Sil', 'workspace.menu.createSchema': 'Şema Oluştur', + 'workspace.menu.exportSchemaDoc': 'Şema Dokümanı Dışa Aktar', + 'workspace.menu.executeSqlStatement': 'SQL İfadesini Çalıştır', + 'workspace.menu.deprecatedTable': 'Tabloyu Kullanımdan Kaldır', + 'workspace.menu.restoreTable': 'Kullanımdan Kaldırılan Tabloyu Geri Yükle', 'workspace.menu.deleteTablePlaceHolder': 'Silmek istediğiniz tablonun adını giriniz', - 'workspace.tips.affirmDeleteTable': 'Girdiğiniz tablo adı, silmek istediğiniz tablo adı ile aynı değil, lütfen tekrar doğrulayın', + 'workspace.tips.affirmDeleteTable': + 'Girdiğiniz tablo adı, silmek istediğiniz tablo adı ile aynı değil, lütfen tekrar doğrulayın', + 'workspace.erDiagram.inferVirtualFk': 'Sanal FK Tahmin Et', + 'workspace.erDiagram.inferVirtualFkNoResult': 'Yeni sanal yabancı anahtar bulunamadı', + 'workspace.erDiagram.inferVirtualFkError': 'Sanal yabancı anahtarlar tahmin edilemedi', + 'workspace.erDiagram.inferResult.title': 'Sanal FK Tahmin Sonucu', + 'workspace.erDiagram.inferResult.added': 'Eklendi ({1})', + 'workspace.erDiagram.inferResult.deleted': 'Kaldırıldı ({1})', 'workspace.table.total': 'Toplam', 'workspace.table.total.tip': 'Toplam satır sayısını yükle', 'workspace.table.export.all.csv': 'Sonuç kümesini CSV olarak dışa aktar', 'workspace.table.export.cur.csv': 'Geçerli sayfa sonucunu CSV olarak dışa aktar', 'workspace.table.export.all.insert': 'Sonuç kümesini INSERT SQL olarak dışa aktar', 'workspace.table.export.cur.insert': 'Geçerli sayfa sonucunu INSERT SQL olarak dışa aktar', + 'workspace.table.export.progress.title': 'Dışa aktarılıyor...', + 'workspace.table.export.progress.rows': 'Dışa aktarılan satır', 'workspace.tree.view': 'Görüntüle', 'workspace.tree.trigger': 'Tetikleyici', 'workspace.tree.function': 'Fonksiyon', 'workspace.tree.procedure': 'Prosedür', + 'workspace.tree.expandDataSourceTree': 'Veri kaynağı ağacını genişlet', 'workspace.tree.search.placeholder': 'Genişletilmiş düğümde arama yapın', 'workspace.tree.delete.tip': 'Bu işlemin kalıcı olarak silindiğini anlıyorum', 'workspace.tree.delete.table.tip': '{1} tablosunu silmek istediğinizden emin misiniz?', + 'workspace.tree.delete.database.tip': '{1} veritabanını silmek istediğinizden emin misiniz? Bu işlem geri alınamaz.', 'workspace.tips.noConnection': 'Henüz bir bağlantı oluşturmadınız', }; diff --git a/chat2db-client/src/i18n/zh-cn/chat.ts b/chat2db-client/src/i18n/zh-cn/chat.ts index 0cd9d0e01..3b3c89aef 100644 --- a/chat2db-client/src/i18n/zh-cn/chat.ts +++ b/chat2db-client/src/i18n/zh-cn/chat.ts @@ -6,4 +6,16 @@ export default { 'chat.input.syncTable.tips': '自动同步所有表结构给AI上下文(在群内联系群主,申请Chat2DBAI白名单后,仅在Chat2DBAI模型下可用)', 'chat.input.remain.tooltip': '手动选中的表的结构将会同步给AI上下文', 'chat.input.syncTable.tempTips': '🎉上线:自动同步所有表结构到AI上下文', + 'chat.sidebar.title': '会话', + 'chat.sidebar.new': '新建会话', + 'chat.sidebar.empty': '暂无会话,点击新建开始', + 'chat.sidebar.delete': '删除', + 'chat.sidebar.rename': '重命名', + 'chat.sidebar.collapse': '折叠会话栏', + 'chat.sidebar.expand': '展开会话栏', + 'chat.sidebar.delete.confirm': '确定删除该会话吗?', + 'chat.sidebar.rename.placeholder': '请输入新标题', + 'chat.header.newChat': '新对话', + 'chat.header.titleFallback': '新对话', + 'chat.switch.confirm': '当前会话正在生成中,确定切换?', }; diff --git a/chat2db-client/src/i18n/zh-cn/common.ts b/chat2db-client/src/i18n/zh-cn/common.ts index 258e7f245..b005f972e 100644 --- a/chat2db-client/src/i18n/zh-cn/common.ts +++ b/chat2db-client/src/i18n/zh-cn/common.ts @@ -27,6 +27,8 @@ export default { 'common.button.execute': '执行', "common.button.import": '导入SQL', 'common.button.format': '格式化', + 'common.button.guess': '猜一猜', + 'common.button.aiFix': 'AI修复', 'common.message.successfulConfig': '配置成功', 'common.text.successful': '成功', 'common.text.failure': '失败', @@ -69,6 +71,12 @@ export default { 'common.text.wechatPopularizeAi2': '关注微信公众号,发送 “AI” 可以免费获得ApiKey,并赠送体验次数。', 'common.text.wechatPopularize': '发送 “推广” 还可以免费获取更多体验次数。', 'common.text.export': '导出', + 'common.text.exportFailed': '导出失败', + 'common.text.importFailed': '导入失败', + 'common.text.importColumnMismatch': '文件列数与表字段数不一致', + 'common.text.importSuccess': '导入成功', + 'common.text.importing': '正在导入...', + 'common.text.selectFile': '选择文件', 'common.notification.detail': '查看详情', 'common.notification.solution': '解决办法', 'common.button.copyError': '复制错误报告', @@ -90,11 +98,14 @@ export default { 'common.button.cancelRequest': '取消请求', 'common.button.executionError': '执行错误', 'common.text.affectedRows': '受影响行:{1}', - 'common.text.selectFile' : '选择文件', - 'common.text.noTableFoundUp' : '当前库没有查询到表', - 'common.text.noTableFoundDown' : '你可以在顶部切换数据库', - 'common.text.updateNow' : '立即更新', - 'common.title.preview' : '预览', + 'common.text.selectFile': '选择文件', + 'common.text.noTableFoundUp': '当前库没有查询到表', + 'common.text.noTableFoundDown': '你可以在顶部切换数据库', + 'common.text.noTables': '当前没有表可以猜测注释', + 'common.text.aiCommentGenerated': 'AI 注释已生成并写入索引', + 'common.text.allTablesHaveComments': '所有表已有注释,无需生成', + 'common.text.updateNow': '立即更新', + 'common.title.preview': '预览', 'common.title.errorMessage': '错误信息', 'common.label.comment': '备注', 'common.label.name': '名称', @@ -119,5 +130,26 @@ export default { 'common.label.LocalFile': '本地', 'common.text.rename': '重命名', 'common.title.info': '信息', - + 'common.button.saveAll': '保存全部', + 'common.button.editAll': '编辑全部', + 'common.text.generateTitle': 'AI 生成标题', + 'common.text.generatingTitle': '正在生成标题...', + 'common.viewAllTable.batchDeprecated': '批量废弃表', + 'common.viewAllTable.noSelectedTables': '请先选择要废弃的表', + 'common.viewAllTable.batchDeprecatedConfirmTitle': '确认批量废弃表', + 'common.viewAllTable.batchDeprecatedConfirmContent': '确定要废弃选中的 {1} 张表吗?', + 'common.viewAllTable.batchDeprecatedSuccess': '成功废弃 {1} 张表', + 'common.viewAllTable.batchDeprecatedPartialSuccess': '成功废弃 {1} 张表,失败 {2} 张表', + 'common.viewAllTable.batchOptimize': '批量优化表', + 'common.viewAllTable.noSelectedTablesForOptimize': '请先选择要优化的表', + 'common.viewAllTable.batchOptimizeConfirmTitle': '确认批量优化表', + 'common.viewAllTable.batchOptimizeConfirmContent': '确定要对选中的 {1} 张表执行 OPTIMIZE TABLE 吗?', + 'common.viewAllTable.batchOptimizeSuccess': '成功优化 {1} 张表', + 'common.viewAllTable.batchOptimizePartialSuccess': '成功优化 {1} 张表,失败 {2} 张表', + 'common.viewAllTable.batchAnalyze': '批量分析表', + 'common.viewAllTable.noSelectedTablesForAnalyze': '请先选择要分析的表', + 'common.viewAllTable.batchAnalyzeConfirmTitle': '确认批量分析表', + 'common.viewAllTable.batchAnalyzeConfirmContent': '确定要对选中的 {1} 张表执行 ANALYZE TABLE 吗?', + 'common.viewAllTable.batchAnalyzeSuccess': '成功分析 {1} 张表', + 'common.viewAllTable.batchAnalyzePartialSuccess': '成功分析 {1} 张表,失败 {2} 张表', }; diff --git a/chat2db-client/src/i18n/zh-cn/connection.ts b/chat2db-client/src/i18n/zh-cn/connection.ts index 830f65473..962571e5a 100644 --- a/chat2db-client/src/i18n/zh-cn/connection.ts +++ b/chat2db-client/src/i18n/zh-cn/connection.ts @@ -16,6 +16,10 @@ export default { 'connection.button.addConnection': '新增连接', 'connection.button.connect': '连接', 'connection.button.remove': '删除链接', + 'connection.group.unknown': '未分组', + 'connection.sort.manual': '手动排序', + 'connection.sort.asc': '按名称升序', + 'connection.sort.desc': '按名称降序', 'connection.message.testConnectResult': '测试连接{1}', 'connection.message.testSshConnection': '测试ssh连接', 'connection.tableHeader.name': '名称', diff --git a/chat2db-client/src/i18n/zh-cn/editTable.ts b/chat2db-client/src/i18n/zh-cn/editTable.ts index 711abe141..16450abe1 100644 --- a/chat2db-client/src/i18n/zh-cn/editTable.ts +++ b/chat2db-client/src/i18n/zh-cn/editTable.ts @@ -35,4 +35,23 @@ export default { 'editTable.title.sqlPreview': 'sql预览', 'editTable.button.addColumn': '添加列', 'editTable.button.addIndex': '添加索引', + 'editTable.tab.foreignKeyInfo': '外键', + 'editTable.label.foreignKeyName': '外键名称', + 'editTable.label.column': '列', + 'editTable.label.referencedTable': '引用表', + 'editTable.label.referencedColumn': '引用列', + 'editTable.label.updateRule': '更新规则', + 'editTable.label.deleteRule': '删除规则', + 'editTable.button.addForeignKey': '添加外键', + 'editTable.button.syncForeignKeys': '同步', + 'editTable.label.sourceType': '类型', + 'editTable.tooltip.syncFK': '从数据库同步外键', + 'editTable.tooltip.virtualFK': '虚拟外键', + 'editTable.tooltip.realFK': '真实外键', + 'editTable.message.syncFKWarning': '缺少必要的表信息', + 'editTable.message.syncFKSuccess': '同步完成:新增 {1} 个,删除 {2} 个,未变 {3} 个', + 'editTable.message.syncFKError': '同步外键失败', + 'editTable.option.cascade': '级联', + 'editTable.option.setNull': '置为NULL', + 'editTable.option.noAction': '无操作', }; diff --git a/chat2db-client/src/i18n/zh-cn/index.ts b/chat2db-client/src/i18n/zh-cn/index.ts index db58915d9..dbbd445d7 100644 --- a/chat2db-client/src/i18n/zh-cn/index.ts +++ b/chat2db-client/src/i18n/zh-cn/index.ts @@ -11,6 +11,7 @@ import login from './login'; import editTable from './editTable'; import editTableData from './editTableData'; import sqlEditor from './sqlEditor' +import schemaDiff from './schemaDiff'; export default { lang: LangType.ZH_CN, @@ -26,5 +27,6 @@ export default { ...login, ...editTable, ...editTableData, - ...sqlEditor + ...sqlEditor, + ...schemaDiff, }; diff --git a/chat2db-client/src/i18n/zh-cn/schemaDiff.ts b/chat2db-client/src/i18n/zh-cn/schemaDiff.ts new file mode 100644 index 000000000..5f5754254 --- /dev/null +++ b/chat2db-client/src/i18n/zh-cn/schemaDiff.ts @@ -0,0 +1,74 @@ +export default { + 'schemaDiff.title': '结构对比', + 'schemaDiff.source': '源端', + 'schemaDiff.target': '目标端', + 'schemaDiff.compare': '开始对比', + 'schemaDiff.comparing': '对比中...', + 'schemaDiff.compareOptions': '对比选项', + 'schemaDiff.compareColumn': '对比列', + 'schemaDiff.compareIndex': '对比索引', + 'schemaDiff.compareForeignKey': '对比外键', + 'schemaDiff.compareTableOption': '对比表选项', + 'schemaDiff.excludeDeprecated': '排除废弃的表', + 'schemaDiff.caseSensitive': '区分大小写', + 'schemaDiff.ignoreCharsetAlias': '忽略字符集别名', + 'schemaDiff.ignoreIntegerDisplayWidth': '忽略整数显示宽度', + 'schemaDiff.ignoreAutoIncrement': '忽略自增值', + 'schemaDiff.summary': '汇总', + 'schemaDiff.totalTables': '总表数', + 'schemaDiff.tablesAdded': '新增', + 'schemaDiff.tablesRemoved': '删除', + 'schemaDiff.tablesModified': '修改', + 'schemaDiff.tablesUnchanged': '一致', + 'schemaDiff.excluded': '已排除废弃', + 'schemaDiff.columns': '列', + 'schemaDiff.indexes': '索引', + 'schemaDiff.foreignKeys': '外键', + 'schemaDiff.noChanges': '未发现差异', + 'schemaDiff.table': '表', + 'schemaDiff.diffType': '变更类型', + 'schemaDiff.added': '新增', + 'schemaDiff.removed': '删除', + 'schemaDiff.modified': '修改', + 'schemaDiff.unchanged': '一致', + 'schemaDiff.ddlPreview': 'DDL 预览', + 'schemaDiff.selectAll': '全选', + 'schemaDiff.deselectAll': '取消全选', + 'schemaDiff.migrate': '执行迁移', + 'schemaDiff.migrating': '执行中...', + 'schemaDiff.migrateConfirm': '确认执行 {count} 条 DDL 语句?', + 'schemaDiff.migrateConfirmMessage': '将对目标数据库执行 {count} 条语句,此操作不可逆,请确认已备份数据。', + 'schemaDiff.migrateResult': '迁移结果', + 'schemaDiff.migrateSuccess': '迁移成功', + 'schemaDiff.migrateFail': '迁移失败', + 'schemaDiff.successCount': '成功', + 'schemaDiff.failCount': '失败', + 'schemaDiff.continueOnError': '失败时继续', + 'schemaDiff.executeInTransaction': '事务执行', + 'schemaDiff.selectSource': '选择源数据源', + 'schemaDiff.selectTarget': '选择目标数据源', + 'schemaDiff.selectDatabase': '选择数据库', + 'schemaDiff.selectSchema': '选择模式', + 'schemaDiff.structuralView': '结构视图', + 'schemaDiff.ddlView': 'DDL 视图', + 'schemaDiff.columnName': '列名', + 'schemaDiff.columnType': '类型', + 'schemaDiff.nullable': '可空', + 'schemaDiff.defaultValue': '默认值', + 'schemaDiff.comment': '注释', + 'schemaDiff.operation': '操作', + 'schemaDiff.viewDiff': '差异', + 'schemaDiff.diffProperties': '属性差异', + 'schemaDiff.diffField': '属性', + 'schemaDiff.sourceValue': '源端值', + 'schemaDiff.targetValue': '目标端值', + 'schemaDiff.tableOptions': '表选项', + 'schemaDiff.indexName': '索引名', + 'schemaDiff.indexType': '索引类型', + 'schemaDiff.unique': '唯一', + 'schemaDiff.foreignKeyName': '外键名', + 'schemaDiff.referencedTable': '引用表', + 'schemaDiff.referencedColumn': '引用列', + 'schemaDiff.loadDatabaseFail': '加载数据库失败', + 'schemaDiff.loadSchemaFail': '加载模式失败', +} diff --git a/chat2db-client/src/i18n/zh-cn/setting.ts b/chat2db-client/src/i18n/zh-cn/setting.ts index c4fa188aa..516315911 100644 --- a/chat2db-client/src/i18n/zh-cn/setting.ts +++ b/chat2db-client/src/i18n/zh-cn/setting.ts @@ -17,10 +17,6 @@ export default { 'setting.title.language': '语言', 'setting.title.aiSource': 'AI 来源', 'setting.tab.custom': '自定义', - 'setting.tab.aiType.zhipu': '智谱', - 'setting.tab.aiType.baichuan': '百川', - 'setting.tab.aiType.wenxin': '文心一言', - 'setting.tab.aiType.tongyiqianwen': '通义千问', 'setting.tab.aiType.custom.tips': "接口格式与OpenAI接口格式一致", 'setting.label.serviceAddress': '服务地址', 'setting.button.apply': '应用', diff --git a/chat2db-client/src/i18n/zh-cn/workspace.ts b/chat2db-client/src/i18n/zh-cn/workspace.ts index c1873b15c..760a76384 100644 --- a/chat2db-client/src/i18n/zh-cn/workspace.ts +++ b/chat2db-client/src/i18n/zh-cn/workspace.ts @@ -3,6 +3,12 @@ export default { 'workspace.cascader.placeholder': '请选择', 'workspace.ai.input.placeholder': '在这里输入纯文本语句', 'workspace.title.savedConsole': '保存记录', + 'workspace.savedConsole.importSql': '导入 SQL 文件', + 'workspace.savedConsole.exportSql': '导出 SQL 文件', + 'workspace.savedConsole.import.success': 'SQL 文件导入成功', + 'workspace.savedConsole.import.emptyFile': 'SQL 文件内容为空', + 'workspace.savedConsole.import.onlySql': '只能导入 .sql 文件', + 'workspace.title.aiChat': 'AI 对话', 'workspace.menu.ViewDDL': '查看DDL', 'workspace.menu.deleteTable': '删除表', 'workspace.menu.openTable': '打开表', @@ -13,9 +19,92 @@ export default { 'workspace.menu.editTableData': '编辑表数据', 'workspace.menu.queryConsole': '新建查询', 'workspace.menu.viewAllTable': '查看所有表', + 'workspace.menu.viewERDiagram': '查看 ER 图', + 'workspace.menu.viewTableRelation': '查看表间关系', + 'workspace.erDiagram.refresh': '刷新', + 'workspace.erDiagram.hierarchical': '层级布局', + 'workspace.erDiagram.forceLayout': '力导向布局', + 'workspace.erDiagram.virtualFk': '虚拟外键', + 'workspace.erDiagram.inferVirtualFk': '推断虚拟外键', + 'workspace.erDiagram.inferVirtualFkSuccess': '成功推断 {1} 个虚拟外键', + 'workspace.erDiagram.inferVirtualFkNoResult': '未发现新的虚拟外键', + 'workspace.erDiagram.inferVirtualFkError': '推断虚拟外键失败', + 'workspace.erDiagram.inferResult.title': '虚拟外键推断结果', + 'workspace.erDiagram.inferResult.added': '新增 ({1})', + 'workspace.erDiagram.inferResult.deleted': '删除 ({1})', + 'workspace.erDiagram.confirmDeleteVirtualFk': '确定要删除虚拟外键 "{1}" 吗?', + 'workspace.erDiagram.suggestionHint': '根据您的SQL语句,检测到以下潜在的表关系:', + 'workspace.erDiagram.zoomIn': '放大', + 'workspace.erDiagram.zoomOut': '缩小', + 'workspace.erDiagram.fitView': '适应画布', + 'workspace.erDiagram.export': '导出 PNG', + 'workspace.erDiagram.filterPlaceholder': '过滤表名...', + 'workspace.erDiagram.showOnlyRelatedTables': '只显示关联表', + 'workspace.erDiagram.legend': '图例', + 'workspace.erDiagram.realFk': '外键', + 'workspace.erDiagram.loading': '正在加载 ER 图...', + 'workspace.erDiagram.noData': '未找到表关系数据', + 'workspace.erDiagram.copyTableName': '复制表名', + 'workspace.erDiagram.copyTableNameSuccess': '表名已复制', + 'workspace.erDiagram.expandColumns': '展开字段列表', + 'workspace.erDiagram.collapseColumns': '收起字段列表', + 'workspace.erDiagram.loadColumnsError': '加载字段列表失败', + 'workspace.erDiagram.createVirtualFk': '创建虚拟外键', + 'workspace.erDiagram.createVirtualFkSuccess': '虚拟外键创建成功', + 'workspace.erDiagram.createVirtualFkError': '虚拟外键创建失败', + 'workspace.erDiagram.invalidVirtualFkConnection': '请从字段右侧绿色连接点拖拽到目标字段左侧绿色连接点', + 'workspace.erDiagram.invalidVirtualFkSameField': '不能为同一个字段创建虚拟外键', + 'workspace.erDiagram.virtualFkConnectHint': + '创建虚拟外键:从外键字段右侧绿色点拖出,连接到引用字段左侧绿色点;拖字段文字不会创建关系。', + 'workspace.erDiagram.virtualFkSourceHandle': '从这里拖出:外键字段', + 'workspace.erDiagram.virtualFkTargetHandle': '拖到这里:引用字段', + 'workspace.erDiagram.createJoinQuery': '创建查询', 'workspace.menu.createDatabase': '创建数据库', + 'workspace.menu.deleteDatabase': '删除数据库', 'workspace.menu.createSchema': '创建Schema', + 'workspace.menu.deleteVirtualKey': '删除虚拟外键', + 'workspace.menu.dataOperation': '数据操作', + 'workspace.menu.importData': '导入数据', + 'workspace.menu.exportData': '导出数据', + 'workspace.menu.generateData': '生成数据', + 'workspace.menu.exportSchemaDoc': '导出数据结构', + 'workspace.menu.executeSqlStatement': '执行sql语句', + 'workspace.menu.deprecatedTable': '废弃表', + 'workspace.menu.restoreTable': '恢复废弃表', 'workspace.menu.deleteTablePlaceHolder': '请输入你要删除的表名', + 'workspace.menu.truncateTable': '截断表', + 'workspace.tableRelation.masterTable': '主表', + 'workspace.tableRelation.uniqueColumn': '唯一列', + 'workspace.tableRelation.childTable': '子表', + 'workspace.tableRelation.relationColumn': '关联字段', + 'workspace.tableRelation.operation': '操作', + 'workspace.tableRelation.searchPlaceholder': '搜索表名、字段、来源或备注', + 'workspace.tableRelation.loadError': '加载表关系失败', + 'workspace.tableRelation.missingId': '缺少外键ID,无法删除', + 'workspace.tableRelation.deleteConfirmTitle': '删除外键', + 'workspace.tableRelation.deleteVirtualConfirmContent': '确认删除该虚拟外键?', + 'workspace.tableRelation.deleteRealConfirmContent': '确认删除该真实外键记录?删除后会返回需要执行的 DDL。', + 'workspace.tableRelation.realDeleteDDLTitle': '删除真实外键 DDL', + 'workspace.tableRelation.deleteSuccess': '删除外键成功', + 'workspace.tableRelation.createSuccess': '添加外键成功', + 'workspace.tableRelation.createError': '添加外键失败', + 'workspace.tableRelation.updateSuccess': '更新虚拟外键成功', + 'workspace.tableRelation.updateError': '更新虚拟外键失败', + 'workspace.tableRelation.importVirtualFk': '导入虚拟外键', + 'workspace.tableRelation.exportVirtualFk': '导出虚拟外键', + 'workspace.tableRelation.importSuccess': '导入成功,新增 {1} 个,覆盖 {2} 个', + 'workspace.tableRelation.importError': '导入虚拟外键失败,请检查 JSON 文件', + 'workspace.tableRelation.importEmpty': '未发现可导入的虚拟外键', + 'workspace.tableRelation.importInvalidTitle': '导入数据与当前库表不匹配', + 'workspace.tableRelation.importInvalidContent': '发现 {1} 条不存在的表或字段关系,已取消导入。', + 'workspace.tableRelation.exportSuccess': '虚拟外键导出成功', + 'workspace.tableRelation.exportEmpty': '没有可导出的虚拟外键', + 'workspace.tableRelation.noVirtualFk': '没有可删除的虚拟外键', + 'workspace.tableRelation.deleteAllVirtualFk': '删除所有虚拟外键', + 'workspace.tableRelation.deleteAllVirtualConfirmTitle': '删除所有虚拟外键', + 'workspace.tableRelation.deleteAllVirtualConfirmContent': '确认删除当前列表中的 {1} 个虚拟外键?', + 'workspace.tableRelation.deleteAllVirtualSuccess': '所有虚拟外键已删除', + 'workspace.tableRelation.deleteAllVirtualError': '删除所有虚拟外键失败', 'workspace.tips.affirmDeleteTable': '输入的表名与要删除的表名不一致,请再次确认', 'workspace.table.total': '总数', 'workspace.table.total.tip': '加载总行数', @@ -23,14 +112,116 @@ export default { 'workspace.table.export.cur.csv': '导出当前页结果集 csv', 'workspace.table.export.all.insert': '导出结果集 insert sql', 'workspace.table.export.cur.insert': '导出当前页结果集 insert sql', + 'workspace.table.import.data': '导入数据', + 'workspace.table.import.selectFile': '请选择文件', + 'workspace.table.import.title': '导入数据', + 'workspace.table.import.targetTable': '目标表', + 'workspace.table.import.fileType': '文件类型', + 'workspace.table.import.fileType.csv': 'CSV', + 'workspace.table.import.fileType.xlsx': 'XLSX', + 'workspace.table.import.fileType.xls': 'XLS', + 'workspace.table.import.fileType.sql': 'SQL', + 'workspace.table.import.uploadFile': '上传文件', + 'workspace.table.import.uploadHint': '点击或拖拽文件到此处上传', + 'workspace.table.import.start': '开始', + 'workspace.table.import.cancel': '取消', + 'workspace.table.import.progress.title': '正在导入...', + 'workspace.table.import.progress.log': '日志详情', + 'workspace.table.import.progress.taskName': '任务名称', + 'workspace.table.import.progress.startTime': '开始时间', + 'workspace.table.import.progress.rows': '已导入行数', + 'workspace.table.import.close': '关闭', + 'workspace.table.import.step.selectFile': '选择文件', + 'workspace.table.import.step.fieldMapping': '字段映射', + 'workspace.table.import.step.importMode': '导入模式', + 'workspace.table.import.step.importProgress': '导入进度', + 'workspace.table.import.next': '下一步', + 'workspace.table.import.previous': '上一步', + 'workspace.table.import.fieldMapping.title': '定义字段映射', + 'workspace.table.import.fieldMapping.description': + '你可以定义字段映射。设置映射来指定的源字段和目的字段之间的对应关系。', + 'workspace.table.import.fieldMapping.source': '源', + 'workspace.table.import.fieldMapping.targetTable': '目标表', + 'workspace.table.import.fieldMapping.sourceField': '源字段', + 'workspace.table.import.fieldMapping.targetField': '目标字段', + 'workspace.table.import.fieldMapping.primaryKey': '主键', + 'workspace.table.import.fieldMapping.autoMatch': '自动匹配', + 'workspace.table.import.fieldMapping.pleaseSelect': '请选择', + 'workspace.table.import.fieldMapping.loading': '正在解析文件...', + 'workspace.table.import.mode.description': '请选择所需的导入模式', + 'workspace.table.import.mode.insert': '追加(向目标表添加记录)', + 'workspace.table.import.mode.update': '更新(更新目标表已有记录)', + 'workspace.table.import.mode.upsert': '追加或更新(目标已有则更新,否则追加)', + 'workspace.table.import.mode.insertIgnore': '不更新追加(目标已有则跳过,否则追加)', + 'workspace.table.import.mode.delete': '删除(删除目标表已有记录)', + 'workspace.table.import.mode.replace': '复制(删除目标所有记录,重新导入)', + 'workspace.table.import.mode.pkRequired': '需要主键', + 'workspace.table.import.mode.replaceConfirm': '将清空目标表所有数据后重新导入,确定继续?', + 'workspace.table.import.export.title': '导出数据', + 'workspace.table.export.progress.title': '导出进度', + 'workspace.table.export.progress.rows': '已导出行数', + 'workspace.table.export.title': '导出数据', + 'workspace.table.export.sourceTable': '源表', + 'workspace.table.export.fileType': '导出格式', + 'workspace.table.export.sql': '导出 SQL', + 'workspace.table.export.start': '开始导出', + 'workspace.table.export.cancel': '取消', + 'workspace.table.export.success': '导出成功', + 'workspace.table.export.failed': '导出失败', + 'workspace.table.export.close': '关闭', + 'workspace.table.export.progress.log': '导出日志', + 'workspace.table.export.progress.taskName': '任务名称', + 'workspace.schemaDoc.export.title': '导出数据结构', + 'workspace.schemaDoc.export.database': '数据库', + 'workspace.schemaDoc.export.fileType': '导出格式', + 'workspace.schemaDoc.export.start': '开始导出', + 'workspace.schemaDoc.export.cancel': '取消', + 'workspace.schemaDoc.export.success': '导出成功', + 'workspace.schemaDoc.export.failed': '导出失败', + 'workspace.schemaDoc.export.close': '关闭', + 'workspace.schemaDoc.export.progress.log': '导出日志', + 'workspace.schemaDoc.export.progress.taskName': '任务名称', + 'workspace.schemaDoc.export.refreshLatest': '获取最新', + 'workspace.schemaDoc.export.refreshLatestTip': '从数据库实时获取表结构(较慢)', 'workspace.tree.view': '视图', 'workspace.tree.trigger': '触发器', 'workspace.tree.function': '函数', 'workspace.tree.procedure': '存储过程', + 'workspace.tree.expandDataSourceTree': '展开数据源树', 'workspace.tree.search.placeholder': '在展开节点中搜索', 'workspace.tree.delete.tip': '我了解该操作是永久性删除', 'workspace.tree.delete.table.tip': '确定要删除表{1}吗?', + 'workspace.tree.delete.database.tip': '确定要删除数据库{1}吗?此操作不可恢复。', + 'workspace.tree.truncate.table.tip': '您确定要截断表 {1} 吗?这将清空表中的所有数据。', + 'workspace.tree.truncate.tip': '我确认要截断该表', 'workspace.tips.noConnection': '你还没有创建连接', 'workspace.tips.maxConsole': '最多只能打开20个控制台', 'workspace.tips.openExecutiveLogging': '打开执行记录', + 'workspace.tips.noSqlContent': '当前控制台没有 SQL 内容', + 'workspace.tips.generateTitleFailed': 'AI 生成标题失败,请重试', + 'workspace.tips.enterReleaseEnvironment': '已进入生产环境', + 'workspace.taskCenter.title': '任务中心', + 'workspace.taskCenter.empty': '暂无任务', + 'workspace.taskCenter.type.export': '导出', + 'workspace.taskCenter.type.import': '导入', + 'workspace.taskCenter.type.executeSql': '执行SQL', + 'workspace.taskCenter.type.generateData': '生成数据', + 'workspace.taskCenter.type.exportSchema': '导出结构', + 'workspace.taskCenter.type.transfer': '迁移', + 'workspace.taskCenter.status.pending': '等待中', + 'workspace.taskCenter.status.running': '运行中', + 'workspace.taskCenter.status.finish': '已完成', + 'workspace.taskCenter.status.error': '失败', + 'workspace.taskCenter.download': '下载', + 'workspace.taskCenter.refresh': '刷新', + 'workspace.taskCenter.cleanup': '垃圾清理', + 'workspace.taskCenter.cleanupConfirmTitle': '清理已完成和失败的任务?', + 'workspace.taskCenter.cleanupConfirmDescription': '将删除任务记录和关联的临时下载文件。', + 'workspace.taskCenter.cleanupSuccess': '已清理 {1} 个任务', + 'workspace.taskCenter.dataSource': '数据源', + 'workspace.taskCenter.database': '数据库', + 'workspace.taskCenter.table': '任务名', + 'workspace.taskCenter.progress': '进度', + 'workspace.taskCenter.type': '类型', + 'workspace.taskCenter.status': '状态', }; diff --git a/chat2db-client/src/indexedDB/index.ts b/chat2db-client/src/indexedDB/index.ts index 9934fe89a..b48fd6494 100644 --- a/chat2db-client/src/indexedDB/index.ts +++ b/chat2db-client/src/indexedDB/index.ts @@ -15,9 +15,11 @@ export const createDB = (dbName: string, version: number) => { // 创建存储库 tableList.forEach((item: any) => { const { tableDetails } = item; - const objectStore = db.createObjectStore(tableDetails.name, tableDetails.primaryKey); + const objectStore = db.objectStoreNames.contains(tableDetails.name) + ? event.target.transaction.objectStore(tableDetails.name) + : db.createObjectStore(tableDetails.name, tableDetails.primaryKey); tableDetails.column.forEach((i: any) => { - if (i.isIndex) { + if (i.isIndex && !objectStore.indexNames.contains(i.name)) { objectStore.createIndex(i.name, i.keyPath, i.options); } }); @@ -26,7 +28,7 @@ export const createDB = (dbName: string, version: number) => { }); }; -type TableType = 'workspaceConsoleDDL'; +type TableType = 'workspaceConsoleDDL' | 'aiChatStore'; type DBType = 'chat2db'; @@ -91,8 +93,7 @@ export const getDataByIndex = (db: DBType, tableName: TableType, indexName: stri }; // 通过游标查询数据,支持传入多个条件 -export const getDataByCursor = (db: DBType, tableName: TableType, condition: {[key in string]: any} -) => { +export const getDataByCursor = (db: DBType, tableName: TableType, condition: { [key in string]: any }) => { return new Promise((resolve, reject) => { const transaction = window._indexedDB[db].transaction(tableName, 'readwrite'); const objectStore = transaction.objectStore(tableName); @@ -119,10 +120,8 @@ export const getDataByCursor = (db: DBType, tableName: TableType, condition: {[k reject(false); }; }); - }; - // 修改数据 export const updateData = (db: DBType, tableName: TableType, data: any) => { return new Promise((resolve, reject) => { diff --git a/chat2db-client/src/indexedDB/table.ts b/chat2db-client/src/indexedDB/table.ts index b22419333..ba083efc4 100644 --- a/chat2db-client/src/indexedDB/table.ts +++ b/chat2db-client/src/indexedDB/table.ts @@ -1,11 +1,10 @@ export interface IWorkspaceConsoleDDL { - consoleId: string; // 控制台的id 唯一 - ddl: string; // 数据源ddl - userId?: string; // 用户的唯一id + consoleId: string; + ddl: string; + userId?: string; } -// 工作区console表 export const workspaceConsoleDDL = { name: 'workspaceConsoleDDL', primaryKey: { @@ -41,8 +40,37 @@ export const workspaceConsoleDDL = { ], } +export const aiChatStore = { + name: 'aiChatStore', + primaryKey: { + keyPath: 'id', + autoIncrement: false, + }, + column: [ + { + name: 'id', + isIndex: true, + keyPath: 'id', + options: { + unique: true, + }, + }, + { + name: 'state', + isIndex: false, + keyPath: 'state', + options: { + unique: false, + }, + }, + ], +} + export const tableList = [ { tableDetails: workspaceConsoleDDL, - } + }, + { + tableDetails: aiChatStore, + }, ] diff --git a/chat2db-client/src/layouts/GlobalLayout/index.tsx b/chat2db-client/src/layouts/GlobalLayout/index.tsx index 9ca5dd2eb..05e012bd9 100644 --- a/chat2db-client/src/layouts/GlobalLayout/index.tsx +++ b/chat2db-client/src/layouts/GlobalLayout/index.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useLayoutEffect, useState } from 'react'; import usePollRequestService, { ServiceStatus } from '@/hooks/usePollRequestService'; import i18n, { isEn } from '@/i18n'; -import { Button, ConfigProvider, Spin, Tooltip } from 'antd'; +import { App, Button, ConfigProvider, Spin, Tooltip } from 'antd'; import antdEnUS from 'antd/locale/en_US'; import antdZhCN from 'antd/locale/zh_CN'; import service from '@/service/misc'; @@ -62,15 +62,15 @@ const GlobalLayout = () => { matchMedia.onchange = change; }; - // 等待状态页面 - // if (serviceStatus === ServiceStatus.PENDING || curUser === null) { - // return ( - //
- // - // - //
- // ); - // } + //等待状态页面 + if (serviceStatus === ServiceStatus.PENDING || curUser === null) { + return ( +
+ + +
+ ); + } // 错误状态页面 if (serviceStatus === ServiceStatus.FAILURE) { @@ -99,15 +99,17 @@ const GlobalLayout = () => { return ( -
- {/* Open screen animation */} - {(serviceStatus === ServiceStatus.PENDING || curUser === null) && } - -
- + +
+ {/* Open screen animation */} + {(serviceStatus === ServiceStatus.PENDING || curUser === null) && } + +
+ +
-
- + + ); }; diff --git a/chat2db-client/src/layouts/init/initIndexedDB.ts b/chat2db-client/src/layouts/init/initIndexedDB.ts index 3fe8d643d..2a36b02b5 100644 --- a/chat2db-client/src/layouts/init/initIndexedDB.ts +++ b/chat2db-client/src/layouts/init/initIndexedDB.ts @@ -2,7 +2,7 @@ import indexedDB from '@/indexedDB'; /** 初始化indexedDB */ const initIndexedDB = () => { - indexedDB.createDB('chat2db', 1).then((db) => { + indexedDB.createDB('chat2db', 2).then((db) => { window._indexedDB = { chat2db: db, }; diff --git a/chat2db-client/src/main/package.json b/chat2db-client/src/main/package.json index 9b1e8e996..e2be9cfc8 100644 --- a/chat2db-client/src/main/package.json +++ b/chat2db-client/src/main/package.json @@ -13,6 +13,7 @@ "dependencies": { "electron-log": "^5.0.3", "electron-store": "^8.1.0", + "main": "file:", "node-machine-id": "^1.1.12", "uuid": "^9.0.1" }, diff --git a/chat2db-client/src/main/preload.js b/chat2db-client/src/main/preload.js index 32ea9fe4e..491545eb4 100644 --- a/chat2db-client/src/main/preload.js +++ b/chat2db-client/src/main/preload.js @@ -19,6 +19,7 @@ contextBridge.exposeInMainWorld('electronApi', { const child = spawn(path.join(__dirname, '../..', `./static/${JAVA_PATH}`), [ '-noverify', `-Dspring.profiles.active=${isTest ? 'test' : 'release'}`, + '-Dmicrometer.context-propagation.enabled=true', '-Dserver.address=127.0.0.1', '-Dchat2db.mode=DESKTOP', `-Dproject.path=${javaPath}`, diff --git a/chat2db-client/src/main/yarn.lock b/chat2db-client/src/main/yarn.lock index a11de950e..c6ee5a168 100644 --- a/chat2db-client/src/main/yarn.lock +++ b/chat2db-client/src/main/yarn.lock @@ -80,7 +80,7 @@ dependencies: undici-types "~5.26.4" -"@webassemblyjs/ast@1.11.6", "@webassemblyjs/ast@^1.11.5": +"@webassemblyjs/ast@^1.11.5", "@webassemblyjs/ast@1.11.6": version "1.11.6" resolved "https://registry.npmmirror.com/@webassemblyjs/ast/-/ast-1.11.6.tgz" integrity sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q== @@ -181,7 +181,7 @@ "@webassemblyjs/wasm-gen" "1.11.6" "@webassemblyjs/wasm-parser" "1.11.6" -"@webassemblyjs/wasm-parser@1.11.6", "@webassemblyjs/wasm-parser@^1.11.5": +"@webassemblyjs/wasm-parser@^1.11.5", "@webassemblyjs/wasm-parser@1.11.6": version "1.11.6" resolved "https://registry.npmmirror.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz" integrity sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ== @@ -231,7 +231,7 @@ acorn-import-assertions@^1.9.0: resolved "https://registry.npmmirror.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz" integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA== -acorn@^8.7.1, acorn@^8.8.2: +acorn@^8, acorn@^8.7.1, acorn@^8.8.2: version "8.11.3" resolved "https://registry.npmmirror.com/acorn/-/acorn-8.11.3.tgz" integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== @@ -248,7 +248,7 @@ ajv-keywords@^3.5.2: resolved "https://registry.npmmirror.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz" integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== -ajv@^6.12.5: +ajv@^6.12.5, ajv@^6.9.1: version "6.12.6" resolved "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -273,7 +273,7 @@ atomically@^1.7.0: resolved "https://registry.npmmirror.com/atomically/-/atomically-1.7.0.tgz" integrity sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w== -browserslist@^4.14.5: +browserslist@^4.14.5, "browserslist@>= 4.21.0": version "4.22.2" resolved "https://registry.npmmirror.com/browserslist/-/browserslist-4.22.2.tgz" integrity sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A== @@ -602,6 +602,16 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +"main@file:": + version "1.0.0" + resolved "file:" + dependencies: + electron-log "^5.0.3" + electron-store "^8.1.0" + main "file:" + node-machine-id "^1.1.12" + uuid "^9.0.1" + merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.npmmirror.com/merge-stream/-/merge-stream-2.0.0.tgz" @@ -897,9 +907,9 @@ watchpack@^2.4.0: glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" -webpack-cli@^5.1.4: +webpack-cli@^5.1.4, webpack-cli@5.x.x: version "5.1.4" - resolved "https://registry.npmmirror.com/webpack-cli/-/webpack-cli-5.1.4.tgz#c8e046ba7eaae4911d7e71e2b25b776fcc35759b" + resolved "https://registry.npmmirror.com/webpack-cli/-/webpack-cli-5.1.4.tgz" integrity sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg== dependencies: "@discoveryjs/json-ext" "^0.5.0" @@ -930,7 +940,7 @@ webpack-sources@^3.2.3: resolved "https://registry.npmmirror.com/webpack-sources/-/webpack-sources-3.2.3.tgz" integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== -webpack@^5.89.0: +webpack@^5.1.0, webpack@^5.89.0, webpack@5.x.x: version "5.89.0" resolved "https://registry.npmmirror.com/webpack/-/webpack-5.89.0.tgz" integrity sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw== diff --git a/chat2db-client/src/pages/main/connection/index.less b/chat2db-client/src/pages/main/connection/index.less index 082b1fd2e..282fd8c1c 100644 --- a/chat2db-client/src/pages/main/connection/index.less +++ b/chat2db-client/src/pages/main/connection/index.less @@ -19,12 +19,24 @@ border-bottom: 0px; } -.pageTitle { - font-size: 20px; - line-height: 24px; - font-weight: 500; - margin: 20px 0px 10px; - padding-left: 20px; +.pageHeader { + display: flex; + align-items: center; + justify-content: space-between; + margin: 20px 12px 10px 20px; + + .pageTitle { + flex: 1; + width: 0; + font-size: 20px; + line-height: 24px; + font-weight: 500; + .f-single-line(); + } + + .sortButton { + flex-shrink: 0; + } } .menuBox { @@ -32,6 +44,41 @@ overflow-y: auto; padding: 0px 8px; + .connectionGroup { + margin-bottom: 8px; + } + + .groupTitle { + display: flex; + align-items: center; + height: 26px; + padding: 0 8px; + color: var(--color-text-secondary); + font-size: 12px; + user-select: none; + } + + .groupName { + flex: 1; + width: 0; + font-weight: 500; + .f-single-line(); + } + + .groupCount { + flex-shrink: 0; + margin-left: 6px; + } + + .envTag { + flex-shrink: 0; + width: 8px; + height: 8px; + border-radius: 50%; + background-color: var(--color-primary); + margin-right: 8px; + } + .menuItem { display: flex; justify-content: space-between; @@ -53,15 +100,6 @@ .f-single-line(); } - .envTag { - flex-shrink: 0; - width: 8px; - height: 8px; - border-radius: 50%; - background-color: var(--color-primary); - margin-right: 8px; - } - .databaseTypeIcon { margin-right: 6px; } @@ -93,6 +131,16 @@ background-color: var(--color-hover-bg); } + .menuItemDragging { + opacity: 0.45; + } + + .dragIcon { + flex-shrink: 0; + margin-right: 6px; + color: var(--color-text-secondary); + } + :global { .ant-menu-inline { border-inline-end: none !important; diff --git a/chat2db-client/src/pages/main/connection/index.tsx b/chat2db-client/src/pages/main/connection/index.tsx index 6d3d8f981..87f43a500 100644 --- a/chat2db-client/src/pages/main/connection/index.tsx +++ b/chat2db-client/src/pages/main/connection/index.tsx @@ -1,5 +1,6 @@ -import React, { useRef, useState, Fragment, useEffect } from 'react'; -import { Button, Dropdown } from 'antd'; +import { DragOutlined, SortAscendingOutlined, SortDescendingOutlined } from '@ant-design/icons'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; +import { Button, Dropdown, Tooltip } from 'antd'; import classnames from 'classnames'; import i18n from '@/i18n'; // import RefreshLoadingButton from '@/components/RefreshLoadingButton'; @@ -21,7 +22,12 @@ import MenuLabel from '@/components/MenuLabel'; import useClickAndDoubleClick from '@/hooks/useClickAndDoubleClick'; // ----- store ----- -import { useConnectionStore, getConnectionList } from '@/pages/main/store/connection'; +import { getConnectionList, setConnectionList, useConnectionStore } from '@/pages/main/store/connection'; +import { + ConnectionSortMode, + groupConnectionList, + sortConnectionList, +} from '@/pages/main/store/connection/utils'; import { setMainPageActiveTab } from '@/pages/main/store/main'; import { setCurrentConnectionDetails } from '@/pages/main/workspace/store/common'; import { getOpenConsoleList } from '@/pages/main/workspace/store/console'; @@ -37,6 +43,12 @@ const ConnectionsPage = () => { const volatileRef = useRef(); const [connectionActiveId, setConnectionActiveId] = useState(null); const [connectionDetail, setConnectionDetail] = useState(null); + const [sortMode, setSortMode] = useState('manual'); + const [dragConnectionId, setDragConnectionId] = useState(null); + + const connectionGroups = useMemo(() => { + return groupConnectionList(connectionList || [], sortMode); + }, [connectionList, sortMode]); // 处理列表单击事件 const handleMenuItemSingleClick = (t: IConnectionListItem) => { @@ -98,7 +110,7 @@ const ConnectionsPage = () => { getConnectionList(); setConnectionActiveId(res); }); - } + }; return [ { @@ -119,36 +131,108 @@ const ConnectionsPage = () => { ]; }; + const handleDragConnection = (targetConnection: IConnectionListItem) => { + if (!connectionList || !dragConnectionId || dragConnectionId === targetConnection.id) { + return; + } + + // Manual mode uses the backend-returned order as the source of truth. + const sortedConnectionList = sortConnectionList(connectionList, 'manual'); + const currentIndex = sortedConnectionList.findIndex((item) => item.id === dragConnectionId); + const targetIndex = sortedConnectionList.findIndex((item) => item.id === targetConnection.id); + + if (currentIndex === -1 || targetIndex === -1) { + return; + } + + const nextConnectionList = [...sortedConnectionList]; + const [dragItem] = nextConnectionList.splice(currentIndex, 1); + nextConnectionList.splice(targetIndex, 0, dragItem); + setConnectionList(nextConnectionList); + setSortMode('manual'); + // Optimistically update the list, then roll back from the server if saving fails. + connectionService.updateSort({ idList: nextConnectionList.map((item) => item.id) }).catch(() => { + getConnectionList(); + }); + }; + + const switchSortMode = () => { + // Cycle through manual order and temporary name sorts without overwriting the saved order. + const nextSortMode = sortMode === 'manual' ? 'asc' : sortMode === 'asc' ? 'desc' : 'manual'; + setSortMode(nextSortMode); + }; + + const sortTooltip = useMemo(() => { + if (sortMode === 'manual') { + return i18n('connection.sort.asc'); + } + + if (sortMode === 'asc') { + return i18n('connection.sort.desc'); + } + + return i18n('connection.sort.manual'); + }, [sortMode]); + const renderConnectionMenuList = () => { - return connectionList?.map((t) => { + return connectionGroups.map((group) => { return ( - -
{ - handleClickConnectionMenu(t); - }} - > -
- - - {} - - {t.alias} - {/* - {t.environment.shortName} - */} -
+
+
+ + {group.environment?.name || i18n('connection.group.unknown')} + {group.connections.length}
- + {group.connections.map((t) => { + return ( + +
{ + event.dataTransfer.effectAllowed = 'move'; + setDragConnectionId(t.id); + }} + onDragOver={(event) => { + if (sortMode === 'manual') { + event.preventDefault(); + } + }} + onDrop={(event) => { + event.preventDefault(); + handleDragConnection(t); + }} + onDragEnd={() => { + setDragConnectionId(null); + }} + onClick={() => { + handleClickConnectionMenu(t); + }} + > +
+ {sortMode === 'manual' && } + + {} + + {t.alias} +
+
+
+ ); + })} +
); }); }; @@ -168,7 +252,25 @@ const ConnectionsPage = () => { <>
-
{i18n('connection.title.connections')}
+
+
{i18n('connection.title.connections')}
+ + + +
{renderConnectionMenuList()}
{connectionActiveId && (
(record.name === selectedKey && !record.isGroup ? styles.selectedRow : '')} + rowKey="rowKey" + size="small" + scroll={{ x: tableScrollX, y: tableScrollY }} + virtual + onRow={(record) => ({ + onClick: () => { + if (!record.isGroup) { + loadKeyDetail(record.name); + } + }, + })} + /> + +
+
+ + + + + + +
+
+ 上次刷新时间: {lastRefreshTime || '-'} + + + +
+
+
+ {detail || detailLoading ? ( + <> +
+ {creating ? '新增 Key' : '编辑器'} +
+ +
+
+
+
+ 键名: + setEditorKey(event.target.value)} /> +
+
+ 键类型: + {creating ? ( + } + placeholder="过滤字段或值" + size="small" + value={hashFieldFilter} + onChange={(event) => setHashFieldFilter(event.target.value)} + /> + + {filteredHashFields.length}/{hashFields.length} 个字段 + +
+
+
+ + {hashFields.length} 个字段 +
+ + ); + } + return ( + setTextValue(event.target.value)} + rows={type === 'string' ? 5 : 7} + placeholder={type === 'string' ? '字符串值' : '每行一个元素'} + /> + ); + } +}); + +function buildRows(keys: IRedisKeyItem[]) { + const root: IRedisKeyRow[] = []; + const groupMap = new Map(); + + keys.forEach((item) => { + const parts = item.name.split(':'); + if (parts.length === 1) { + root.push(toLeaf(item, item.name)); + return; + } + + let children = root; + let prefix = ''; + for (let i = 0; i < parts.length - 1; i++) { + prefix = prefix ? `${prefix}:${parts[i]}` : parts[i]; + let group = groupMap.get(prefix); + if (!group) { + group = { + rowKey: `group-${prefix}`, + name: prefix, + displayName: parts[i], + isGroup: true, + count: 0, + children: [], + }; + groupMap.set(prefix, group); + children.push(group); + } + group.count = (group.count || 0) + 1; + children = group.children!; + } + children.push(toLeaf(item, parts[parts.length - 1])); + }); + + return root; +} + +function toLeaf(item: IRedisKeyItem, displayName: string): IRedisKeyRow { + return { + ...item, + rowKey: `key-${item.name}`, + displayName, + }; +} + +function getTypeColor(type: string) { + const colors: Record = { + string: 'blue', + hash: 'green', + list: 'purple', + set: 'orange', + zset: 'cyan', + stream: 'geekblue', + }; + return colors[type] || 'default'; +} + +function normalizeType(type?: string) { + return (type || '').toLowerCase(); +} + +function isEditableType(type?: string) { + return ['string', 'hash', 'list', 'set', 'zset'].includes(normalizeType(type)); +} + +function buildEditorValue(type: string, textValue: string, hashFields: IHashFieldRow[]) { + if (type === 'hash') { + return hashFields.reduce>((result, item) => { + const field = item.field.trim(); + if (field) { + result[field] = item.value; + } + return result; + }, {}); + } + if (type === 'list' || type === 'set' || type === 'zset') { + return textValue.split('\n').filter((item) => item.length > 0); + } + return textValue; +} + +function formatTtl(ttl?: number) { + if (ttl === undefined || ttl === null || ttl === -1) { + return '(无 TTL)'; + } + if (ttl === -2) { + return '已过期'; + } + return `${ttl}s`; +} + +function formatSize(size?: number) { + if (!size) { + return '-'; + } + if (size < 1024) { + return `${size} B`; + } + if (size < 1024 * 1024) { + return `${(size / 1024).toFixed(1)} KB`; + } + return `${(size / 1024 / 1024).toFixed(1)} MB`; +} + +function computeHashDiff( + original: Record, + current: IHashFieldRow[], +): { addedFields: Record; removedFields: string[] } { + const currentMap: Record = {}; + current.forEach((row) => { + const field = row.field.trim(); + if (field) { + currentMap[field] = row.value; + } + }); + + const addedFields: Record = {}; + const removedFields: string[] = []; + + for (const [field, value] of Object.entries(currentMap)) { + if (!(field in original) || original[field] !== value) { + addedFields[field] = value; + } + } + + for (const field of Object.keys(original)) { + if (!(field in currentMap)) { + removedFields.push(field); + } + } + + return { addedFields, removedFields }; +} + +export default RedisDataView; diff --git a/chat2db-client/src/pages/main/workspace/components/RedisMonitorView/index.less b/chat2db-client/src/pages/main/workspace/components/RedisMonitorView/index.less new file mode 100644 index 000000000..2c620e61e --- /dev/null +++ b/chat2db-client/src/pages/main/workspace/components/RedisMonitorView/index.less @@ -0,0 +1,76 @@ +.redisMonitorView { + height: 100%; + display: flex; + flex-direction: column; + background: var(--color-bg-base); +} + +.toolbar { + height: 48px; + display: flex; + align-items: center; + gap: 8px; + padding: 8px 12px; + border-bottom: 1px solid var(--color-border); +} + +.spacer { + flex: 1; +} + +.searchInput { + width: 280px; +} + +.status { + height: 28px; + display: flex; + align-items: center; + gap: 10px; + padding: 0 12px; + color: var(--color-text-45); + font-size: 12px; + border-bottom: 1px solid var(--color-border); +} + +.content { + flex: 1; + min-height: 0; + overflow: auto; + padding: 8px 0 16px; + font-family: Menlo, Consolas, 'Courier New', monospace; + font-size: 13px; + line-height: 24px; + white-space: pre-wrap; +} + +.noWrap { + white-space: pre; +} + +.line { + display: flex; + align-items: flex-start; + min-height: 24px; +} + +.lineNo { + flex: 0 0 58px; + padding-right: 12px; + color: var(--color-text-45); + text-align: right; + user-select: none; +} + +.lineText { + flex: 1; + min-width: 0; + padding-right: 16px; + color: var(--color-text); + word-break: break-word; +} + +.noWrap .lineText { + min-width: max-content; + word-break: normal; +} diff --git a/chat2db-client/src/pages/main/workspace/components/RedisMonitorView/index.tsx b/chat2db-client/src/pages/main/workspace/components/RedisMonitorView/index.tsx new file mode 100644 index 000000000..a8d059f05 --- /dev/null +++ b/chat2db-client/src/pages/main/workspace/components/RedisMonitorView/index.tsx @@ -0,0 +1,162 @@ +import React, { memo, useEffect, useMemo, useRef, useState } from 'react'; +import { Button, Checkbox, Input, message } from 'antd'; +import { v4 as uuid } from 'uuid'; + +import Iconfont from '@/components/Iconfont'; +import redisService from '@/service/redis'; + +import styles from './index.less'; + +interface IProps { + uniqueData: { + dataSourceId: number; + databaseName?: string; + }; +} + +const MAX_LINES = 5000; + +const RedisMonitorView = memo((props: IProps) => { + const { uniqueData } = props; + const [running, setRunning] = useState(false); + const [autoWrap, setAutoWrap] = useState(true); + const [keyword, setKeyword] = useState(''); + const [lines, setLines] = useState([]); + const [startTime, setStartTime] = useState(''); + const closeStreamRef = useRef<(() => void) | null>(null); + const sessionIdRef = useRef(''); + const contentRef = useRef(null); + + const visibleLines = useMemo(() => { + const value = keyword.trim().toLowerCase(); + if (!value) { + return lines; + } + return lines.filter((line) => line.toLowerCase().includes(value)); + }, [keyword, lines]); + + useEffect(() => { + if (contentRef.current) { + contentRef.current.scrollTop = contentRef.current.scrollHeight; + } + }, [visibleLines]); + + useEffect(() => { + return () => { + stopMonitor(); + }; + }, []); + + const startMonitor = () => { + if (!uniqueData?.dataSourceId || running) { + return; + } + const sessionId = uuid(); + sessionIdRef.current = sessionId; + setLines((current) => current.concat(`Begin Monitoring (${formatTime(new Date())})`)); + setStartTime(formatTime(new Date())); + setRunning(true); + closeStreamRef.current = redisService.streamMonitor({ + uid: sessionId, + dataSourceId: uniqueData.dataSourceId, + databaseName: uniqueData.databaseName, + onCommand: (line) => { + if (sessionIdRef.current !== sessionId || !line) { + return; + } + setLines((current) => current.concat(line).slice(-MAX_LINES)); + }, + onDone: () => { + if (sessionIdRef.current !== sessionId) { + return; + } + setRunning(false); + setLines((current) => current.concat(`End Monitoring (${formatTime(new Date())})`)); + closeStreamRef.current = null; + }, + onError: (errorMessage) => { + if (sessionIdRef.current !== sessionId) { + return; + } + setRunning(false); + message.error(errorMessage || 'Redis monitor 连接失败'); + setLines((current) => current.concat(`End Monitoring (${formatTime(new Date())})`)); + closeStreamRef.current = null; + }, + }); + }; + + const stopMonitor = () => { + closeStreamRef.current?.(); + closeStreamRef.current = null; + if (running) { + setLines((current) => current.concat(`End Monitoring (${formatTime(new Date())})`)); + } + sessionIdRef.current = ''; + setRunning(false); + }; + + const clearLines = () => { + setLines([]); + }; + + const exportLines = () => { + const blob = new Blob([lines.join('\n')], { type: 'text/plain;charset=utf-8' }); + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = `redis-monitor-${uniqueData.databaseName || '0'}-${Date.now()}.log`; + link.click(); + URL.revokeObjectURL(url); + }; + + return ( +
+
+ + + setAutoWrap(event.target.checked)}> + 自动换行 + + + +
+ } + value={keyword} + onChange={(event) => setKeyword(event.target.value)} + /> +
+
+ + {running ? `Monitoring since ${startTime}` : 'Monitor stopped'} + {visibleLines.length} lines +
+
+ {visibleLines.map((line, index) => ( +
+ {index + 1} + {line} +
+ ))} +
+
+ ); +}); + +function formatTime(date: Date) { + return date.toLocaleString(); +} + +export default RedisMonitorView; diff --git a/chat2db-client/src/pages/main/workspace/components/SQLExecute/consoleEditorRegistry.ts b/chat2db-client/src/pages/main/workspace/components/SQLExecute/consoleEditorRegistry.ts new file mode 100644 index 000000000..4e2d033e7 --- /dev/null +++ b/chat2db-client/src/pages/main/workspace/components/SQLExecute/consoleEditorRegistry.ts @@ -0,0 +1,17 @@ +import { RefObject } from 'react'; +import { IConsoleRef } from '@/components/ConsoleEditor'; + +// 存储所有 consoleId 对应的 editor ref +const consoleEditorMap = new Map>(); + +export const registerConsoleEditor = (consoleId: number, ref: RefObject) => { + consoleEditorMap.set(consoleId, ref); +}; + +export const unregisterConsoleEditor = (consoleId: number) => { + consoleEditorMap.delete(consoleId); +}; + +export const getConsoleEditor = (consoleId: number) => { + return consoleEditorMap.get(consoleId); +}; diff --git a/chat2db-client/src/pages/main/workspace/components/SQLExecute/index.tsx b/chat2db-client/src/pages/main/workspace/components/SQLExecute/index.tsx index 427258606..934c15042 100644 --- a/chat2db-client/src/pages/main/workspace/components/SQLExecute/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/SQLExecute/index.tsx @@ -6,11 +6,13 @@ import ConsoleEditor, { IConsoleRef } from '@/components/ConsoleEditor'; import SearchResult, { ISearchResultRef } from '@/components/SearchResult'; import { useWorkspaceStore } from '@/pages/main/workspace/store'; import { IBoundInfo } from '@/typings'; +import { registerConsoleEditor, unregisterConsoleEditor } from './consoleEditorRegistry'; +import { registerSearchResult, unregisterSearchResult } from './searchResultRegistry'; interface IProps { boundInfo: IBoundInfo; initDDL: string; - // 异步加载sql + // 异步加载 sql loadSQL: () => Promise; } @@ -22,13 +24,34 @@ const SQLExecute = memo((props) => { const [boundInfo, setBoundInfo] = useState(_boundInfo); const activeConsoleId = useWorkspaceStore((state) => state.activeConsoleId); + // 注册 consoleRef 到全局 map 中 + useEffect(() => { + const consoleId = boundInfo.consoleId; + registerConsoleEditor(consoleId, consoleRef); + registerSearchResult(consoleId, searchResultRef); + + return () => { + // 组件卸载时移除 + unregisterConsoleEditor(consoleId); + unregisterSearchResult(consoleId); + }; + }, [boundInfo.consoleId]); + useEffect(() => { if (loadSQL) { - loadSQL().then((sql) => { - consoleRef.current?.editorRef?.setValue(sql, 'cover'); - }); + loadSQL() + .then((sql) => { + if (sql) { + consoleRef.current?.editorRef?.setValue(sql, 'cover'); + } else { + console.warn('loadSQL returned empty value'); + } + }) + .catch((err) => { + console.error('Failed to load SQL:', err); + }); } - }, []); + }, [loadSQL]); return (
@@ -40,11 +63,12 @@ const SQLExecute = memo((props) => { defaultValue={initDDL} boundInfo={boundInfo} setBoundInfo={setBoundInfo} - hasAiChat={true} hasAi2Lang={true} isActive={activeConsoleId === boundInfo.consoleId} - onExecuteSQL={(sql) => { - searchResultRef.current?.handleExecuteSQL(sql); + onExecuteSQL={(sql, selectedInfo) => { + searchResultRef.current?.handleExecuteSQL(sql, { + scriptStartLine: selectedInfo?.startLine || 1, + }); }} />
@@ -53,6 +77,13 @@ const SQLExecute = memo((props) => { isActive={activeConsoleId === boundInfo.consoleId} ref={searchResultRef} executeSqlParams={boundInfo} + onLocateStatement={(result) => { + consoleRef.current?.editorRef?.locateStatement( + result.statementStartLine || 1, + result.statementEndLine || result.statementStartLine || 1, + result.errorLine, + ); + }} />
diff --git a/chat2db-client/src/pages/main/workspace/components/SQLExecute/searchResultRegistry.ts b/chat2db-client/src/pages/main/workspace/components/SQLExecute/searchResultRegistry.ts new file mode 100644 index 000000000..9b52f2a75 --- /dev/null +++ b/chat2db-client/src/pages/main/workspace/components/SQLExecute/searchResultRegistry.ts @@ -0,0 +1,16 @@ +import { RefObject } from 'react'; +import { ISearchResultRef } from '@/components/SearchResult'; + +const searchResultMap = new Map>(); + +export const registerSearchResult = (consoleId: number, ref: RefObject) => { + searchResultMap.set(consoleId, ref); +}; + +export const unregisterSearchResult = (consoleId: number) => { + searchResultMap.delete(consoleId); +}; + +export const getSearchResult = (consoleId: number) => { + return searchResultMap.get(consoleId); +}; diff --git a/chat2db-client/src/pages/main/workspace/components/SaveList/index.less b/chat2db-client/src/pages/main/workspace/components/SaveList/index.less index 94e2c6b64..c1629c00e 100644 --- a/chat2db-client/src/pages/main/workspace/components/SaveList/index.less +++ b/chat2db-client/src/pages/main/workspace/components/SaveList/index.less @@ -15,6 +15,9 @@ display: flex; align-items: center; border-bottom: 1px solid var(--color-border); + .importFileInput { + display: none; + } .leftModuleTitleText { width: 100%; display: flex; @@ -34,6 +37,7 @@ margin-right: 10px; } .refreshIcon, + .importIcon, .searchIcon { cursor: pointer; height: 100%; @@ -43,6 +47,9 @@ color: var(--color-primary); } } + .importIcon { + margin-right: 10px; + } } .leftModuleTitleSearch { height: 26px; @@ -64,6 +71,14 @@ } } +.paginationWrapper { + flex-shrink: 0; + padding: 8px 4px; + border-top: 1px solid var(--color-border); + display: flex; + justify-content: center; +} + .leftModuleTitleShadow { // 地步加一点模糊 box-shadow: 0px 1px 2px 0px var(--color-border); @@ -85,6 +100,14 @@ .f-single-line(); display: flex; align-items: center; + .envTag { + flex-shrink: 0; + width: 8px; + height: 8px; + border-radius: 50%; + background-color: var(--color-primary); + margin-right: 6px; + } .iconBox { width: 20px; flex-shrink: 0; diff --git a/chat2db-client/src/pages/main/workspace/components/SaveList/index.tsx b/chat2db-client/src/pages/main/workspace/components/SaveList/index.tsx index d25bb9f84..9ca141ad8 100644 --- a/chat2db-client/src/pages/main/workspace/components/SaveList/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/SaveList/index.tsx @@ -1,29 +1,67 @@ -import React, { useState, useEffect, useRef } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import i18n from '@/i18n'; -import { Input, Dropdown, Modal } from 'antd'; +import { Dropdown, Input, message, Modal, Pagination, Tooltip } from 'antd'; import Iconfont from '@/components/Iconfont'; import LoadingContent from '@/components/Loading/LoadingContent'; import historyServer from '@/service/history'; -import { ConsoleOpenedStatus, workspaceTabConfig } from '@/constants'; +import { ConsoleOpenedStatus, ConsoleStatus, WorkspaceTabType, workspaceTabConfig } from '@/constants'; import { IConsole, ITreeNode } from '@/typings'; import styles from './index.less'; import { approximateList } from '@/utils'; import { addWorkspaceTab, getSavedConsoleList } from '@/pages/main/workspace/store/console'; import { useWorkspaceStore } from '@/pages/main/workspace/store'; import MenuLabel from '@/components/MenuLabel'; +import { useConnectionStore } from '@/pages/main/store/connection'; +import { useTreeStore } from '@/blocks/Tree/treeStore'; + +const SQL_FILE_EXTENSION = '.sql'; + +const getSqlFileName = (name?: string) => { + const normalizedName = (name || 'untitled').replace(/[\\/:*?"<>|]/g, '_').trim() || 'untitled'; + return normalizedName.toLocaleLowerCase().endsWith(SQL_FILE_EXTENSION) + ? normalizedName + : `${normalizedName}${SQL_FILE_EXTENSION}`; +}; + +const downloadSqlFile = (filename: string, sql: string) => { + const blob = new Blob([sql], { type: 'application/sql;charset=utf-8' }); + const blobUrl = URL.createObjectURL(blob); + const link = document.createElement('a'); + + link.href = blobUrl; + link.download = filename; + link.click(); + URL.revokeObjectURL(blobUrl); +}; const SaveList = () => { const [searching, setSearching] = useState(false); const inputRef = useRef(); + const fileInputRef = useRef(null); const [searchedList, setSearchedList] = useState(); const leftModuleTitleRef = useRef(null); const saveBoxListRef = useRef(null); - const consoleList = useWorkspaceStore((state) => state.savedConsoleList); + const savedConsoleList = useWorkspaceStore((state) => state.savedConsoleList); + const savedConsoleTotal = useWorkspaceStore((state) => state.savedConsoleTotal); + const currentConnectionDetails = useWorkspaceStore((state) => state.currentConnectionDetails); + const [pageNo, setPageNo] = useState(1); + const [pageSize, setPageSize] = useState(100); + const { connectionList } = useConnectionStore((state) => { + return { + connectionList: state.connectionList, + }; + }); const [editData, setEditData] = useState(null); + const getConnectionEnvironment = (dataSourceId?: number) => { + if (!dataSourceId || !connectionList) return null; + const connection = connectionList.find((item) => item.id === dataSourceId); + return connection?.environment || null; + }; + useEffect(() => { - getSavedConsoleList(); - }, []); + getSavedConsoleList(pageNo, pageSize); + }, [pageNo, pageSize]); useEffect(() => { if (searching) { @@ -37,6 +75,15 @@ const SaveList = () => { setSearching(true); } + function openImportSqlFile() { + if (!currentConnectionDetails?.id) { + message.warning(i18n('workspace.tips.noConnection')); + return; + } + + fileInputRef.current?.click(); + } + function onBlur() { if (!inputRef.current.input.value) { setSearching(false); @@ -45,8 +92,8 @@ const SaveList = () => { } function onChange(value: string) { - if (consoleList) { - setSearchedList(approximateList(consoleList as any, value)); + if (savedConsoleList) { + setSearchedList(approximateList(savedConsoleList as any, value)); } } @@ -79,18 +126,115 @@ const SaveList = () => { id: data.id, }; historyServer.deleteSavedConsole(params).then(() => { - getSavedConsoleList(); + getSavedConsoleList(pageNo, pageSize); }); } + function exportSavedSql(data: IConsole) { + if (!data.ddl?.trim()) { + message.warning(i18n('workspace.tips.noSqlContent')); + return; + } + + downloadSqlFile(getSqlFileName(data.name), data.ddl); + } + + function importSavedSql(file: File) { + if (!currentConnectionDetails?.id) { + message.warning(i18n('workspace.tips.noConnection')); + return; + } + + const reader = new FileReader(); + + reader.onload = () => { + const ddl = String(reader.result || ''); + if (!ddl.trim()) { + message.warning(i18n('workspace.savedConsole.import.emptyFile')); + return; + } + + const { databaseName, schemaName } = useTreeStore.getState().focusTreeNode || {}; + const name = file.name.replace(/\.sql$/i, ''); + const params = { + name, + ddl, + status: ConsoleStatus.RELEASE, + type: currentConnectionDetails.type, + dataSourceId: currentConnectionDetails.id, + databaseName, + schemaName, + operationType: WorkspaceTabType.CONSOLE, + }; + + historyServer.createConsole(params).then((id) => { + getSavedConsoleList(pageNo, pageSize); + addWorkspaceTab({ + id, + type: WorkspaceTabType.CONSOLE, + title: name, + uniqueData: { + dataSourceId: currentConnectionDetails.id, + dataSourceName: currentConnectionDetails.alias, + databaseType: currentConnectionDetails.type, + databaseName, + schemaName, + status: ConsoleStatus.RELEASE, + ddl, + connectable: true, + }, + }); + message.success(i18n('workspace.savedConsole.import.success')); + }); + }; + + reader.onerror = () => { + message.error(i18n('common.text.importFailed')); + }; + + reader.readAsText(file); + } + + function handleImportFileChange(e: React.ChangeEvent) { + const file = e.target.files?.[0]; + e.target.value = ''; + + if (!file) { + return; + } + + if (!file.name.toLocaleLowerCase().endsWith(SQL_FILE_EXTENSION)) { + message.warning(i18n('workspace.savedConsole.import.onlySql')); + return; + } + + importSavedSql(file); + } + const editSaved = (data: IConsole) => { setEditData(data); }; + const handlePageChange = (page: number) => { + setPageNo(page); + }; + + const handlePageSizeChange = (current: number, size: number) => { + setPageSize(size); + setPageNo(1); + }; + return ( <>
+ {searching ? (
{ {/*
refreshTableList()}>
*/} + +
openImportSqlFile()}> + +
+
openSearch()}>
@@ -118,8 +267,9 @@ const SaveList = () => { )}
- - {(searchedList || consoleList)?.map((t) => { + + {(searchedList || savedConsoleList)?.map((t) => { + const environment = getConnectionEnvironment(t.dataSourceId); return ( { openConsole(t); }, }, + { + key: 'export', + label: , + onClick: () => { + exportSavedSql(t); + }, + }, { key: 'edit', label: , @@ -157,6 +314,12 @@ const SaveList = () => { className={styles.saveItem} >
+ {environment && ( + + )}
@@ -168,6 +331,18 @@ const SaveList = () => { })}
+
+ `共 ${total} 条`} + /> +
((props) => { const currentConnectionDetails = useWorkspaceStore((state) => state.currentConnectionDetails); + const abortControllerRef = useRef(null); + const requestIdRef = useRef(0); + const getTreeData = (refresh = false) => { if (!currentConnectionDetails?.id) { setTreeData([]); return; } + + const requestId = requestIdRef.current + 1; + requestIdRef.current = requestId; + + if (abortControllerRef.current) { + abortControllerRef.current.abort(); + } + abortControllerRef.current = new AbortController(); + const signal = abortControllerRef.current.signal; + const treeNodeType = currentConnectionDetails.supportDatabase ? TreeNodeType.DATA_SOURCE : TreeNodeType.DATABASE; - setTreeData(null); + setTreeData((prevTreeData) => (refresh && prevTreeData ? prevTreeData : null)); treeConfig[treeNodeType] - .getChildren?.({ - dataSourceId: currentConnectionDetails.id, - dataSourceName: currentConnectionDetails.alias, - refresh: refresh, - extraParams: { + .getChildren?.( + { dataSourceId: currentConnectionDetails.id, dataSourceName: currentConnectionDetails.alias, - databaseType: currentConnectionDetails.type, + refresh: refresh, + extraParams: { + dataSourceId: currentConnectionDetails.id, + dataSourceName: currentConnectionDetails.alias, + databaseType: currentConnectionDetails.type, + }, }, - }) + { signal }, + ) .then((res) => { + if (signal.aborted || requestIdRef.current !== requestId) return; setTreeData(res); }) .catch(() => { + if (signal.aborted || requestIdRef.current !== requestId) return; setTreeData([]); }); }; @@ -54,10 +72,19 @@ export default memo((props) => { getTreeData(); }, [currentConnectionDetails]); + useEffect(() => { + return () => { + requestIdRef.current += 1; + if (abortControllerRef.current) { + abortControllerRef.current.abort(); + } + }; + }, []); + return (
- +
); }); diff --git a/chat2db-client/src/pages/main/workspace/components/ViewAllTable/index.less b/chat2db-client/src/pages/main/workspace/components/ViewAllTable/index.less index 72735e7ff..7f85005c0 100644 --- a/chat2db-client/src/pages/main/workspace/components/ViewAllTable/index.less +++ b/chat2db-client/src/pages/main/workspace/components/ViewAllTable/index.less @@ -10,8 +10,12 @@ flex: 1; display: flex; width: 100%; + min-width: 0; + overflow: hidden; .tableBox{ flex: 1; + min-width: 0; + overflow: hidden; } .viewDDLBox{ border-left: 1px solid var(--color-border); @@ -46,6 +50,11 @@ display: flex; align-items: center; } + .headerBoxRight { + display: flex; + align-items: center; + white-space: nowrap; + } } .pagingBox { diff --git a/chat2db-client/src/pages/main/workspace/components/ViewAllTable/index.tsx b/chat2db-client/src/pages/main/workspace/components/ViewAllTable/index.tsx index fb22ae520..685358922 100644 --- a/chat2db-client/src/pages/main/workspace/components/ViewAllTable/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/ViewAllTable/index.tsx @@ -1,22 +1,25 @@ -import React, { memo, useEffect } from 'react'; +import React, { memo, useEffect, useState, useCallback } from 'react'; import i18n from '@/i18n'; import styles from './index.less'; import classnames from 'classnames'; -import { Table, Dropdown, Input, Pagination } from 'antd'; +import { Table, Dropdown, Input, Pagination, Button, Form, Modal, message, Spin } from 'antd'; import { DatabaseTypeCode, TreeNodeType, OperationColumn, WorkspaceTabType } from '@/constants'; -import sqlServer from '@/service/sql'; +import sqlServer, { IBatchModifyTableSqlParams } from '@/service/sql'; import type { ColumnsType } from 'antd/es/table'; import { IPageParams } from '@/typings'; import { v4 as uuid } from 'uuid'; +import ExecuteSQL from '@/components/ExecuteSQL'; // ----- components ----- import Iconfont from '@/components/Iconfont'; import { getRightClickMenu } from '@/blocks/Tree/hooks/useGetRightClickMenu'; import MenuLabel from '@/components/MenuLabel'; -import { setCurrentWorkspaceGlobalExtend } from '@/pages/main/workspace/store/common'; +import { setCurrentWorkspaceGlobalExtend, setPendingAiChat, setCurrentWorkspaceExtend, IBatchTableCommentResult } from '@/pages/main/workspace/store/common'; +import { deprecatedTable } from '@/blocks/Tree/functions/deprecatedTable'; // ----- store ----- import { addWorkspaceTab } from '@/pages/main/workspace/store/console'; +import { useWorkspaceStore } from '@/pages/main/workspace/store'; const { Search } = Input; @@ -32,18 +35,23 @@ interface IProps { } export default memo((props) => { - const { className, uniqueData } = props; - const [tableData, setTableData] = React.useState(null); - const [tableLoading, setTableLoading] = React.useState(false); + const { className, uniqueData, } = props; + const [tableData, setTableData] = useState(null); + const [tableLoading, setTableLoading] = useState(false); const tableBoxRef = React.useRef(null); - const [allTableWidth, setAllTableWidth] = React.useState(0); - const [allTableHeight, setAllTableHeight] = React.useState(0); - // 选中表 - const [activeId, setActiveId] = React.useState(''); - const [tableDataTotal, setTableDataTotal] = React.useState(0); - const [currentPageNo, setCurrentPageNo] = React.useState(1); - const [openDropdown, setOpenDropdown] = React.useState(undefined); - const [dropdownItems, setDropdownItems] = React.useState([]); + const [allTableWidth, setAllTableWidth] = useState(0); + const [allTableHeight, setAllTableHeight] = useState(0); + const [activeId, setActiveId] = useState(''); + const [tableDataTotal, setTableDataTotal] = useState(0); + const [currentPageNo, setCurrentPageNo] = useState(1); + const [openDropdown, setOpenDropdown] = useState(undefined); + const [dropdownItems, setDropdownItems] = useState([]); + const [isEditing, setIsEditing] = useState(false); + const [viewSqlModal, setViewSqlModal] = useState(false); + const [appendValue, setAppendValue] = useState(''); + const [guessLoading, setGuessLoading] = useState(false); + const [form] = Form.useForm(); + const [selectedRowKeys, setSelectedRowKeys] = useState([]); useEffect(() => { getTable({ @@ -77,6 +85,9 @@ export default memo((props) => { key: t.name, pinned: t.pinned, comment: t.comment, + rawComment: t.rawComment ?? null, + aiComment: t.aiComment ?? null, + rowCount: t.rowCount, extraParams: { ...uniqueData, tableName: t.name, @@ -111,87 +122,176 @@ export default memo((props) => { const getDropdownsItems = (record) => { const rightClickMenu = getRightClickMenu({ treeNodeData: record, - loadData: () => {}, + loadData: () => { + getTable({ + pageNo: currentPageNo, + pageSize: 1000, + }); + }, }); - const dropdownsItems: any = rightClickMenu.map((item) => { - return { + const buildMenuItem = (item: any): any => { + const menuItem: any = { key: item.key, type: item.type, - onClick: () => { - setOpenDropdown(false); - item.onClick(record); - }, label: , }; - }); + if (item.children && item.children.length > 0) { + menuItem.children = item.children.map(buildMenuItem); + } else { + menuItem.onClick = () => { + setOpenDropdown(false); + item.onClick(record); + }; + } + return menuItem; + }; + const dropdownsItems: any = rightClickMenu.map(buildMenuItem); - const excludeList = [ + const excludeList: any[] = [ OperationColumn.OpenTable, OperationColumn.CreateConsole, // OperationColumn.Pin, OperationColumn.ViewDDL, OperationColumn.EditTable, OperationColumn.CopyName, + OperationColumn.DeprecatedTable, + OperationColumn.TruncateTable, + 'dataOperation', // 数据操作(二级菜单:导入/导出/生成数据) ]; return dropdownsItems.filter((item) => excludeList.includes(item.type)); }; - const renderCell = (text, record) => { - return ( + const renderCell = (text, record, dataIndex) => { + if (dataIndex === 'name') { + return ( +
+ {text} +
+ ); + } + + if (dataIndex === 'rowCount') { + return ( +
+ {formatRowCount(text)} +
+ ); + } + + return isEditing ? ( + + + + ) : (
{text}
); }; + const formatRowCount = (count?: number) => { + if (count == null || count < 0) return '-'; + if (count >= 1000000) return `${(count / 1000000).toFixed(1)}M`; + if (count >= 1000) return `${(count / 1000).toFixed(1)}K`; + return count.toString(); + }; + const columns: ColumnsType = [ { title: 'Table name', dataIndex: 'name', key: 'name', - render: renderCell, + sorter: true, + render: (text, record) => renderCell(text, record, 'name'), + }, + { + title: 'Row Count', + dataIndex: 'rowCount', + key: 'rowCount', + width: 120, + sorter: true, + render: (text, record) => renderCell(text, record, 'rowCount'), }, { title: 'Comment', dataIndex: 'comment', key: 'comment', - render: renderCell, + render: (text, record) => renderCell(text, record, 'comment'), }, ]; - // 监听allTable的高度的变化 - useEffect(() => { - const resizeObserver = new ResizeObserver((entries) => { - const { width, height } = entries[0].contentRect; - setAllTableWidth(width); - setAllTableHeight(height); + const startEditing = () => { + form.setFieldsValue( + tableData?.reduce((acc, record) => { + acc[record.key] = record; + return acc; + }, {}) + ); + setIsEditing(true); + }; + + const cancelEditing = () => { + setIsEditing(false); + }; + + const saveAll = async () => { + try { + if (tableData && uniqueData.databaseName) { + const values = await form.validateFields(); + const newData = tableData.map((item) => ({ + ...item, + ...values[item.key], + })); + setIsEditing(false); + const params: IBatchModifyTableSqlParams = { + databaseName: uniqueData.databaseName, + dataSourceId: uniqueData.dataSourceId, + schemaName: uniqueData.schemaName, + refresh: true, + oldTables: tableData.map((item) => ({ + ...item, + comment: item.rawComment, + })), + newTables: newData, + }; + // 调用批量获取修改表的SQL语句的API + sqlServer.getBatchModifyTableSql(params).then(res => { + setViewSqlModal(true); // 显示SQL预览Modal + setAppendValue(res?.join('\n')); + }) + } + + } catch (errInfo) { + console.log('Validate Failed:', errInfo); + } + }; + const executeSuccessCallBack = () => { + setViewSqlModal(false); + message.success(i18n('common.text.successfulExecution')); + // 保存成功后,刷新左侧树 + getTable({ + refresh: true, + pageNo: currentPageNo, + pageSize: 1000, }); - resizeObserver.observe(tableBoxRef.current!); - }, []); + }; - // 监听allTable的宽度的变化 + // 监听tableBox的尺寸变化 useEffect(() => { const resizeObserver = new ResizeObserver((entries) => { const { width, height } = entries[0].contentRect; setAllTableWidth(width); setAllTableHeight(height); }); - resizeObserver.observe(tableBoxRef.current!); + if (tableBoxRef.current) { + resizeObserver.observe(tableBoxRef.current); + } + return () => resizeObserver.disconnect(); }, []); - // useEffect(() => { - // const record = tableData?.find((t) => t.key === activeId); - // if (record) { - // sqlServer - // .exportCreateTableSql({ - // ...uniqueData, - // tableName: record.name, - // } as any) - // .then((res) => { - // setViewDDLSql(res); - // }); - // } - // }, [activeId]); - const onSearch = (value: string) => { getTable({ pageNo: 1, @@ -200,14 +300,276 @@ export default memo((props) => { }); }; + const handleTableChange = (pagination: any, filters: any, sorter: any) => { + const params: any = { + pageNo: 1, + pageSize: 1000, + }; + + if (sorter.field) { + params.sortField = sorter.field; + params.sortOrder = sorter.order === 'ascend' ? 'ascend' : 'descend'; + } + + getTable(params); + }; + + const batchDeprecatedTable = async () => { + if (selectedRowKeys.length === 0) { + message.warning(i18n('common.viewAllTable.noSelectedTables')); + return; + } + + Modal.confirm({ + title: i18n('common.viewAllTable.batchDeprecatedConfirmTitle'), + content: i18n('common.viewAllTable.batchDeprecatedConfirmContent', selectedRowKeys.length), + okText: i18n('common.button.confirm'), + cancelText: i18n('common.button.cancel'), + onOk: async () => { + const selectedTables = tableData?.filter((item) => selectedRowKeys.includes(item.key)) || []; + let successCount = 0; + let failCount = 0; + + for (const table of selectedTables) { + try { + await sqlServer.deprecatedTable({ + dataSourceId: uniqueData.dataSourceId, + databaseName: uniqueData.databaseName, + schemaName: uniqueData.schemaName, + tableName: table.name, + }); + successCount++; + } catch (error) { + failCount++; + console.error(`Failed to deprecate table ${table.name}:`, error); + } + } + + setSelectedRowKeys([]); + + if (failCount === 0) { + message.success(i18n('common.viewAllTable.batchDeprecatedSuccess', successCount)); + } else { + message.warning(i18n('common.viewAllTable.batchDeprecatedPartialSuccess', successCount, failCount)); + } + + getTable({ + pageNo: currentPageNo, + pageSize: 1000, + }); + }, + }); + }; + + const batchOptimizeTable = async () => { + if (selectedRowKeys.length === 0) { + message.warning(i18n('common.viewAllTable.noSelectedTablesForOptimize')); + return; + } + + Modal.confirm({ + title: i18n('common.viewAllTable.batchOptimizeConfirmTitle'), + content: i18n('common.viewAllTable.batchOptimizeConfirmContent', selectedRowKeys.length), + okText: i18n('common.button.confirm'), + cancelText: i18n('common.button.cancel'), + onOk: async () => { + const selectedTables = tableData?.filter((item) => selectedRowKeys.includes(item.key)) || []; + const tableNames = selectedTables.map(t => t.name); + + try { + const results = await sqlServer.batchOptimizeTables({ + dataSourceId: Number(uniqueData.dataSourceId), + databaseName: uniqueData.databaseName, + schemaName: uniqueData.schemaName, + tableNames, + }); + + const successCount = results.filter((r: any) => r.success).length; + const failCount = results.length - successCount; + + setSelectedRowKeys([]); + + if (failCount === 0) { + message.success(i18n('common.viewAllTable.batchOptimizeSuccess', successCount)); + } else { + message.warning(i18n('common.viewAllTable.batchOptimizePartialSuccess', successCount, failCount)); + } + + getTable({ + pageNo: currentPageNo, + pageSize: 1000, + }); + } catch (error) { + message.error(i18n('common.viewAllTable.batchOptimizePartialSuccess', 0, tableNames.length)); + console.error('Failed to optimize tables:', error); + } + }, + }); + }; + + const batchAnalyzeTable = async () => { + if (selectedRowKeys.length === 0) { + message.warning(i18n('common.viewAllTable.noSelectedTablesForAnalyze')); + return; + } + + Modal.confirm({ + title: i18n('common.viewAllTable.batchAnalyzeConfirmTitle'), + content: i18n('common.viewAllTable.batchAnalyzeConfirmContent', selectedRowKeys.length), + okText: i18n('common.button.confirm'), + cancelText: i18n('common.button.cancel'), + onOk: async () => { + const selectedTables = tableData?.filter((item) => selectedRowKeys.includes(item.key)) || []; + const tableNames = selectedTables.map(t => t.name); + + try { + const results = await sqlServer.batchAnalyzeTables({ + dataSourceId: Number(uniqueData.dataSourceId), + databaseName: uniqueData.databaseName, + schemaName: uniqueData.schemaName, + tableNames, + }); + + const successCount = results.filter((r: any) => r.success).length; + const failCount = results.length - successCount; + + setSelectedRowKeys([]); + + if (failCount === 0) { + message.success(i18n('common.viewAllTable.batchAnalyzeSuccess', successCount)); + } else { + message.warning(i18n('common.viewAllTable.batchAnalyzePartialSuccess', successCount, failCount)); + } + + getTable({ + pageNo: currentPageNo, + pageSize: 1000, + }); + } catch (error) { + message.error(i18n('common.viewAllTable.batchAnalyzePartialSuccess', 0, tableNames.length)); + console.error('Failed to analyze tables:', error); + } + }, + }); + }; + + const openDataTransfer = () => { + if (selectedRowKeys.length === 0) { + message.warning('请先选择需要传输的表'); + return; + } + const selectedTables = tableData?.filter((item) => selectedRowKeys.includes(item.key)) || []; + const { openDataTransferModal } = useWorkspaceStore.getState(); + openDataTransferModal?.({ + dataSourceId: Number(uniqueData.dataSourceId), + databaseName: uniqueData.databaseName, + schemaName: uniqueData.schemaName, + tableNames: selectedTables.map((table) => table.name), + executedCallback: () => { + getTable({ + pageNo: currentPageNo, + pageSize: 1000, + }); + }, + }); + }; + + const rowSelection = { + selectedRowKeys, + onChange: (newSelectedRowKeys: React.Key[]) => { + setSelectedRowKeys(newSelectedRowKeys); + }, + columnWidth: 32, + getCheckboxProps: (record: any) => ({ + disabled: false, + }), + }; + + const pendingBatchesRef = React.useRef([]); + + const handleBatchCommentGenerated = useCallback((result: IBatchTableCommentResult) => { + if (result.tables && result.tables.length > 0) { + message.success(i18n('common.text.aiCommentGenerated')); + + const commentMap = new Map(); + result.tables.forEach((t) => { + commentMap.set(t.table_name, t.table_comment); + }); + + setTableData((prevData) => { + if (!prevData) return prevData; + const newData = prevData.map((item) => { + const newComment = commentMap.get(item.name); + if (newComment) { + return { ...item, comment: newComment }; + } + return item; + }); + + form.setFieldsValue( + newData.reduce((acc, record) => { + acc[record.key] = record; + return acc; + }, {}) + ); + + return newData; + }); + } + + if (pendingBatchesRef.current.length > 0) { + const nextBatch = pendingBatchesRef.current.shift()!; + setTimeout(() => { + setPendingAiChat({ + dataSourceId: Number(uniqueData.dataSourceId), + databaseName: uniqueData.databaseName, + schemaName: uniqueData.schemaName, + tableNames: nextBatch, + message: '请为这些表生成合适的中文注释', + promptType: 'NL_2_COMMENT_BATCH', + onBatchCommentGenerated: handleBatchCommentGenerated, + }); + setCurrentWorkspaceExtend('ai'); + }, 500); + } + }, [form, uniqueData]); + + const openAiChatForGuess = useCallback(() => { + if (!tableData || tableData.length === 0) { + message.warning(i18n('common.text.noTables')); + return; + } + const tableNamesWithoutComment = tableData + .filter((t) => !t.comment || !t.comment.trim()) + .map((t) => t.name); + if (tableNamesWithoutComment.length === 0) { + message.warning(i18n('common.text.allTablesHaveComments')); + return; + } + const BATCH_SIZE = 20; + const batches: string[][] = []; + for (let i = 0; i < tableNamesWithoutComment.length; i += BATCH_SIZE) { + batches.push(tableNamesWithoutComment.slice(i, i + BATCH_SIZE)); + } + pendingBatchesRef.current = batches.slice(1); + const firstBatch = batches[0]; + + setGuessLoading(true); + setPendingAiChat({ + dataSourceId: Number(uniqueData.dataSourceId), + databaseName: uniqueData.databaseName, + schemaName: uniqueData.schemaName, + tableNames: firstBatch, + message: '请为这些表生成合适的中文注释', + promptType: 'NL_2_COMMENT_BATCH', + onBatchCommentGenerated: handleBatchCommentGenerated, + }); + setCurrentWorkspaceExtend('ai'); + setGuessLoading(false); + message.success('已切换到 AI 助手,请在 AI 聊天面板中查看推荐结果'); + }, [tableData, uniqueData, handleBatchCommentGenerated]); + return ( - //
@@ -227,6 +589,55 @@ export default memo((props) => {
+ {isEditing ? ( + <> + + + + + + + ) : ( + <> + + + + + + + )}
@@ -241,39 +652,41 @@ export default memo((props) => { }} >
-
{ - return { - onClick: () => { - setActiveId(row.key); - setCurrentWorkspaceGlobalExtend({ - code: 'viewDDL', - uniqueData: { - ...uniqueData, - tableName: row.name, - } - }); - }, - onContextMenu: (event) => { - event.preventDefault(); - setActiveId(row.key); - setOpenDropdown(true); - setDropdownItems(getDropdownsItems(tableData?.find((t) => t.key === row.key))); - }, - }; - }} - virtual - scroll={{ x: allTableWidth - 10, y: allTableHeight - 25 }} - columns={columns} - pagination={false} - dataSource={tableData || []} - /> + +
{ + return { + onClick: () => { + setActiveId(row.key); + setCurrentWorkspaceGlobalExtend({ + code: 'viewDDL', + uniqueData: { + ...uniqueData, + tableName: row.name, + } + }); + }, + onContextMenu: (event) => { + event.preventDefault(); + setActiveId(row.key); + setOpenDropdown(true); + setDropdownItems(getDropdownsItems(tableData?.find((t) => t.key === row.key))); + }, + }; + }} + virtual + scroll={{ x: allTableWidth - 10, y: allTableHeight - 25 }} + columns={columns} + pagination={false} + dataSource={tableData || []} + /> + - {/* {tableDataTotal > 1000 && ( - )} */}
((props) => { total={tableDataTotal} />
+ setViewSqlModal(false)} // 关闭Modal + width="60vw" + maskClosable={false} + footer={false} + destroyOnHidden={true} + > + + ); }); diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceExtend/config.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceExtend/config.tsx index 9b5a0cb4f..beb634a00 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceExtend/config.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceExtend/config.tsx @@ -2,6 +2,7 @@ import i18n from '@/i18n'; import Output from '@/components/Output'; import GlobalExtendComponents from './GlobalExtendComponents'; import SaveList from '../SaveList'; +import AiChat from '@/components/AiChat'; import ViewDDL from '@/components/ViewDDL'; interface IToolbar { @@ -14,7 +15,8 @@ interface IToolbar { export enum GlobalComponents { view_ddl = 'viewDDL', executive_log = 'executiveLog', - save_list = 'saveList' + save_list = 'saveList', + ai = 'ai' } export const globalComponents: { @@ -22,7 +24,8 @@ export const globalComponents: { } = { [GlobalComponents.view_ddl]: ViewDDL, [GlobalComponents.executive_log]: Output, - [GlobalComponents.save_list]: SaveList + [GlobalComponents.save_list]: SaveList, + [GlobalComponents.ai]: AiChat } export const extendConfig: IToolbar[] = [ @@ -44,4 +47,10 @@ export const extendConfig: IToolbar[] = [ icon: '\ue619', components: globalComponents.saveList, }, + { + code: 'ai', + title: i18n('workspace.title.aiChat'), + icon: '\uec5f', + components: globalComponents.ai, + }, ]; diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx index 01579e396..a3de117d7 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx @@ -5,6 +5,12 @@ import styles from './index.less'; import TableList from '../TableList'; import WorkspaceLeftHeader from '../WorkspaceLeftHeader'; import CreateDatabase from '@/components/CreateDatabase'; +import ImportDataModal from '@/components/ImportDataModal'; +import ExportDataModal from '@/components/ExportDataModal'; +import ExportSchemaDocModal from '@/components/ExportSchemaDocModal'; +import DataGenerationModal from '@/components/DataGenerationModal'; +import ExecuteSqlStatementModal from '@/components/ExecuteSqlStatementModal'; +import DataTransferModal from '@/components/DataTransferModal'; import Iconfont from '@/components/Iconfont'; import { useConnectionStore } from '@/pages/main/store/connection'; import { setMainPageActiveTab } from '@/pages/main/store/main'; @@ -42,6 +48,12 @@ const WorkspaceLeft = memo(() => { )} + + + + + + ); }); diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeftHeader/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeftHeader/index.tsx index b678d3b29..9f19bf58c 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeftHeader/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeftHeader/index.tsx @@ -5,6 +5,7 @@ import styles from './index.less'; // ---- store ---- import { useConnectionStore } from '@/pages/main/store/connection'; +import { groupConnectionList } from '@/pages/main/store/connection/utils'; import { useWorkspaceStore } from '@/pages/main/workspace/store'; import { setCurrentConnectionDetails } from '@/pages/main/workspace/store/common'; @@ -35,7 +36,7 @@ export default memo(() => { {/* {item.environment.shortName} */} - +
@@ -46,13 +47,20 @@ export default memo(() => { const connectionItems = useMemo(() => { return ( - connectionList?.map((item) => { + groupConnectionList(connectionList || []).map((group) => { return { - key: item.id, - label: renderConnectionLabel(item), - onClick: () => { - setCurrentConnectionDetails(item); - }, + type: 'group' as const, + key: group.key, + label: group.environment?.name || '', + children: group.connections.map((item) => { + return { + key: item.id, + label: renderConnectionLabel(item), + onClick: () => { + setCurrentConnectionDetails(item); + }, + }; + }), }; }) || [] ); diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceTabs/index.less b/chat2db-client/src/pages/main/workspace/components/WorkspaceTabs/index.less index 7a5f8ef54..e8eb4c56b 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceTabs/index.less +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceTabs/index.less @@ -18,3 +18,21 @@ align-items: center; } } + +.tabLabel { + display: flex; + align-items: center; + gap: 6px; +} + +.envTag { + flex-shrink: 0; + width: 8px; + height: 8px; + border-radius: 50%; + background-color: var(--color-primary); +} + +.tabTitle { + flex: 1; +} diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceTabs/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceTabs/index.tsx index bf878431e..820761ca0 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceTabs/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceTabs/index.tsx @@ -1,7 +1,8 @@ -import React, { memo, useEffect, useMemo, Fragment } from 'react'; +import React, { memo, useEffect, useMemo, Fragment, useState, useRef } from 'react'; import styles from './index.less'; import i18n from '@/i18n'; -import { Button } from 'antd'; +import { Button, message, Spin } from 'antd'; +import { LoadingOutlined } from '@ant-design/icons'; // ----- constants ----- import { WorkspaceTabType, workspaceTabConfig } from '@/constants'; @@ -13,6 +14,10 @@ import SearchResult from '@/components/SearchResult'; import DatabaseTableEditor from '@/blocks/DatabaseTableEditor'; import SQLExecute from '../SQLExecute'; import ViewAllTable from '../ViewAllTable'; +import RedisDataView from '../RedisDataView'; +import RedisMonitorView from '../RedisMonitorView'; +import ERDiagram from '../ERDiagram'; +import SchemaDiffPanel from '@/blocks/SchemaDiff'; import Iconfont from '@/components/Iconfont'; import ShortcutKey from '@/components/ShortcutKey'; @@ -25,11 +30,18 @@ import { } from '@/pages/main/workspace/store/console'; import { useWorkspaceStore } from '@/pages/main/workspace/store'; import { useTreeStore } from '@/blocks/Tree/treeStore'; +import { useConnectionStore } from '@/pages/main/store/connection'; // ----- services ----- import historyService from '@/service/history'; +import sqlService from '@/service/sql'; import indexedDB from '@/indexedDB'; +import connectToEventSource from '@/utils/eventSource'; +import { formatParams } from '@/utils/url'; +import { getCookie } from '@/utils'; +import { v4 as uuidv4 } from 'uuid'; +import { getConsoleEditor } from '../SQLExecute/consoleEditorRegistry'; const WorkspaceTabs = memo(() => { const { activeConsoleId, consoleList, workspaceTabList } = useWorkspaceStore((state) => { @@ -40,14 +52,36 @@ const WorkspaceTabs = memo(() => { }; }); + const { connectionList } = useConnectionStore((state) => { + return { + connectionList: state.connectionList, + }; + }); + const currentConnectionDetails = useWorkspaceStore((state) => state.currentConnectionDetails); + const [generatingTitleKey, setGeneratingTitleKey] = useState(null); + const closeEventSourceRef = useRef<(() => void) | null>(null); + const prevEnvironmentRef = useRef(null); + + const getConnectionEnvironment = (dataSourceId?: number) => { + if (!dataSourceId || !connectionList) return null; + const connection = connectionList.find((item) => item.id === dataSourceId); + return connection?.environment || null; + }; + + const isReleaseEnvironment = (environment: { name: string; shortName: string } | null) => { + if (!environment) return false; + const envName = environment.name?.toLowerCase(); + const envShortName = environment.shortName?.toLowerCase(); + return envName?.includes('release') || envShortName === 'release'; + }; // 获取console useEffect(() => { getOpenConsoleList(); }, []); - // consoleList 先转换为通用的 workspaceTabList + // consoleList 先转换为通用的 workspaceTabList,同时保留非 consoleList 管理的本地 tab useEffect(() => { const _workspaceTabItems = consoleList?.map((item) => { @@ -64,10 +98,17 @@ const WorkspaceTabs = memo(() => { status: item.status, ddl: item.ddl, connectable: item.connectable, + name: item.name, // 添加名称用于动态创建 loadSQL }, }; }) || []; - setWorkspaceTabList(_workspaceTabItems); + + const currentWorkspaceTabList = useWorkspaceStore.getState().workspaceTabList; + const localTabs = (currentWorkspaceTabList || []).filter( + (tab) => !_workspaceTabItems.some((ct) => ct.id === tab.id), + ); + + setWorkspaceTabList([..._workspaceTabItems, ...localTabs]); }, [consoleList]); // 关闭tab @@ -80,6 +121,13 @@ const WorkspaceTabs = memo(() => { historyService.updateSavedConsole(p).then(() => { indexedDB.deleteData('chat2db', 'workspaceConsoleDDL', key); }); + + const { consoleList: currentConsoleList } = useWorkspaceStore.getState(); + if (currentConsoleList) { + useWorkspaceStore.setState({ + consoleList: currentConsoleList.filter((item) => item.id !== key), + }); + } }; const createNewConsole = () => { @@ -126,14 +174,28 @@ const WorkspaceTabs = memo(() => { // 切换tab const onTabChange = (key: string | null) => { + const tab = workspaceTabList?.find((item) => String(item.id) === String(key)); + setActiveConsoleId(key); + + const environment = getConnectionEnvironment(tab?.uniqueData?.dataSourceId); + + if (isReleaseEnvironment(environment)) { + const envKey = environment?.id?.toString() || 'release'; + if (prevEnvironmentRef.current !== envKey) { + message.warning(i18n('workspace.tips.enterReleaseEnvironment')); + prevEnvironmentRef.current = envKey; + } + } else { + prevEnvironmentRef.current = null; + } }; // 编辑名称 - const editableNameOnBlur = (t: ITabItem) => { + const editableNameOnBlur = (t: ITabItem, value: string) => { const _params: any = { id: t.key, - name: t.label, + name: value, }; historyService.updateSavedConsole(_params); @@ -142,7 +204,7 @@ const WorkspaceTabs = memo(() => { if (item.id === t.key) { return { ...item, - title: t.label, + title: value, }; } return item; @@ -150,6 +212,104 @@ const WorkspaceTabs = memo(() => { setWorkspaceTabList(_workspaceTabList); }; + // AI 生成标题 + const handleGenerateTitle = async (t: ITabItem) => { + const consoleId = t.key as number; + const tabData = workspaceTabList?.find((item) => item.id === consoleId); + if (!tabData) return; + + // 获取 SQL 内容,优先从编辑器实例获取最新内容 + let sqlContent = ''; + const editorRef = getConsoleEditor(consoleId); + if (editorRef?.current?.editorRef) { + sqlContent = editorRef.current.editorRef.getAllContent() || ''; + } + + // 如果编辑器实例中没有内容,尝试从 indexedDB 获取(可能有未保存的修改) + if (!sqlContent) { + try { + const userId = getCookie('CHAT2DB.USER_ID'); + const cachedData: any = await indexedDB.getDataByCursor('chat2db', 'workspaceConsoleDDL', { + consoleId, + userId, + }); + if (cachedData?.[0]?.ddl) { + sqlContent = cachedData[0].ddl; + } else { + sqlContent = tabData.uniqueData?.ddl || ''; + } + } catch { + sqlContent = tabData.uniqueData?.ddl || ''; + } + } + + if (!sqlContent?.trim()) { + message.warning(i18n('workspace.tips.noSqlContent')); + return; + } + + setGeneratingTitleKey(consoleId); + const uid = uuidv4(); + let generatedTitle = ''; + + const params = formatParams({ + message: sqlContent, + promptType: 'TITLE_GENERATION', + dataSourceId: tabData.uniqueData?.dataSourceId, + databaseName: tabData.uniqueData?.databaseName, + schemaName: tabData.uniqueData?.schemaName, + }); + + const handleMessage = (content?: string) => { + if (content) { + generatedTitle += content; + } + }; + + const handleDone = () => { + setGeneratingTitleKey(null); + // 清理标题,去除引号和多余空白 + const cleanTitle = generatedTitle.replace(/^["'`]+|["'`]+$/g, '').trim(); + if (cleanTitle) { + const _params: any = { + id: consoleId, + name: cleanTitle, + }; + historyService.updateSavedConsole(_params); + + const _workspaceTabList: any = + workspaceTabList?.map((item) => { + if (item.id === consoleId) { + return { + ...item, + title: cleanTitle, + }; + } + return item; + }) || []; + setWorkspaceTabList(_workspaceTabList); + message.success(i18n('common.tips.updateSuccess')); + } else { + message.warning(i18n('workspace.tips.generateTitleFailed')); + } + }; + + const handleError = (error: string) => { + console.error('AI chat error:', error); + message.error(i18n('workspace.tips.generateTitleFailed')); + setGeneratingTitleKey(null); + }; + + closeEventSourceRef.current = connectToEventSource({ + url: `/api/ai/chat?${params}`, + uid, + onOpen: () => {}, + onMessage: handleMessage, + onDone: handleDone, + onError: handleError, + }); + }; + // 修改tab详情 const changeTabDetails = (data: IWorkspaceTab) => { const list = @@ -165,6 +325,68 @@ const WorkspaceTabs = memo(() => { // 渲染sql执行器 const renderSQLExecute = (item: IWorkspaceTab) => { const { uniqueData } = item; + + // 动态创建 loadSQL (当从 consoleList 恢复时 loadSQL 会丢失) + const getLoadSQL = () => { + if (uniqueData.loadSQL) { + return uniqueData.loadSQL; + } + + // 根据 tab 类型动态创建 loadSQL + const commonParams = { + dataSourceId: uniqueData.dataSourceId, + databaseName: uniqueData?.databaseName, + schemaName: uniqueData?.schemaName, + }; + + switch (item.type) { + case WorkspaceTabType.FUNCTION: + return () => { + return sqlService.getFunctionDetail({ + ...commonParams, + functionName: uniqueData.functionName || uniqueData.name, + } as any).then((res) => { + return res.functionBody || ''; + }); + }; + + case WorkspaceTabType.PROCEDURE: + return () => { + return sqlService.getProcedureDetail({ + ...commonParams, + procedureName: uniqueData.procedureName || uniqueData.name, + } as any).then((res) => { + return res.procedureBody || ''; + }); + }; + + case WorkspaceTabType.TRIGGER: + return () => { + return sqlService.getTriggerDetail({ + ...commonParams, + triggerName: uniqueData.triggerName || uniqueData.name, + } as any).then((res) => { + return res.triggerBody || ''; + }); + }; + + case WorkspaceTabType.VIEW: + return () => { + return sqlService.getViewDetail({ + ...commonParams, + tableName: uniqueData.tableName || uniqueData.name, + } as any).then((res) => { + return res.ddl || ''; + }); + }; + + default: + return undefined; + } + }; + + const loadSQL = getLoadSQL(); + return ( { supportSchema: uniqueData.supportSchema, }} initDDL={uniqueData.ddl} - loadSQL={uniqueData.loadSQL} + loadSQL={loadSQL} /> ); }; @@ -240,23 +462,74 @@ const WorkspaceTabs = memo(() => { return renderSearchResult(item); case WorkspaceTabType.ViewAllTable: return renderViewAllTable(item); + case WorkspaceTabType.RedisData: + return ; + case WorkspaceTabType.RedisMonitor: + return ; + case WorkspaceTabType.ViewERDiagram: + return renderViewERDiagram(item); + case WorkspaceTabType.SchemaDiff: + return ; default: return
Unknown
; } }; - // tab列表 + // 渲染 ER 图 + const renderViewERDiagram = (item: IWorkspaceTab) => { + const { uniqueData } = item; + return ; + }; + // 判断是否是可以生成标题的 tab 类型 + const canGenerateTitleType = (type: WorkspaceTabType) => { + return ( + type === WorkspaceTabType.CONSOLE || + type === WorkspaceTabType.FUNCTION || + type === WorkspaceTabType.PROCEDURE || + type === WorkspaceTabType.TRIGGER || + type === WorkspaceTabType.VIEW || + type === ('table' as any) || + !type + ); + }; + + const getEnvLabelStyle = (environment: { name: string; shortName: string } | null, isActive: boolean) => { + if (!environment || !isActive) return {}; + const envName = environment.name?.toLowerCase(); + const envShortName = environment.shortName?.toLowerCase(); + if (envName?.includes('test') || envShortName === 'test') return { color: '#1890ff' }; + if (envName?.includes('release') || envShortName === 'release') return { color: '#ff4d4f' }; + return {}; + }; + + // tab 列表 const workspaceTabItems = useMemo(() => { return workspaceTabList?.map((item) => { + const environment = getConnectionEnvironment(item.uniqueData?.dataSourceId); + const isGeneratingTitle = generatingTitleKey === item.id; + const isActive = activeConsoleId === item.id; return { - prefixIcon: workspaceTabConfig[item.type]?.icon, - label: item.title, + prefixIcon: isGeneratingTitle + ? } size="small" /> + : workspaceTabConfig[item.type]?.icon, + label: ( +
+ {environment && ( + + )} + + {isGeneratingTitle ? i18n('common.text.generatingTitle') : item.title} + +
+ ), + name: isGeneratingTitle ? i18n('common.text.generatingTitle') : item.title, key: item.id, editableName: item.type === WorkspaceTabType.CONSOLE, + canGenerateTitle: canGenerateTitleType(item.type), children: {workspaceTabConnectionMap(item)}, }; }); - }, [workspaceTabList, activeConsoleId]); + }, [workspaceTabList, activeConsoleId, connectionList, generatingTitleKey]); function renderCreateConsoleButton() { return ( @@ -283,6 +556,8 @@ const WorkspaceTabs = memo(() => { activeKey={activeConsoleId} editableNameOnBlur={editableNameOnBlur} items={workspaceTabItems} + onGenerateTitle={handleGenerateTitle} + generatingTitleKey={generatingTitleKey} /> ) : (
diff --git a/chat2db-client/src/pages/main/workspace/index.less b/chat2db-client/src/pages/main/workspace/index.less index eb0104395..36eca1378 100644 --- a/chat2db-client/src/pages/main/workspace/index.less +++ b/chat2db-client/src/pages/main/workspace/index.less @@ -1,6 +1,7 @@ @import '../../../styles/var.less'; .workspace { + position: relative; width: 100%; height: 100%; display: flex; @@ -91,3 +92,31 @@ .hiddenPanelLeft { display: none; } + +.expandPanelLeft { + position: absolute; + top: 40px; + left: 0px; + z-index: 20; + width: 24px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + border: 1px solid var(--color-border-secondary); + border-left: 0px; + border-radius: 0px 4px 4px 0px; + background-color: var(--color-bg-subtle); + color: var(--color-text-secondary); + cursor: pointer; + box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.08); + + &:hover { + color: var(--color-primary); + background-color: var(--color-hover-bg); + } + + i { + font-size: 14px; + } +} diff --git a/chat2db-client/src/pages/main/workspace/index.tsx b/chat2db-client/src/pages/main/workspace/index.tsx index 9d44b6a91..01aaf29b7 100644 --- a/chat2db-client/src/pages/main/workspace/index.tsx +++ b/chat2db-client/src/pages/main/workspace/index.tsx @@ -1,20 +1,26 @@ import React, { memo, useCallback, useEffect, useRef } from 'react'; import classnames from 'classnames'; +import { Tooltip } from 'antd'; import { useWorkspaceStore } from '@/pages/main/workspace/store'; -import { setPanelLeftWidth } from '@/pages/main/workspace/store/config'; +import { setPanelLeft, setPanelLeftWidth } from '@/pages/main/workspace/store/config'; import DraggableContainer from '@/components/DraggableContainer'; +import Iconfont from '@/components/Iconfont'; import WorkspaceLeft from './components/WorkspaceLeft'; import WorkspaceRight from './components/WorkspaceRight'; +import i18n from '@/i18n'; import useMonacoTheme from '@/components/MonacoEditor/useMonacoTheme'; import shortcutKeyCreateConsole from './functions/shortcutKeyCreateConsole'; import styles from './index.less'; +const COLLAPSE_PANEL_LEFT_MEDIA = '(max-width: 960px)'; + const workspacePage = memo(() => { const draggableRef = useRef(); + const isAutoCollapseCheckedRef = useRef(false); const { panelLeft, panelLeftWidth } = useWorkspaceStore((state) => { return { panelLeft: state.layout.panelLeft, @@ -30,6 +36,16 @@ const workspacePage = memo(() => { shortcutKeyCreateConsole(); }, []); + useEffect(() => { + if (isAutoCollapseCheckedRef.current) { + return; + } + isAutoCollapseCheckedRef.current = true; + if (window.matchMedia(COLLAPSE_PANEL_LEFT_MEDIA).matches) { + setPanelLeft(false); + } + }, []); + const draggableContainerResize = useCallback((data: number) => { setPanelLeftWidth(data); }, []); @@ -46,6 +62,13 @@ const workspacePage = memo(() => {
+ {!panelLeft && ( + +
setPanelLeft(true)}> + +
+
+ )} ); }); diff --git a/chat2db-client/src/pages/main/workspace/store/aiChatStore.ts b/chat2db-client/src/pages/main/workspace/store/aiChatStore.ts new file mode 100644 index 000000000..d4d835f6b --- /dev/null +++ b/chat2db-client/src/pages/main/workspace/store/aiChatStore.ts @@ -0,0 +1,504 @@ +import { create } from 'zustand'; +import { persist, createJSONStorage } from 'zustand/middleware'; +import { v4 as uuidv4 } from 'uuid'; +import aiConversationService from '@/service/aiConversation'; +import { IAiConversation, IAiMessage } from '@/typings/aiConversation'; +import { createIndexedDbStorage } from '@/utils/indexedDbStorage'; + +export type ChatStateType = + | 'IDLE' + | 'AUTO_SELECTING_TABLES' + | 'FETCHING_TABLE_SCHEMA' + | 'EXECUTING_EXPLAIN' + | 'BUILDING_PROMPT' + | 'STREAMING' + | 'COMPLETED' + | 'FAILED'; + +export interface IChatMessage { + id: string; + role: 'user' | 'assistant'; + content: string; + thinking?: string; + promptType?: string; + sqlExtracted?: string | null; + sequenceNo?: number; +} + +export interface AiChatSession { + sessionId: string; + state: ChatStateType; + messages: IChatMessage[]; + currentContent: string; + currentThinking: string; + selectedTables?: string[]; + schemaInfo?: string; + explainResult?: { sql: string; plan: string[][]; formatted: string; success: boolean }; + error?: string; + dataSourceId?: number | null; + databaseName?: string | null; + schemaName?: string | null; + title?: string | null; + createdAt: number; + updatedAt: number; +} + +export interface IConversationSummary { + conversationId: string; + title?: string | null; + dataSourceId?: number | null; + dataSourceName?: string | null; + databaseName?: string | null; + schemaName?: string | null; + messageCount: number; + lastMessagePreview?: string | null; + status: 'ACTIVE' | 'ARCHIVED' | 'DELETED'; + gmtCreate?: string; + gmtModified?: string; +} + +interface ILastRequest { + message: string; + promptType: string; + dataSourceId?: number; + databaseName?: string; + schemaName?: string | null; + tableNames?: string[] | null; + ext?: string; + conversationId?: string; + history?: string; + previousSql?: string | null; + isRevision?: boolean; +} + +interface IAiChatStore { + sessions: Record; + currentSessionId: string | null; + lastRequest: ILastRequest | null; + conversationList: IConversationSummary[]; + conversationListLoading: boolean; + conversationListHasMore: boolean; + conversationListPageNo: number; + + createSession: (sessionId: string, initial?: Partial) => void; + updateState: (sessionId: string, state: ChatStateType) => void; + appendContent: (sessionId: string, content: string, thinking?: string) => void; + addMessage: (sessionId: string, message: IChatMessage) => void; + setSelectedTables: (sessionId: string, tables: string[]) => void; + setSchemaInfo: (sessionId: string, ddl: string) => void; + setExplainResult: (sessionId: string, explain: AiChatSession['explainResult']) => void; + setError: (sessionId: string, error: string) => void; + setLastRequest: (req: ILastRequest) => void; + clearSession: (sessionId: string) => void; + resetCurrentContent: (sessionId: string) => void; + renameSession: (sessionId: string, title: string) => Promise; + deleteSession: (sessionId: string) => Promise; + switchToSession: (sessionId: string) => void; + startNewConversation: (boundInfo?: { + dataSourceId?: number | null; + databaseName?: string | null; + schemaName?: string | null; + }) => string; + loadConversationList: (reset?: boolean) => Promise; + loadConversationDetail: (conversationId: string) => Promise; + syncLocalSessionFromDetail: (conversationId: string, conversation: IAiConversation, messages: IAiMessage[]) => void; +} + +const generateTitleFromMessage = (message: string): string => { + if (!message) return '新对话'; + const trimmed = message.trim().replace(/\s+/g, ' '); + return trimmed.length > 20 ? trimmed.substring(0, 20) + '...' : trimmed; +}; + +const persistStorage = createJSONStorage(() => createIndexedDbStorage('aiChatStore', 'current')); + +export const useAiChatStore = create()( + persist( + (set, get) => ({ + sessions: {}, + currentSessionId: null, + lastRequest: null, + conversationList: [], + conversationListLoading: false, + conversationListHasMore: false, + conversationListPageNo: 0, + + createSession: (sessionId: string, initial?: Partial) => { + set((state) => { + const now = Date.now(); + const newSession: AiChatSession = { + sessionId, + state: 'IDLE', + messages: [], + currentContent: '', + currentThinking: '', + createdAt: now, + updatedAt: now, + ...initial, + }; + const existsInList = state.conversationList.some((item) => item.conversationId === sessionId); + const conversationList = existsInList + ? state.conversationList + : [ + { + conversationId: sessionId, + title: newSession.title ?? '新对话', + dataSourceId: newSession.dataSourceId ?? null, + dataSourceName: null, + databaseName: newSession.databaseName ?? null, + schemaName: newSession.schemaName ?? null, + messageCount: newSession.messages.length, + lastMessagePreview: null, + status: 'ACTIVE' as const, + }, + ...state.conversationList, + ]; + return { + sessions: { ...state.sessions, [sessionId]: newSession }, + conversationList, + currentSessionId: sessionId, + }; + }); + }, + + updateState: (sessionId: string, newState: ChatStateType) => { + set((state) => { + const session = state.sessions[sessionId]; + if (!session) { + return state; + } + return { + sessions: { + ...state.sessions, + [sessionId]: { ...session, state: newState, updatedAt: Date.now() }, + }, + }; + }); + }, + + appendContent: (sessionId: string, content: string, thinking?: string) => { + set((state) => { + const session = state.sessions[sessionId]; + if (!session) { + return state; + } + return { + sessions: { + ...state.sessions, + [sessionId]: { + ...session, + currentContent: session.currentContent + (content || ''), + currentThinking: session.currentThinking + (thinking || ''), + updatedAt: Date.now(), + }, + }, + }; + }); + }, + + addMessage: (sessionId: string, message: IChatMessage) => { + set((state) => { + const session = state.sessions[sessionId]; + if (!session) { + return state; + } + return { + sessions: { + ...state.sessions, + [sessionId]: { + ...session, + messages: [...session.messages, message], + updatedAt: Date.now(), + }, + }, + conversationList: state.conversationList.map((item) => + item.conversationId === sessionId + ? { + ...item, + messageCount: item.messageCount + 1, + lastMessagePreview: message.content, + } + : item, + ), + }; + }); + }, + + setSelectedTables: (sessionId: string, tables: string[]) => { + set((state) => { + const session = state.sessions[sessionId]; + if (!session) { + return state; + } + return { + sessions: { + ...state.sessions, + [sessionId]: { ...session, selectedTables: tables, updatedAt: Date.now() }, + }, + }; + }); + }, + + setSchemaInfo: (sessionId: string, ddl: string) => { + set((state) => { + const session = state.sessions[sessionId]; + if (!session) { + return state; + } + return { + sessions: { + ...state.sessions, + [sessionId]: { ...session, schemaInfo: ddl, updatedAt: Date.now() }, + }, + }; + }); + }, + + setExplainResult: (sessionId: string, explain: AiChatSession['explainResult']) => { + set((state) => { + const session = state.sessions[sessionId]; + if (!session) { + return state; + } + return { + sessions: { + ...state.sessions, + [sessionId]: { ...session, explainResult: explain, updatedAt: Date.now() }, + }, + }; + }); + }, + + setError: (sessionId: string, error: string) => { + set((state) => { + const session = state.sessions[sessionId]; + if (!session) { + return state; + } + return { + sessions: { + ...state.sessions, + [sessionId]: { ...session, state: 'FAILED', error, updatedAt: Date.now() }, + }, + }; + }); + }, + + setLastRequest: (req: ILastRequest) => { + set({ lastRequest: req }); + }, + + clearSession: (sessionId: string) => { + set((state) => { + const { [sessionId]: _omit, ...rest } = state.sessions; + void _omit; + return { + sessions: rest, + currentSessionId: state.currentSessionId === sessionId ? null : state.currentSessionId, + }; + }); + }, + + resetCurrentContent: (sessionId: string) => { + set((state) => { + const session = state.sessions[sessionId]; + if (!session) { + return state; + } + return { + sessions: { + ...state.sessions, + [sessionId]: { + ...session, + currentContent: '', + currentThinking: '', + updatedAt: Date.now(), + }, + }, + }; + }); + }, + + renameSession: async (sessionId: string, title: string) => { + set((state) => { + const session = state.sessions[sessionId]; + if (session) { + return { + sessions: { ...state.sessions, [sessionId]: { ...session, title } }, + }; + } + return state; + }); + try { + await aiConversationService.renameAiConversation({ conversationId: sessionId, title }); + } catch (e) { + console.error('[AiChatStore] rename failed:', e); + } + set((state) => { + return { + conversationList: state.conversationList.map((item) => + item.conversationId === sessionId ? { ...item, title } : item, + ), + }; + }); + }, + + deleteSession: async (sessionId: string) => { + const { [sessionId]: _omit, ...rest } = get().sessions; + void _omit; + set((state) => ({ + sessions: rest, + currentSessionId: state.currentSessionId === sessionId ? null : state.currentSessionId, + conversationList: state.conversationList.filter((item) => item.conversationId !== sessionId), + })); + try { + await aiConversationService.deleteAiConversation({ conversationId: sessionId }); + } catch (e) { + console.error('[AiChatStore] delete failed:', e); + } + }, + + switchToSession: (sessionId: string) => { + set({ currentSessionId: sessionId }); + const session = get().sessions[sessionId]; + if (!session) { + get().loadConversationDetail(sessionId); + } + }, + + startNewConversation: (boundInfo) => { + const newSessionId = uuidv4(); + get().createSession(newSessionId, { + dataSourceId: boundInfo?.dataSourceId ?? null, + databaseName: boundInfo?.databaseName ?? null, + schemaName: boundInfo?.schemaName ?? null, + title: '新对话', + }); + aiConversationService + .createAiConversation({ + conversationId: newSessionId, + dataSourceId: boundInfo?.dataSourceId ?? undefined, + databaseName: boundInfo?.databaseName ?? undefined, + schemaName: boundInfo?.schemaName ?? undefined, + title: '新对话', + }) + .then((created) => { + if (created) { + set((s) => { + const exists = s.conversationList.some( + (item) => item.conversationId === created.conversationId, + ); + if (exists) { + return s; + } + const summary: IConversationSummary = { + conversationId: created.conversationId, + title: created.title ?? '新对话', + dataSourceId: created.dataSourceId ?? null, + dataSourceName: created.dataSourceName ?? null, + databaseName: created.databaseName ?? null, + schemaName: created.schemaName ?? null, + messageCount: created.messageCount ?? 0, + lastMessagePreview: created.lastMessagePreview ?? null, + status: created.status, + gmtCreate: created.gmtCreate, + gmtModified: created.gmtModified, + }; + return { + conversationList: [summary, ...s.conversationList], + }; + }); + } + }) + .catch((e) => { + console.warn('[AiChatStore] startNewConversation create failed (offline?):', e); + }); + return newSessionId; + }, + + loadConversationList: async (reset = false) => { + const state = get(); + if (state.conversationListLoading) { + return; + } + const nextPageNo = reset ? 1 : state.conversationListPageNo + 1; + set({ conversationListLoading: true }); + try { + const res = await aiConversationService.queryAiConversations({ + pageNo: nextPageNo, + pageSize: 20, + }); + const list = res.data || []; + set((s) => ({ + conversationList: reset ? list : [...s.conversationList, ...list], + conversationListPageNo: nextPageNo, + conversationListHasMore: list.length >= res.pageSize, + conversationListLoading: false, + })); + } catch (e) { + console.error('[AiChatStore] loadConversationList failed:', e); + set({ conversationListLoading: false }); + } + }, + + loadConversationDetail: async (conversationId: string) => { + try { + const detail = await aiConversationService.getAiConversation({ conversationId }); + get().syncLocalSessionFromDetail(conversationId, detail.conversation, detail.messages); + } catch (e) { + console.error('[AiChatStore] loadConversationDetail failed:', e); + } + }, + + syncLocalSessionFromDetail: ( + conversationId: string, + conversation: IAiConversation, + messages: IAiMessage[], + ) => { + set((state) => { + const localMessages: IChatMessage[] = messages.map((m) => ({ + id: m.messageId, + role: m.role, + content: m.content, + thinking: m.thinking || undefined, + sequenceNo: m.sequenceNo, + sqlExtracted: undefined, + })); + const existing = state.sessions[conversationId]; + const now = Date.now(); + const session: AiChatSession = { + sessionId: conversationId, + state: 'COMPLETED', + messages: localMessages, + currentContent: '', + currentThinking: '', + title: conversation.title, + dataSourceId: conversation.dataSourceId, + databaseName: conversation.databaseName, + schemaName: conversation.schemaName, + createdAt: existing?.createdAt ?? now, + updatedAt: now, + }; + return { + sessions: { ...state.sessions, [conversationId]: session }, + currentSessionId: state.currentSessionId ?? conversationId, + }; + }); + }, + }), + { + name: 'ai-chat-store', + storage: persistStorage, + partialize: (state) => ({ + sessions: state.sessions, + currentSessionId: state.currentSessionId, + conversationList: state.conversationList, + conversationListPageNo: state.conversationListPageNo, + conversationListHasMore: state.conversationListHasMore, + }), + }, + ), +); + +export const generateTitle = generateTitleFromMessage; +export { uuidv4 }; diff --git a/chat2db-client/src/pages/main/workspace/store/common.ts b/chat2db-client/src/pages/main/workspace/store/common.ts index c3da941d6..ffe8c44aa 100644 --- a/chat2db-client/src/pages/main/workspace/store/common.ts +++ b/chat2db-client/src/pages/main/workspace/store/common.ts @@ -1,29 +1,106 @@ import { IConnectionListItem } from '@/typings/connection'; import { useWorkspaceStore } from './index'; +export type IAiChatPromptType = + | 'NL_2_SQL' + | 'SQL_EXPLAIN' + | 'SQL_OPTIMIZER' + | 'SQL_2_SQL' + | 'NL_2_COMMENT' + | 'NL_2_COMMENT_BATCH' + | 'NL_2_FIELD_MAPPING' + | 'NL_2_DATA_EXPRESSION' + | 'SQL_FIX' + | 'SQL_COMPLETION'; + +export interface IColumnComment { + column_name: string; + comment: string; +} + +export interface ITableCommentResult { + table_comment: string; + column_comments: IColumnComment[]; +} + +export interface IBatchTableComment { + table_name: string; + table_comment: string; +} + +export interface IBatchTableCommentResult { + tables: IBatchTableComment[]; +} + +export interface IFieldMappingResult { + mappings: { + sourceField: string; + targetField: string; + confidence: number; + }[]; +} + +export interface IDataExpressionResult { + column_expressions: { + column_name: string; + expression: string; + reason: string; + }[]; +} + +export interface ISqlFixResult { + error_analysis: string; + fixed_sql: string; + explanation: string; + can_fix: boolean; +} + +export interface IPendingAiChat { + dataSourceId: number; + databaseName?: string; + schemaName?: string | null; + tableNames?: string[] | null; + message: string; + promptType: IAiChatPromptType; + onCommentGenerated?: (result: ITableCommentResult) => void; + onBatchCommentGenerated?: (result: IBatchTableCommentResult) => void; + onMappingGenerated?: (result: IFieldMappingResult) => void; + onExpressionGenerated?: (result: IDataExpressionResult) => void; + onSqlFixed?: (sql: string) => void; + ext?: string; +} + export interface ICommonStore { currentConnectionDetails: IConnectionListItem | null; currentWorkspaceExtend: string | null; currentWorkspaceGlobalExtend: { - code: string, - uniqueData: any, + code: string; + uniqueData: any; } | null; + pendingAiChat: IPendingAiChat | null; } export const initCommonStore: ICommonStore = { currentConnectionDetails: null, currentWorkspaceExtend: null, currentWorkspaceGlobalExtend: null, -} + pendingAiChat: null, +}; export const setCurrentConnectionDetails = (connectionDetails: ICommonStore['currentConnectionDetails']) => { return useWorkspaceStore.setState({ currentConnectionDetails: connectionDetails }); -} +}; export const setCurrentWorkspaceExtend = (workspaceExtend: ICommonStore['currentWorkspaceExtend']) => { return useWorkspaceStore.setState({ currentWorkspaceExtend: workspaceExtend }); -} +}; -export const setCurrentWorkspaceGlobalExtend = (workspaceGlobalExtend: ICommonStore['currentWorkspaceGlobalExtend']) => { +export const setCurrentWorkspaceGlobalExtend = ( + workspaceGlobalExtend: ICommonStore['currentWorkspaceGlobalExtend'], +) => { return useWorkspaceStore.setState({ currentWorkspaceGlobalExtend: workspaceGlobalExtend }); -} +}; + +export const setPendingAiChat = (pendingAiChat: ICommonStore['pendingAiChat']) => { + return useWorkspaceStore.setState({ pendingAiChat }); +}; diff --git a/chat2db-client/src/pages/main/workspace/store/config.ts b/chat2db-client/src/pages/main/workspace/store/config.ts index 7a27b704a..9ab61bacf 100644 --- a/chat2db-client/src/pages/main/workspace/store/config.ts +++ b/chat2db-client/src/pages/main/workspace/store/config.ts @@ -1,4 +1,4 @@ -import {useWorkspaceStore} from './index' +import { useWorkspaceStore } from './index'; export interface IConfigStore { layout: { panelLeft: boolean; @@ -15,7 +15,7 @@ export const initConfigStore: IConfigStore = { panelLeftWidth: 220, panelRightWidth: 300, }, -} +}; export const togglePanelRight = () => { return useWorkspaceStore.setState((state) => ({ @@ -23,8 +23,8 @@ export const togglePanelRight = () => { ...state.layout, panelRight: !state.layout.panelRight, }, - })) -} + })); +}; export const togglePanelLeft = () => { return useWorkspaceStore.setState((state) => ({ @@ -32,23 +32,32 @@ export const togglePanelLeft = () => { ...state.layout, panelLeft: !state.layout.panelLeft, }, - })) -} + })); +}; + +export const setPanelLeft = (panelLeft: boolean) => { + return useWorkspaceStore.setState((state) => ({ + layout: { + ...state.layout, + panelLeft, + }, + })); +}; -export const setPanelLeftWidth = (width: number) => { +export const setPanelLeftWidth = (width: number) => { return useWorkspaceStore.setState((state) => ({ layout: { ...state.layout, panelLeftWidth: width, }, - })) -} + })); +}; -export const setPanelRightWidth = (width: number) => { +export const setPanelRightWidth = (width: number) => { return useWorkspaceStore.setState((state) => ({ layout: { ...state.layout, panelRightWidth: width, }, - })) -} + })); +}; diff --git a/chat2db-client/src/pages/main/workspace/store/console.ts b/chat2db-client/src/pages/main/workspace/store/console.ts index f82e3b90b..a01c74405 100644 --- a/chat2db-client/src/pages/main/workspace/store/console.ts +++ b/chat2db-client/src/pages/main/workspace/store/console.ts @@ -9,6 +9,7 @@ import i18n from '@/i18n'; export interface IConsoleStore { consoleList: IConsole[] | null; savedConsoleList: IConsole[] | null; + savedConsoleTotal: number; activeConsoleId: string | number | null; workspaceTabList: IWorkspaceTab[] | null; createConsoleLoading: boolean @@ -17,6 +18,7 @@ export interface IConsoleStore { export const initConsoleStore = { consoleList: null, savedConsoleList: null, + savedConsoleTotal: 0, activeConsoleId: null, workspaceTabList: null, createConsoleLoading: false, @@ -34,15 +36,19 @@ export const getOpenConsoleList = () => { }); }; -export const getSavedConsoleList = () => { +export const getSavedConsoleList = (pageNo: number = 1, pageSize: number = 100) => { historyService .getConsoleList({ - pageNo: 1, - pageSize: 100, + pageNo, + pageSize, status: ConsoleStatus.RELEASE, + orderByCreateDesc: true, }) .then((res) => { - useWorkspaceStore.setState({ savedConsoleList: res?.data }); + useWorkspaceStore.setState({ + savedConsoleList: res?.data, + savedConsoleTotal: res?.total || 0, + }); }); } @@ -56,6 +62,7 @@ export const setWorkspaceTabList = (items: IConsoleStore['workspaceTabList']) => export const createConsole = (params: ICreateConsoleParams) => { const workspaceTabList = useWorkspaceStore.getState().workspaceTabList; + const consoleList = useWorkspaceStore.getState().consoleList; const currentConnectionDetails = useWorkspaceStore.getState().currentConnectionDetails; const newConsole = { ...params, @@ -67,7 +74,6 @@ export const createConsole = (params: ICreateConsoleParams) => { supportDatabase: currentConnectionDetails?.supportDatabase, supportSchema: currentConnectionDetails?.supportSchema, }; - return new Promise((resolve) => { if ((workspaceTabList?.length || 0) >= 20) { message.warning(i18n('workspace.tips.maxConsole')); @@ -75,6 +81,23 @@ export const createConsole = (params: ICreateConsoleParams) => { } useWorkspaceStore.setState({ createConsoleLoading: true }); historyService.createConsole(newConsole).then((res) => { + const newConsoleItem: IConsole = { + id: res, + name: newConsole.name, + ddl: newConsole.ddl, + dataSourceId: newConsole.dataSourceId, + dataSourceName: newConsole.dataSourceName, + type: newConsole.type, + databaseName: newConsole.databaseName, + schemaName: newConsole.schemaName, + status: newConsole.status, + connectable: true, + operationType: newConsole.operationType, + }; + + const newConsoleList = [...(consoleList || []), newConsoleItem]; + useWorkspaceStore.setState({ consoleList: newConsoleList }); + const newList = [ ...(workspaceTabList || []), { diff --git a/chat2db-client/src/pages/main/workspace/store/modal.ts b/chat2db-client/src/pages/main/workspace/store/modal.ts index c60b72a44..232c363d6 100644 --- a/chat2db-client/src/pages/main/workspace/store/modal.ts +++ b/chat2db-client/src/pages/main/workspace/store/modal.ts @@ -1,6 +1,13 @@ import { useWorkspaceStore } from './index'; import { DatabaseTypeCode } from '@/constants'; import { CreateType } from '@/components/CreateDatabase'; +import { IImportDataModalParams } from '@/components/ImportDataModal'; +import { IExportDataModalParams } from '@/components/ExportDataModal'; +import { IExportSchemaDocModalParams } from '@/components/ExportSchemaDocModal'; +import { IDataGenerationModalParams } from '@/components/DataGenerationModal'; +import { IExecuteSqlStatementModalParams } from '@/components/ExecuteSqlStatementModal'; +import { IDataTransferModalParams } from '@/components/DataTransferModal'; + export interface IModalStore { openCreateDatabaseModal: ((params: { type: CreateType; @@ -11,12 +18,48 @@ export interface IModalStore { }; executedCallback?: (status: true) => void; }) => void) | null; + openImportDataModal: ((params: IImportDataModalParams) => void) | null; + openExportDataModal: ((params: IExportDataModalParams) => void) | null; + openExportSchemaDocModal: ((params: IExportSchemaDocModalParams) => void) | null; + openDataGenerationModal: ((params: IDataGenerationModalParams) => void) | null; + openExecuteSqlStatementModal: ((params: IExecuteSqlStatementModalParams) => void) | null; + openDataTransferModal: ((params: IDataTransferModalParams) => void) | null; } export const initModalStore: IModalStore = { openCreateDatabaseModal: null, + openImportDataModal: null, + openExportDataModal: null, + openExportSchemaDocModal: null, + openDataGenerationModal: null, + openExecuteSqlStatementModal: null, + openDataTransferModal: null, }; export const setOpenCreateDatabaseModal = (fn: any) => { useWorkspaceStore.setState({ openCreateDatabaseModal: fn }); }; + +export const setOpenImportDataModal = (fn: any) => { + useWorkspaceStore.setState({ openImportDataModal: fn }); +}; + +export const setOpenExportDataModal = (fn: any) => { + useWorkspaceStore.setState({ openExportDataModal: fn }); +}; + +export const setOpenExportSchemaDocModal = (fn: any) => { + useWorkspaceStore.setState({ openExportSchemaDocModal: fn }); +}; + +export const setOpenDataGenerationModal = (fn: any) => { + useWorkspaceStore.setState({ openDataGenerationModal: fn }); +}; + +export const setOpenExecuteSqlStatementModal = (fn: any) => { + useWorkspaceStore.setState({ openExecuteSqlStatementModal: fn }); +}; + +export const setOpenDataTransferModal = (fn: any) => { + useWorkspaceStore.setState({ openDataTransferModal: fn }); +}; diff --git a/chat2db-client/src/service/aiConversation.ts b/chat2db-client/src/service/aiConversation.ts new file mode 100644 index 000000000..cdf9d6678 --- /dev/null +++ b/chat2db-client/src/service/aiConversation.ts @@ -0,0 +1,42 @@ +import createRequest from './base'; +import { + IAiConversation, + IAiConversationCreateRequest, + IAiConversationDetail, + IAiConversationQueryRequest, + IAiConversationRenameRequest, + IPageResponse, +} from '@/typings/aiConversation'; + +export const createAiConversation = createRequest( + '/api/ai/conversation/create', + { method: 'post' }, +); + +export const queryAiConversations = createRequest>( + '/api/ai/conversation/page', + { method: 'get' }, +); + +export const getAiConversation = createRequest<{ conversationId: string }, IAiConversationDetail>( + '/api/ai/conversation/:conversationId', + { method: 'get' }, +); + +export const renameAiConversation = createRequest< + IAiConversationRenameRequest & { conversationId: string }, + void +>('/api/ai/conversation/:conversationId/rename', { method: 'post' }); + +export const deleteAiConversation = createRequest<{ conversationId: string }, void>( + '/api/ai/conversation/:conversationId', + { method: 'delete' }, +); + +export default { + createAiConversation, + queryAiConversations, + getAiConversation, + renameAiConversation, + deleteAiConversation, +}; diff --git a/chat2db-client/src/service/aiSqlCompletion.ts b/chat2db-client/src/service/aiSqlCompletion.ts new file mode 100644 index 000000000..264cf346b --- /dev/null +++ b/chat2db-client/src/service/aiSqlCompletion.ts @@ -0,0 +1,100 @@ +import { v4 as uuidv4 } from 'uuid'; +import { cancelSSESession, createSSEConnection } from '@/utils/sse'; +import { formatParams } from '@/utils/url'; +import { IBoundInfo } from '@/typings'; + +export interface IAiSqlCompletionParams { + boundInfo: IBoundInfo; + message: string; + ext?: string; +} + +export interface IAiSqlCompletionTask { + promise: Promise; + cancel: () => void; +} + +const extractSqlFromContent = (content: string): string => { + const trimmedContent = content.trim(); + const sqlBlockMatch = trimmedContent.match(/```sql\s*([\s\S]*?)```/i); + if (sqlBlockMatch?.[1]) { + return sqlBlockMatch[1].trim(); + } + + const codeBlockMatch = trimmedContent.match(/```\s*([\s\S]*?)```/); + if (codeBlockMatch?.[1]) { + return codeBlockMatch[1].trim(); + } + + return trimmedContent + .replace(/^SQL\s*[::]\s*/i, '') + .replace(/^完整\s*SQL\s*[::]\s*/i, '') + .trim(); +}; + +export const requestAiSqlCompletion = (params: IAiSqlCompletionParams): IAiSqlCompletionTask => { + const sessionId = uuidv4(); + const query = formatParams({ + message: params.message, + promptType: 'SQL_COMPLETION', + dataSourceId: params.boundInfo.dataSourceId, + databaseName: params.boundInfo.databaseName, + schemaName: params.boundInfo.schemaName, + tableNames: (params.boundInfo as any).tableNames, + ext: params.ext, + }); + const eventSource = createSSEConnection({ url: `/api/ai/chat?${query}`, uid: sessionId }); + + let settled = false; + let content = ''; + let rejectPromise: ((reason?: any) => void) | null = null; + + const cleanup = () => { + eventSource.close(); + }; + + const promise = new Promise((resolve, reject) => { + rejectPromise = reject; + + eventSource.addEventListener('message', (event: any) => { + const data = event.data; + if (data === '[DONE]') { + settled = true; + cleanup(); + resolve(extractSqlFromContent(content)); + return; + } + + try { + const parsed = JSON.parse(data); + if (parsed.content) { + content += parsed.content; + } + } catch { + content += data; + } + }); + + eventSource.addEventListener('error', () => { + if (settled) { + return; + } + settled = true; + cleanup(); + reject(new Error('AI SQL completion failed')); + }); + }); + + return { + promise, + cancel: () => { + if (settled) { + return; + } + settled = true; + cleanup(); + rejectPromise?.(new Error('AI SQL completion cancelled')); + cancelSSESession(sessionId); + }, + }; +}; diff --git a/chat2db-client/src/service/base.ts b/chat2db-client/src/service/base.ts index 4cdc02ba3..134cea1f9 100644 --- a/chat2db-client/src/service/base.ts +++ b/chat2db-client/src/service/base.ts @@ -190,17 +190,24 @@ export default function createRequest

(url: string, options?: .then((res) => { if (!res) return; const { success, errorCode, errorMessage, errorDetail, solutionLink, data } = res; - if (!success && errorLevel === 'toast' && !noNeedToastErrorCode.includes(errorCode)) { + if (!success) { + if (errorLevel === 'toast' && !noNeedToastErrorCode.includes(errorCode)) { + delayTimeFn(() => { + if (typeof window._notificationApi === 'function') { + window._notificationApi({ + requestUrl: eventualUrl, + requestParams: JSON.stringify(params), + errorCode, + errorMessage, + errorDetail, + solutionLink, + }); + } else { + message.error(`${errorCode}: ${errorMessage}`); + } + }, delayTime); + } delayTimeFn(() => { - window._notificationApi({ - requestUrl: eventualUrl, - requestParams: JSON.stringify(params), - errorCode, - errorMessage, - errorDetail, - solutionLink, - }); - // message.error(`${errorCode}: ${errorMessage}`); reject(`${errorCode}: ${errorMessage}`); }, delayTime); return; diff --git a/chat2db-client/src/service/config.ts b/chat2db-client/src/service/config.ts index d78c99d29..45f403370 100644 --- a/chat2db-client/src/service/config.ts +++ b/chat2db-client/src/service/config.ts @@ -1,4 +1,4 @@ -import { IAiConfig } from '@/typings'; +import { IDefaultModelConfig, IModelServiceConfig } from '@/typings/setting'; import createRequest from './base'; export interface ILatestVersion { @@ -45,13 +45,32 @@ const setSystemConfig = createRequest<{ code: string; content: string }, void>(' method: 'post', }); -const getAiSystemConfig = createRequest<{ aiSqlSource?: string }, IAiConfig>('/api/config/system_config/ai', { - errorLevel: false, +const getModelServiceList = createRequest('/api/config/model_service/list', { + method: 'get', }); -const setAiSystemConfig = createRequest('/api/config/system_config/ai', { +const upsertModelService = createRequest('/api/config/model_service/upsert', { + method: 'post', errorLevel: 'toast', +}); + +const deleteModelService = createRequest<{ id: string }, void>('/api/config/model_service/delete', { + method: 'post', + errorLevel: 'toast', +}); + +const testModelService = createRequest('/api/config/model_service/test', { method: 'post', + errorLevel: false, +}); + +const getDefaultModelConfig = createRequest('/api/config/model/default', { + method: 'get', +}); + +const setDefaultModelConfig = createRequest('/api/config/model/default', { + method: 'post', + errorLevel: 'toast', }); const getAiWhiteAccess = createRequest<{ apiKey: string }, boolean>('/api/ai/embedding/white/check', { @@ -81,11 +100,15 @@ const setAppUpdateType = createRequest('/api/sy export default { getSystemConfig, setSystemConfig, - getAiSystemConfig, - setAiSystemConfig, + getModelServiceList, + upsertModelService, + deleteModelService, + testModelService, + getDefaultModelConfig, + setDefaultModelConfig, getAiWhiteAccess, getLatestVersion, isUpdateSuccess, updateDesktopVersion, - setAppUpdateType + setAppUpdateType, }; diff --git a/chat2db-client/src/service/connection.ts b/chat2db-client/src/service/connection.ts index ee681cc3f..4c5c69c27 100644 --- a/chat2db-client/src/service/connection.ts +++ b/chat2db-client/src/service/connection.ts @@ -55,6 +55,8 @@ const remove = createRequest<{ id: number }, void>('/api/connection/datasource/: const clone = createRequest<{ id: number }, number>('/api/connection/datasource/clone', { method: 'post' }); +const updateSort = createRequest<{ idList: number[] }, void>('/api/connection/datasource/sort', { method: 'post' }); + const getDatabaseList = createRequest<{ dataSourceId: number; refresh?: boolean }, any>('/api/rdb/database/list', { method: 'get', }); @@ -95,6 +97,7 @@ export default { update, remove, clone, + updateSort, getDatabaseList, getSchemaList, close, diff --git a/chat2db-client/src/service/redis.ts b/chat2db-client/src/service/redis.ts new file mode 100644 index 000000000..238a13ce3 --- /dev/null +++ b/chat2db-client/src/service/redis.ts @@ -0,0 +1,201 @@ +import createRequest from './base'; +import { createSSEConnection } from '@/utils/sse'; +import { formatParams } from '@/utils/url'; + +export interface IRedisKeyListParams { + dataSourceId: number; + databaseName?: string; + searchKey?: string; + cursor?: string; + count?: number; +} + +export interface IRedisKeyItem { + name: string; + value?: any; + type?: string; + ttl?: number; + size?: number; +} + +export interface IRedisKeyQueryParams { + dataSourceId: number; + databaseName?: string; + keyName: string; +} + +export interface IRedisKeyUpdateParams { + dataSourceId: number; + databaseName?: string; + originalKey: string; + updateKey: string; + keyType: string; + value: any; + updateTtl?: number; +} + +export interface IRedisKeyPartialUpdateParams { + dataSourceId: number; + databaseName?: string; + keyName: string; + keyType: string; + addedFields?: Record; + removedFields?: string[]; + updateTtl?: number; +} + +export interface IRedisKeyCreateParams { + dataSourceId: number; + databaseName?: string; + name: string; + keyType: string; + value: any; + ttl?: number; +} + +export interface IRedisKeyDeleteParams { + dataSourceId: number; + databaseName?: string; + keyName: string; +} + +export interface IRedisMonitorStreamOptions { + dataSourceId: number; + databaseName?: string; + uid: string; + onCommand: (line: string) => void; + onDone: () => void; + onError: (message: string) => void; +} + +export interface IRedisKeyStreamOptions extends IRedisKeyListParams { + uid: string; + onBatch: (items: IRedisKeyItem[], total: number, cursor: string, hasMore: boolean) => void; + onDone: (total: number, cursor: string, hasMore: boolean) => void; + onError: (message: string) => void; +} + +const getKeyList = createRequest('/api/redis/key/list', { + method: 'get', + delayTime: 200, +}); + +const queryKey = createRequest('/api/redis/key/query', { + method: 'get', +}); + +const updateKey = createRequest('/api/redis/key/update', { + method: 'post', +}); + +const partialUpdateKey = createRequest('/api/redis/key/partial-update', { + method: 'post', +}); + +const createKey = createRequest('/api/redis/key/create', { + method: 'post', +}); + +const deleteKey = createRequest('/api/redis/key/delete', { + method: 'post', +}); + +const streamMonitor = (options: IRedisMonitorStreamOptions) => { + const { uid, onCommand, onDone, onError, ...params } = options; + const url = `/api/redis/monitor/stream?${formatParams(params)}`; + const eventSource = createSSEConnection({ url, uid }); + + eventSource.addEventListener('command', (event: any) => { + try { + const data = event.data ? JSON.parse(event.data) : {}; + onCommand(data.line || ''); + } catch { + onError('Redis monitor 数据解析失败'); + eventSource.close(); + } + }); + + eventSource.addEventListener('done', () => { + onDone(); + eventSource.close(); + }); + + eventSource.addEventListener('redis_error', (event: any) => { + try { + const data = event.data ? JSON.parse(event.data) : {}; + onError(data.message || 'Redis monitor 连接失败'); + } catch { + onError('Redis monitor 连接失败'); + } finally { + eventSource.close(); + } + }); + + eventSource.addEventListener('error', () => { + onError('Redis monitor 连接已断开'); + eventSource.close(); + }); + + return () => { + eventSource.close(); + }; +}; + +const streamKeyList = (options: IRedisKeyStreamOptions) => { + const { uid, onBatch, onDone, onError, ...params } = options; + const url = `/api/redis/key/stream?${formatParams(params)}`; + const eventSource = createSSEConnection({ url, uid }); + + eventSource.addEventListener('keys', (event: any) => { + try { + const data = JSON.parse(event.data); + const items = Array.isArray(data) ? data : data.items || []; + onBatch(items, 0, '0', false); + } catch { + onError('Redis key 数据解析失败'); + eventSource.close(); + } + }); + + eventSource.addEventListener('done', (event: any) => { + try { + const data = JSON.parse(event.data); + onDone(data.total || 0, data.cursor || '0', Boolean(data.hasMore)); + } catch { + onDone(0, '0', false); + } finally { + eventSource.close(); + } + }); + + eventSource.addEventListener('redis_error', (event: any) => { + try { + const data = event.data ? JSON.parse(event.data) : {}; + onError(data.message || 'Redis key 加载失败'); + } catch { + onError('Redis key 加载失败'); + } finally { + eventSource.close(); + } + }); + + eventSource.addEventListener('error', () => { + onError('Redis key 加载连接失败'); + eventSource.close(); + }); + + return () => { + eventSource.close(); + }; +}; + +export default { + createKey, + deleteKey, + getKeyList, + partialUpdateKey, + queryKey, + streamKeyList, + streamMonitor, + updateKey, +}; diff --git a/chat2db-client/src/service/sql.ts b/chat2db-client/src/service/sql.ts index faf3afffe..be58e8f5a 100644 --- a/chat2db-client/src/service/sql.ts +++ b/chat2db-client/src/service/sql.ts @@ -1,16 +1,41 @@ -import createRequest from './base'; +import { DatabaseTypeCode } from '@/constants'; import { - IPageResponse, - IPageParams, - IUniversalTableParams, - IManageResultData, - IRoutines, IDatabaseSupportField, IEditTableInfo, + IManageResultData, + IPageParams, + IPageResponse, + IRoutines, ITable, + IUniversalTableParams, } from '@/typings'; -import { DatabaseTypeCode } from '@/constants'; +import { ISchemaCompareParams, ISchemaDiffResult, ISchemaMigrateParams, IMigrateResult } from '@/typings/schemaDiff'; import { ExportSizeEnum, ExportTypeEnum } from '@/typings/resultTable'; +import createRequest from './base'; + +export interface ITreeSearchParams extends IPageParams { + dataSourceId: number; + databaseName?: string; + schemaName?: string; + searchKey?: string; + treeNodeType?: string; + refresh?: boolean; +} + +export interface ITreeNodeResponse { + uuid: string; + key: string; + name: string; + treeNodeType: string; + pretendNodeType?: string; + comment?: string; + isLeaf?: boolean; + pinned?: boolean; + parentPath?: string[]; + extraParams?: Record; +} + +const searchTree = createRequest('/api/rdb/tree/search', { method: 'get' }); export interface IGetTableListParams extends IPageParams { dataSourceId: number; @@ -21,6 +46,7 @@ export interface IGetTableListParams extends IPageParams { export interface IExecuteSqlParams { sql?: string; + scriptStartLine?: number; consoleId?: number; dataSourceId?: number; databaseName?: string; @@ -44,11 +70,76 @@ export interface IConnectConsoleParams { databaseName: string; } -const getTableList = createRequest>('/api/rdb/table/list', { method: 'get' }); +/** ER图节点,代表一张数据库表 */ +export interface IErNode { + /** 节点唯一标识 */ + id: string; + /** 表名 */ + name: string; + /** 表注释 */ + comment?: string; + /** 表的列数量 */ + columnCount?: number; +} + +/** ER图边,代表表之间的外键关系 */ +export interface IErEdge { + /** 边唯一标识 */ + id: string; + /** 源表名(拥有外键的表) */ + source: string; + /** 目标表名(被引用的表) */ + target: string; + /** 源表的外键列名 */ + sourceColumn: string; + /** 目标表被引用的列名 */ + targetColumn: string; + /** 关系描述 */ + label: string; + /** 是否为虚拟外键 */ + virtual: boolean; +} -const executeSql = createRequest('/api/rdb/dml/execute', { method: 'post', delayTime: 10 }); +/** ER图数据,包含节点和边 */ +export interface IErDiagram { + nodes: IErNode[]; + edges: IErEdge[]; +} -const viewTable = createRequest('/api/rdb/dml/execute_table', { method: 'post', delayTime: 10 }); +/** ER图查询参数 */ +export interface IErParams { + /** 数据源ID */ + dataSourceId: number; + /** 数据库名 */ + databaseName: string; + /** Schema名 */ + schemaName?: string; + /** 表名过滤条件 */ + tableNameFilter?: string; + /** 是否包含虚拟外键 */ + includeVirtualFk?: boolean; + /** 是否同步数据库真实外键到本地 */ + syncForeignKeys?: boolean; + /** 是否只显示关联表 */ + onlyRelatedTables?: boolean; +} + +/** 获取ER图数据接口 */ +const getErDiagram = createRequest('/api/rdb/er/diagram', { method: 'get' }); + +const getTableList = createRequest>('/api/rdb/table/list', { + method: 'get', +}); + +const executeSql = createRequest('/api/rdb/dml/execute', { + method: 'post', + delayTime: 10, +}); + +const viewTable = createRequest('/api/rdb/dml/execute_table', { + method: 'post', + delayTime: 10, +}); const connectConsole = createRequest('/api/connection/console/connect', { method: 'get' }); @@ -81,6 +172,17 @@ export interface IColumn { comment: string; } +/** 外键定义接口 */ +export interface IForeignKey { + name: string; // 外键名称 + referencedTable: string; // 引用的表名 + referencedColumn: string; // 引用的列名 + updateRule: number; // 更新规则 + deleteRule: number; // 删除规则 + comment?: string; // 备注(可选) + isNullable: boolean; // 是否允许为空 +} + export interface ISchemaParams { dataSourceId: number; databaseName: string; @@ -103,32 +205,53 @@ export interface Schema { name: string; } -const deleteTable = createRequest('/api/rdb/ddl/delete', { method: 'post' }); -const createTableExample = createRequest<{ dbType: DatabaseTypeCode }, string>('/api/rdb/ddl/create/example', { +export interface IFunctionCall { + name?: string; + arguments?: string; +} + +export interface IToolCall { + id?: string; + type?: string; + function?: IFunctionCall; +} + +export interface IMessage { + role?: string; + tool_calls?: IToolCall[]; +} + +const deleteTable = createRequest('/api/rdb/table/delete', { method: 'post' }); +const deleteDatabase = createRequest<{ dataSourceId: number; databaseName: string }, void>( + '/api/rdb/database/delete_database', + { method: 'post' }, +); +const truncateTable = createRequest('/api/rdb/table/truncate', { method: 'post' }); +const createTableExample = createRequest<{ dbType: DatabaseTypeCode }, string>('/api/rdb/table/create/example', { method: 'get', }); -const updateTableExample = createRequest<{ dbType: DatabaseTypeCode }, string>('/api/rdb/ddl/update/example', { +const updateTableExample = createRequest<{ dbType: DatabaseTypeCode }, string>('/api/rdb/table/update/example', { method: 'get', }); -const exportCreateTableSql = createRequest('/api/rdb/ddl/export', { method: 'get' }); -const executeTable = createRequest('/api/rdb/ddl/execute', { method: 'post' }); +const exportCreateTableSql = createRequest('/api/rdb/table/export', { method: 'get' }); +const executeTable = createRequest('/api/rdb/dml/execute', { method: 'post' }); -const getColumnList = createRequest('/api/rdb/ddl/column_list', { +const getColumnList = createRequest('/api/rdb/table/column_list', { method: 'get', delayTime: 200, }); -const getIndexList = createRequest('/api/rdb/ddl/index_list', { +const getIndexList = createRequest('/api/rdb/table/index_list', { method: 'get', delayTime: 200, }); -const getKeyList = createRequest('/api/rdb/ddl/key_list', { method: 'get', delayTime: 200 }); -const getSchemaList = createRequest('/api/rdb/ddl/schema_list', { +const getKeyList = createRequest('/api/rdb/fk/list', { method: 'get', delayTime: 200 }); +const getSchemaList = createRequest('/api/rdb/schema/list', { method: 'get', delayTime: 200, }); const getDatabaseSchemaList = createRequest<{ dataSourceId: number }, MetaSchemaVO>( - '/api/rdb/ddl/database_schema_list', + '/api/rdb/database/database_schema_list', { method: 'get' }, ); @@ -136,6 +259,16 @@ const addTablePin = createRequest('/api/pin/table/a const deleteTablePin = createRequest('/api/pin/table/delete', { method: 'post' }); +const deprecatedTable = createRequest('/api/rdb/table/deprecated', { method: 'post' }); + +const restoreDeprecatedTable = createRequest('/api/rdb/table/cancel_deprecated', { + method: 'post', +}); + +const getDeprecatedTableList = createRequest('/api/rdb/table/deprecated_list', { + method: 'get', +}); + /** 获取当前执行SQL 所有行 */ const getDMLCount = createRequest('/api/rdb/dml/count', { method: 'post' }); @@ -260,6 +393,19 @@ const getAllFieldByTable = createRequest< Array<{ name: string; tableName: string }> >('/api/rdb/table/column_list', { method: 'get' }); +const getAiGuess = createRequest< + { + dataSourceId: number; + databaseName: string; + schemaName?: string | null | undefined; + tableNames: string[]; + promptType: string; + }, + IMessage +>('/api/ai/er/guess', { + method: 'get', +}); + export interface IModifyTableSqlParams { dataSourceId: number; databaseName: string; @@ -269,12 +415,27 @@ export interface IModifyTableSqlParams { newTable: IEditTableInfo; refresh: boolean; } +export interface IBatchModifyTableSqlParams { + dataSourceId: string; + databaseName: string; + schemaName?: string | null; + tableName?: string; + oldTables?: any[]; + newTables: any[]; + refresh: boolean; +} /** 获取修改表的sql */ const getModifyTableSql = createRequest('/api/rdb/table/modify/sql', { method: 'post', }); - +/** 定义批量获取修改表的SQL语句的API接口 */ +const getBatchModifyTableSql = createRequest( + '/api/rdb/table/batch/modify/sql', + { + method: 'post', + }, +); /** 执行编辑表的sql, 专为编辑表而生 */ const executeDDL = createRequest( '/api/rdb/dml/execute_ddl', @@ -290,26 +451,187 @@ const executeUpdateDataSql = createRequest('/api/rdb/dml/get_update_sql', { method: 'post' }); -/** 创建数据库 */ -const getCreateDatabaseSql = createRequest<{ +/** 创建数据库 */ +const getCreateDatabaseSql = createRequest< + { + dataSourceId: number; + databaseName: string; + }, + { sql: string } +>('/api/rdb/database/create_database_sql', { method: 'post' }); + +/** 创建schema */ +const getCreateSchemaSql = createRequest< + { + dataSourceId: number; + databaseName?: string; + schemaName?: string; + }, + { sql: string } +>('/api/rdb/schema/create_schema_sql', { method: 'post' }); + +const deleteVirtualForeignKey = createRequest< + { + dataSourceId: number; + databaseName: string; + schemaName?: string; + tableName: string; + keyName: string; + }, + void +>('/api/rdb/fk/delete_by_name', { method: 'post' }); + +/** 外键列表查询参数 */ +export interface IForeignKeyListParams { dataSourceId: number; - databaseName: string; -}, { sql: string }>('/api/rdb/database/create_database_sql', { method: 'post' }); + databaseName?: string; + schemaName?: string; + tableName?: string; +} -/** 创建schema */ -const getCreateSchemaSql = createRequest<{ +/** 外键列表响应 */ +export interface IForeignKeyVO { + id?: number; + name: string; + tableName: string; + columnName: string; + referencedTable: string; + referencedColumnName: string; + comment?: string; + updateRule: number; + deleteRule: number; + sourceType: 'REAL' | 'VIRTUAL'; + editable: boolean; + virtualProperty?: string; +} + +/** 外键同步参数 */ +export interface IForeignKeySyncParams { dataSourceId: number; databaseName?: string; schemaName?: string; -}, {sql:string}>('/api/rdb/schema/create_schema_sql', { method: 'post' }); + tableName?: string; +} + +/** 外键同步结果 */ +export interface ISyncResult { + added: number; + deleted: number; + unchanged: number; +} + +/** 创建虚拟外键参数 */ +export interface ICreateVirtualFKParams { + dataSourceId: number; + databaseName?: string; + schemaName?: string; + tableName: string; + columnName: string; + referencedTable: string; + referencedColumnName: string; + comment?: string; +} + +/** 更新虚拟外键参数 */ +export interface IUpdateVirtualFKParams { + id: number; + dataSourceId: number; + databaseName?: string; + schemaName?: string; + vkName?: string; + tableName?: string; + columnName?: string; + referencedTable?: string; + referencedColumnName?: string; + comment?: string; +} + +/** 删除外键参数 */ +export interface IDeleteFKParams { + id: number; + sourceType: 'REAL' | 'VIRTUAL'; +} + +/** 删除外键结果 */ +export interface IDeleteFKResult { + executedDDL: string | null; +} + +/** 虚拟外键推断参数 */ +export interface IInferVirtualFKParams { + dataSourceId: number; + databaseName: string; + schemaName?: string; + tableNameFilter?: string; +} + +/** 虚拟外键推断结果项 */ +export interface IInferVirtualFkItem { + tableName: string; + columnName: string; + referencedTable: string; + referencedColumnName: string; +} + +/** 虚拟外键推断结果 */ +export interface IInferVirtualFkResult { + addedCount: number; + deletedCount: number; + added: IInferVirtualFkItem[]; + deleted: IInferVirtualFkItem[]; +} + +const syncForeignKeys = createRequest('/api/rdb/fk/sync', { method: 'post' }); +const getForeignKeyList = createRequest('/api/rdb/fk/list', { method: 'get' }); +const createVirtualForeignKey = createRequest('/api/rdb/fk/virtual/create', { + method: 'post', +}); +const updateVirtualForeignKey = createRequest('/api/rdb/fk/virtual/update', { + method: 'post', +}); +const deleteForeignKey = createRequest('/api/rdb/fk/delete', { method: 'post' }); +const inferVirtualForeignKeys = createRequest( + '/api/rdb/er/infer-virtual-fk', + { method: 'post' }, +); + +const batchOptimizeTables = createRequest< + { + dataSourceId: number; + databaseName?: string; + schemaName?: string; + tableNames: string[]; + }, + any[] +>('/api/rdb/table/batch/optimize', { method: 'post' }); + +const batchAnalyzeTables = createRequest< + { + dataSourceId: number; + databaseName?: string; + schemaName?: string; + tableNames: string[]; + }, + any[] +>('/api/rdb/table/batch/analyze', { method: 'post' }); + +const compareSchema = createRequest('/api/rdb/schema/diff/compare', { + method: 'post', +}); + +const migrateSchema = createRequest('/api/rdb/schema/diff/migrate', { + method: 'post', +}); export default { + searchTree, getCreateSchemaSql, getCreateDatabaseSql, executeUpdateDataSql, executeDDL, getExecuteUpdateSql, getModifyTableSql, + getBatchModifyTableSql, getTableDetails, getDatabaseFieldTypeList, sqlFormat, @@ -323,10 +645,12 @@ export default { getFunctionList, getViewList, getTableList, + getErDiagram, executeSql, executeTable, connectConsole, deleteTable, + deleteDatabase, createTableExample, updateTableExample, exportCreateTableSql, @@ -334,12 +658,28 @@ export default { getColumnList, getIndexList, getKeyList, + getForeignKeyList, getSchemaList, getDatabaseSchemaList, addTablePin, deleteTablePin, + deprecatedTable, + restoreDeprecatedTable, + getDeprecatedTableList, getDMLCount, // exportResultTable getAllTableList, getAllFieldByTable, + getAiGuess, + deleteVirtualForeignKey, + truncateTable, + inferVirtualForeignKeys, + createVirtualForeignKey, + deleteForeignKey, + updateVirtualForeignKey, + syncForeignKeys, + batchOptimizeTables, + batchAnalyzeTables, + compareSchema, + migrateSchema, }; diff --git a/chat2db-client/src/service/task.ts b/chat2db-client/src/service/task.ts new file mode 100644 index 000000000..6c8b3c581 --- /dev/null +++ b/chat2db-client/src/service/task.ts @@ -0,0 +1,187 @@ +import createRequest from './base'; + +export interface ITask { + id: number; + gmtCreate: Date; + gmtModified: Date; + dataSourceId: number; + databaseName: string; + schemaName: string; + tableName: string; + deleted: string; + userId: number; + taskType: string; + taskStatus: string; + taskProgress: string; + taskName: string; + downloadUrl: string; + content?: string; +} + +export interface IExportResultDataParams { + sql: string; + originalSql: string; + exportType: string; + exportSize: string; + dataSourceId?: number; + databaseName?: string; + schemaName?: string; +} + +export interface IImportDataParams { + file: File; + tableName: string; + fileType: string; + dataSourceId?: number; + databaseName?: string; + schemaName?: string; + fieldMappings?: string; + importMode?: string; +} + +export interface IPreviewHeadersParams { + file: File; + tableName: string; + fileType: string; + dataSourceId?: number; + databaseName?: string; + schemaName?: string; +} + +export interface IExecuteSqlFileParams { + file: File; + dataSourceId?: number; + databaseName?: string; + schemaName?: string; +} + +export interface ITableColumnInfo { + name: string; + type: string; + primaryKey: boolean; +} + +export interface IAutoMapping { + sourceField: string; + targetField: string; + matched: boolean; +} + +export interface IPreviewHeadersResult { + fileHeaders: string[]; + tableColumns: ITableColumnInfo[]; + autoMappings: IAutoMapping[]; +} + +export interface IExportSchemaDocParams { + exportType: string; + exportSize: string; + dataSourceId?: number; + databaseName?: string; + schemaName?: string; + refresh?: boolean; +} + +export interface IDataTransferParams { + sourceDataSourceId: number; + sourceDatabaseName?: string; + sourceSchemaName?: string; + targetDataSourceId: number; + targetDatabaseName?: string; + targetSchemaName?: string; + tableNames: string[]; +} + +const exportResultData = createRequest('/api/export/export_data', { method: 'post' }); +const exportSchemaDoc = createRequest('/api/export/export_doc', { method: 'post' }); +const transferData = createRequest('/api/transfer/data', { method: 'post' }); +const getTask = createRequest<{ id: number }, ITask>('/api/task/get/:id', { method: 'get' }); +const getTaskList = createRequest, ITask[]>('/api/task/list', { method: 'get' }); +const cleanupTasks = createRequest, number>('/api/task/cleanup', { method: 'post' }); + +const previewFileHeaders = (params: IPreviewHeadersParams): Promise => { + const { file, ...restParams } = params; + const formData = new FormData(); + formData.append('file', file); + Object.keys(restParams).forEach((key) => { + const value = (restParams as any)[key]; + if (value !== undefined && value !== null) { + formData.append(key, String(value)); + } + }); + + return fetch('/api/import/preview_headers', { + method: 'POST', + credentials: 'include', + body: formData, + }) + .then((res) => res.json()) + .then((res) => { + if (res.success) { + return res.data; + } + throw new Error(res.errorMessage || 'Preview failed'); + }); +}; + +const importData = (params: IImportDataParams): Promise => { + const { file, ...restParams } = params; + const formData = new FormData(); + formData.append('file', file); + Object.keys(restParams).forEach((key) => { + const value = (restParams as any)[key]; + if (value !== undefined && value !== null) { + formData.append(key, String(value)); + } + }); + + return fetch('/api/import/import_data', { + method: 'POST', + credentials: 'include', + body: formData, + }) + .then((res) => res.json()) + .then((res) => { + if (res.success) { + return res.data; + } + throw new Error(res.errorMessage || 'Import failed'); + }); +}; + +const executeSqlFile = (params: IExecuteSqlFileParams): Promise => { + const { file, ...restParams } = params; + const formData = new FormData(); + formData.append('file', file); + Object.keys(restParams).forEach((key) => { + const value = (restParams as any)[key]; + if (value !== undefined && value !== null) { + formData.append(key, String(value)); + } + }); + + return fetch('/api/sql/execute_file', { + method: 'POST', + credentials: 'include', + body: formData, + }) + .then((res) => res.json()) + .then((res) => { + if (res.success) { + return res.data; + } + throw new Error(res.errorMessage || 'Execute SQL failed'); + }); +}; + +export default { + exportResultData, + exportSchemaDoc, + importData, + executeSqlFile, + getTask, + getTaskList, + cleanupTasks, + previewFileHeaders, + transferData, +}; diff --git a/chat2db-client/src/store/setting/index.ts b/chat2db-client/src/store/setting/index.ts index af6a36291..5e528625a 100644 --- a/chat2db-client/src/store/setting/index.ts +++ b/chat2db-client/src/store/setting/index.ts @@ -3,11 +3,8 @@ import { devtools, persist } from 'zustand/middleware'; import { shallow } from 'zustand/shallow'; import { StoreApi } from 'zustand'; -import { message } from 'antd'; -import i18n from '@/i18n'; - import { IAiConfig } from '@/typings/setting'; -import { IRemainingUse, AIType } from '@/typings/ai'; +import { AIType, IRemainingUse } from '@/typings/ai'; import configService from '@/service/config'; import aiService from '@/service/ai'; @@ -25,12 +22,12 @@ const initSetting = { }, hasWhite: false, holdingService: false, -} +}; export const useSettingStore: UseBoundStoreWithEqualityFn> = createWithEqualityFn( devtools( persist( - () => (initSetting), + () => initSetting, { name: 'global-setting', getStorage: () => localStorage, @@ -41,49 +38,28 @@ export const useSettingStore: UseBoundStoreWithEqualityFn { useSettingStore.setState({ aiConfig }); -} +}; export const setRemainUse = (remainingUse?: IRemainingUse) => { useSettingStore.setState({ remainingUse }); -} +}; export const setAiWithWhite = (hasWhite: boolean) => { useSettingStore.setState({ hasWhite }); -} +}; export const updateAiWithWhite = (apiKey: string) => { configService.getAiWhiteAccess({ apiKey: apiKey ?? '' }).then((res) => { setAiWithWhite(res); }); -} +}; -export const getAiSystemConfig = () => { - configService.getAiSystemConfig({}).then((res) => { - setAiConfig(res); - if (res?.aiSqlSource === AIType.CHAT2DBAI && res.apiKey) { - updateAiWithWhite(res.apiKey); - } - }); -} - -export const setAiSystemConfig = (aiConfig) => { - configService.setAiSystemConfig(aiConfig).then(() => { - message.success(i18n('common.text.submittedSuccessfully')); - setAiConfig(aiConfig); - }) - if (aiConfig?.aiSqlSource === AIType.CHAT2DBAI) { - updateAiWithWhite(aiConfig?.apiKey); - } else { - setAiWithWhite(false); - } -} - -export const fetchRemainingUse = (apiKey)=>{ +export const fetchRemainingUse = (apiKey) => { const currentState = useSettingStore.getState(); if (!apiKey || currentState.aiConfig.aiSqlSource !== AIType.CHAT2DBAI) { setRemainUse(undefined); @@ -91,13 +67,9 @@ export const fetchRemainingUse = (apiKey)=>{ } aiService.getRemainingUse().then((res) => { setRemainUse(res); - }) -} + }); +}; export const setHoldingService = (holdingService: boolean) => { useSettingStore.setState({ holdingService }); -} - - - - +}; diff --git a/chat2db-client/src/typings/ai.ts b/chat2db-client/src/typings/ai.ts index e7baf564b..35d4c03c8 100644 --- a/chat2db-client/src/typings/ai.ts +++ b/chat2db-client/src/typings/ai.ts @@ -1,12 +1,6 @@ export enum AIType { - CHAT2DBAI = 'CHAT2DBAI', - ZHIPUAI = 'ZHIPUAI', - BAICHUANAI='BAICHUANAI', - WENXINAI='WENXINAI', - // TONGYIQIANWENAI='TONGYIQIANWENAI', + ANTHROPIC = 'ANTHROPIC', OPENAI = 'OPENAI', - AZUREAI = 'AZUREAI', - RESTAI = 'RESTAI', } export interface IRemainingUse { diff --git a/chat2db-client/src/typings/aiConversation.ts b/chat2db-client/src/typings/aiConversation.ts new file mode 100644 index 000000000..fc7affa93 --- /dev/null +++ b/chat2db-client/src/typings/aiConversation.ts @@ -0,0 +1,54 @@ +export interface IAiConversation { + conversationId: string; + title?: string | null; + dataSourceId?: number | null; + dataSourceName?: string | null; + databaseName?: string | null; + schemaName?: string | null; + messageCount: number; + lastMessagePreview?: string | null; + status: 'ACTIVE' | 'ARCHIVED' | 'DELETED'; + gmtCreate?: string; + gmtModified?: string; +} + +export interface IAiMessage { + messageId: string; + role: 'user' | 'assistant'; + content: string; + thinking?: string | null; + sequenceNo: number; + gmtCreate?: string; +} + +export interface IAiConversationDetail { + conversation: IAiConversation; + messages: IAiMessage[]; +} + +export interface IAiConversationCreateRequest { + conversationId?: string | null; + dataSourceId?: number | null; + databaseName?: string | null; + schemaName?: string | null; + title?: string | null; + initialMessage?: string | null; +} + +export interface IAiConversationQueryRequest { + pageNo: number; + pageSize: number; + searchKey?: string; +} + +export interface IAiConversationRenameRequest { + title: string; +} + +export interface IPageResponse { + data: T[]; + pageNo: number; + pageSize: number; + total: number; + hasNextPage?: boolean; +} diff --git a/chat2db-client/src/typings/connection.ts b/chat2db-client/src/typings/connection.ts index 0e80b9e6b..d5b719f20 100644 --- a/chat2db-client/src/typings/connection.ts +++ b/chat2db-client/src/typings/connection.ts @@ -24,7 +24,6 @@ export interface IConnectionListItem { supportSchema: boolean; } - export interface IConnectionDetails { id: number; alias: string; @@ -46,14 +45,5 @@ export interface IConnectionDetails { [key: string]: any; } -export interface IConnectionListItem { - id: number; - alias: string; - environment: IConnectionEnv; - type: DatabaseTypeCode; - supportDatabase: boolean; - supportSchema: boolean; -} - -export type ICreateConnectionDetails = Omit +export type ICreateConnectionDetails = Omit; diff --git a/chat2db-client/src/typings/console.ts b/chat2db-client/src/typings/console.ts index 5c558a37e..8edc35a06 100644 --- a/chat2db-client/src/typings/console.ts +++ b/chat2db-client/src/typings/console.ts @@ -1,4 +1,5 @@ import { ConsoleStatus, DatabaseTypeCode, WorkspaceTabType, ConsoleOpenedStatus } from '@/constants'; +import { IConnectionEnv } from './connection'; export interface ICreateConsoleParams { name?: string; @@ -16,16 +17,17 @@ export interface ICreateConsoleParams { export interface IConsole { id: number; // consoleId name: string; // 控制台名称 - ddl: string; // 控制台内的sql - dataSourceId?: number; // 数据源id + ddl: string; // 控制台内的 sql + dataSourceId?: number; // 数据源 id dataSourceName?: string; // 数据源名称 type?: DatabaseTypeCode; // 数据库类型 databaseName?: string; // 数据库名称 - schemaName?: string; // schema名称 + schemaName?: string; // schema 名称 status: ConsoleStatus; // 控制台状态 connectable: boolean; // 是否可连接 - tabOpened?: ConsoleOpenedStatus; // 控制台tab是否打开 + tabOpened?: ConsoleOpenedStatus; // 控制台 tab 是否打开 operationType: WorkspaceTabType; // 操作类型 + environment?: IConnectionEnv; // 环境信息 } export type ICreateConsole = Omit; diff --git a/chat2db-client/src/typings/database.ts b/chat2db-client/src/typings/database.ts index a99ff117e..8b480ebc1 100644 --- a/chat2db-client/src/typings/database.ts +++ b/chat2db-client/src/typings/database.ts @@ -20,6 +20,11 @@ export interface ITableHeaderItem { } export interface IManageResultData { + statementIndex?: number; + statementStartLine?: number; + statementEndLine?: number; + errorLineInStatement?: number; + errorLine?: number; dataList: string[][]; headerList: ITableHeaderItem[]; description: string; @@ -35,6 +40,17 @@ export interface IManageResultData { updateCount?: number; // 如果是修改的话。后端会返回修改的条数 canEdit?: boolean; // 返回的数据是否可以编辑 tableName?: string; // 如果可以编辑的话。后端会返回表名称。修改需要给后端传递表名 + + /** 虚拟外键建议列表 */ + vkSuggestions?: IVirtualFkSuggestion[]; +} + +export interface IVirtualFkSuggestion { + sourceTable: string; + sourceColumn: string; + targetTable: string; + targetColumn: string; + reason?: string; } /** 查询结果 配置属性 */ @@ -90,3 +106,20 @@ export interface IColumnTypes { export interface IDefaultValue { defaultValue: string; // 默认值 } + +/** 外键定义接口 */ +export interface IForeignKey { + +} + +/** 数据库列表项 */ +export interface IDatabaseItem { + name: string; + comment?: string; +} + +/** Schema列表项 */ +export interface ISchemaItem { + name: string; + comment?: string; +} diff --git a/chat2db-client/src/typings/editTable.ts b/chat2db-client/src/typings/editTable.ts index a00a6987c..b6a5e3b4d 100644 --- a/chat2db-client/src/typings/editTable.ts +++ b/chat2db-client/src/typings/editTable.ts @@ -4,6 +4,7 @@ import { EditColumnOperationType, NullableType } from '@/constants'; export interface IBaseInfo { name: string; comment?: string | null; + aiComment?: string | null; charset: string | null; // 字符集 engine: string | null; // 引擎 incrementValue: string | null; // 自增值 @@ -21,10 +22,11 @@ export interface IColumnItemNew { tableName: string | null; // 表名 columnType: string | null; // 列的类型 比如 varchar(100) ,double(10,6) - dataType: number | null; // 数据类型 + dataType: string | null; // 数据类型 defaultValue: string | null; // 默认值 - autoIncrement: string | null; // 是否自增 + autoIncrement: boolean | null; // 是否自增 comment: string | null; // 注释 + aiComment: string | null; // AI注释 primaryKey: boolean | null; // 是否主键 primaryKeyOrder: number | null; // 主键顺序 typeName: string | null; // 类型名 @@ -44,6 +46,28 @@ export interface IColumnItemNew { value: string | null; // 值 } +export interface IForeignKeyItemNew { + editStatus: EditColumnOperationType | null; // 操作类型 + + key?: string; + id?: number; // 后端存储的外键ID + + name: string | null; // 外键名称 + tableName: string | null; // 表名 + schemaName: string | null; // 模式名 + databaseName: string | null; // 数据库名 + column: string | null; // 外键列名 + referencedTable: string | null; // 引用的表名 + referencedColumn: string | null; // 引用的列名 + + updateRule: number; // 更新规则 + deleteRule: number; // 删除规则 + comment: string | null; // 备注 + + sourceType?: 'REAL' | 'VIRTUAL'; // 外键来源类型 + editable?: boolean; // 是否可编辑 +} + // export interface IIndexIncludeColumnItem { key?: string; // 前端添加的唯一标识 @@ -80,4 +104,5 @@ export interface IIndexItem { export interface IEditTableInfo extends IBaseInfo { columnList: IColumnItemNew[]; indexList: IIndexItem[]; + foreignKeyList: IForeignKeyItemNew[] | undefined; } diff --git a/chat2db-client/src/typings/schemaDiff.ts b/chat2db-client/src/typings/schemaDiff.ts new file mode 100644 index 000000000..ae3d6207b --- /dev/null +++ b/chat2db-client/src/typings/schemaDiff.ts @@ -0,0 +1,152 @@ +export interface ICompareOption { + compareColumn?: boolean; + compareIndex?: boolean; + compareForeignKey?: boolean; + compareTableOption?: boolean; + caseSensitive?: boolean; + excludeDeprecated?: boolean; + ignoreCharsetAlias?: boolean; + ignoreIntegerDisplayWidth?: boolean; + ignoreAutoIncrement?: boolean; +} + +export interface IDiffSummary { + totalTables: number; + tablesOnlyInSource: number; + tablesOnlyInTarget: number; + modifiedTables: number; + unchangedTables: number; + excludedDeprecatedTables: number; +} + +export interface IFieldDiff { + fieldName: string; + sourceValue?: string; + targetValue?: string; +} + +/** 列对比差异项 */ +export interface IColumnDiff { + changeType: 'ADD' | 'MODIFY' | 'DELETE'; + sourceColumn?: ITableColumn; + targetColumn?: ITableColumn; + changedFields?: IFieldDiff[]; +} + +/** 索引对比差异项 */ +export interface IIndexDiff { + changeType: 'ADD' | 'MODIFY' | 'DELETE'; + sourceIndex?: ITableIndex; + targetIndex?: ITableIndex; + changedFields?: IFieldDiff[]; +} + +/** 外键对比差异项 */ +export interface IForeignKeyDiff { + changeType: 'ADD' | 'MODIFY' | 'DELETE'; + sourceForeignKey?: ITableForeignKey; + targetForeignKey?: ITableForeignKey; + changedFields?: IFieldDiff[]; +} + +/** 表列信息 */ +export interface ITableColumn { + name?: string; + dataType?: string; + columnType?: string; + columnSize?: number; + decimalDigits?: number; + nullable?: number; + defaultValue?: string; + comment?: string; + autoIncrement?: boolean; + charSetName?: string; + collationName?: string; + primaryKey?: boolean; + primaryKeyOrder?: number; + ordinalPosition?: number; + editStatus?: string; + oldName?: string; +} + +/** 表索引信息 */ +export interface ITableIndex { + name?: string; + type?: string; + unique?: boolean; + method?: string; + comment?: string; + columnList?: Array<{ columnName?: string; ascOrDesc?: string }>; + editStatus?: string; + oldName?: string; +} + +/** 表外键信息 */ +export interface ITableForeignKey { + name?: string; + column?: string; + referencedTable?: string; + referencedColumn?: string; + updateRule?: number; + deleteRule?: number; + comment?: string; + editStatus?: string; + oldName?: string; +} + +export interface ITableDiff { + tableName: string; + diffType: 'ADDED' | 'REMOVED' | 'MODIFIED' | 'UNCHANGED'; + sourceTable?: any; + targetTable?: any; + columnDiffs?: IColumnDiff[]; + indexDiffs?: IIndexDiff[]; + foreignKeyDiffs?: IForeignKeyDiff[]; + tableOptionDiffs?: IFieldDiff[]; + ddlStatements?: string[]; + ddlStatement?: string; +} + +export interface ISchemaDiffResult { + sourceKey: string; + targetKey: string; + summary: IDiffSummary; + tableDiffs: ITableDiff[]; + warnings?: string[]; +} + +export interface IMigrationStatementResult { + sequence: number; + sql: string; + success: boolean; + errorMessage?: string; + duration?: number; +} + +export interface IMigrateResult { + success: boolean; + statementResults: IMigrationStatementResult[]; + totalStatements: number; + successCount: number; + failCount: number; +} + +export interface ISchemaCompareParams { + sourceDataSourceId: number; + sourceDatabaseName: string; + sourceSchemaName?: string; + targetDataSourceId: number; + targetDatabaseName: string; + targetSchemaName?: string; + tableNames?: string[]; + compareOption?: ICompareOption; +} + +export interface ISchemaMigrateParams { + targetDataSourceId: number; + targetDatabaseName: string; + targetSchemaName?: string; + ddlStatements: string[]; + executeInTransaction?: boolean; + continueOnError?: boolean; +} diff --git a/chat2db-client/src/typings/setting.ts b/chat2db-client/src/typings/setting.ts index 3f20fd630..44f4158ff 100644 --- a/chat2db-client/src/typings/setting.ts +++ b/chat2db-client/src/typings/setting.ts @@ -1,12 +1,69 @@ import { AIType } from './ai'; -export interface IAiConfig { - aiSqlSource: AIType; - apiKey?: string; +export interface IAnthropicConfig { + aiSqlSource: AIType.ANTHROPIC; + apiKey: string; + apiHost?: string; + model?: string; + temperature?: number; + maxTokens?: number; + topP?: number; + topK?: number; + stopSequences?: string; + betaVersion?: string; +} + +export interface IOpenAIConfig { + aiSqlSource: AIType.OPENAI; + apiKey: string; apiHost?: string; httpProxyHost?: string; httpProxyPort?: string; - stream?: boolean; - secretKey?:string; model?: string; + temperature?: number; + maxTokens?: number; + topP?: number; + n?: number; + stop?: string; + presencePenalty?: number; + frequencyPenalty?: number; + logitBias?: string; + user?: string; + organizationId?: string; + projectId?: string; +} + +export interface IFastAIConfig { + aiSqlSource?: AIType; + apiKey?: string; + apiHost?: string; + model?: string; + temperature?: number; + maxTokens?: number; +} + +export type IAiConfig = IAnthropicConfig | IOpenAIConfig; + +export interface IModelItem { + id?: string; + name: string; + model: string; +} + +export interface IModelServiceConfig { + id?: string; + name: string; + provider: AIType; + apiKey?: string; + apiHost?: string; + httpProxyHost?: string; + httpProxyPort?: string; + organizationId?: string; + projectId?: string; + modelList: IModelItem[]; +} + +export interface IDefaultModelConfig { + defaultModelId: string; + fastModelId?: string; } diff --git a/chat2db-client/src/typings/tree.ts b/chat2db-client/src/typings/tree.ts index d7ce93d1c..3e9f918ef 100644 --- a/chat2db-client/src/typings/tree.ts +++ b/chat2db-client/src/typings/tree.ts @@ -51,6 +51,14 @@ export interface ITable { * 表描述 */ comment?: string | null; + /** + * 数据库中的原始注释 + */ + rawComment?: string | null; + /** + * AI 生成注释 + */ + aiComment?: string | null; /** * 表名称 */ @@ -59,5 +67,9 @@ export interface ITable { * 是否已经被固定 */ pinned?: boolean; + /** + * 预估行数 + */ + rowCount?: number; } diff --git a/chat2db-client/src/typings/workspace.ts b/chat2db-client/src/typings/workspace.ts index b882b2046..d7883e4a0 100644 --- a/chat2db-client/src/typings/workspace.ts +++ b/chat2db-client/src/typings/workspace.ts @@ -26,3 +26,13 @@ export interface IBoundInfo { supportDatabase: boolean; supportSchema: boolean; } + +export interface ICurWorkspaceParams { + [key: string]: any; +} + +export interface IWorkspaceModelType { + state: { + databaseAndSchema: any; + }; +} diff --git a/chat2db-client/src/utils/database.ts b/chat2db-client/src/utils/database.ts index b28d3bf28..c5b8363bb 100644 --- a/chat2db-client/src/utils/database.ts +++ b/chat2db-client/src/utils/database.ts @@ -1,8 +1,7 @@ import { DatabaseTypeCode } from '@/constants/common'; -import { IWorkspaceModelType } from '@/models/workspace'; import { Option } from '@/typings/common'; -export function handleDatabaseAndSchema(databaseAndSchema: IWorkspaceModelType['state']['databaseAndSchema']) { +export function handleDatabaseAndSchema(databaseAndSchema: any) { let newCascaderOptions: Option[] = []; if (databaseAndSchema.databases) { newCascaderOptions = (databaseAndSchema?.databases || []).map((t) => { @@ -44,7 +43,7 @@ export function handleDatabaseAndSchema(databaseAndSchema: IWorkspaceModelType[' * @param databaseType * @returns */ -export function compatibleDataBaseName(databaseName: string, databaseType: DatabaseTypeCode) { +export function compatibleDataBaseName(databaseName: string, databaseType: DatabaseTypeCode, schemaName: string = '') { //"" oracele sqlite postgrsql h2 dm // ` MYSQL clickhouse MariaDB @@ -65,6 +64,8 @@ export function compatibleDataBaseName(databaseName: string, databaseType: Datab return `[${databaseName}]`; } else if ([DatabaseTypeCode.MYSQL, DatabaseTypeCode.CLICKHOUSE, DatabaseTypeCode.MARIADB].includes(databaseType)) { return `\`${databaseName}\``; + } else if ([DatabaseTypeCode.PHOENIX].includes(databaseType)) { + return `${schemaName}.${databaseName}`; } else { return `${databaseName}`; } diff --git a/chat2db-client/src/utils/date.ts b/chat2db-client/src/utils/date.ts index 0ac1d47fb..defe39a9b 100644 --- a/chat2db-client/src/utils/date.ts +++ b/chat2db-client/src/utils/date.ts @@ -8,7 +8,7 @@ export function formatDate(date: any, fmt = 'yyyy-MM-dd') { if (!(date instanceof Date) || isNaN(date.getTime())) { return ''; } - var o: any = { + const o: any = { 'M+': date.getMonth() + 1, 'd+': date.getDate(), 'h+': date.getHours(), @@ -18,7 +18,7 @@ export function formatDate(date: any, fmt = 'yyyy-MM-dd') { S: date.getMilliseconds(), }; if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)); - for (var k in o) + for (const k in o) if (new RegExp('(' + k + ')').test(fmt)) fmt = fmt.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length)); return fmt; @@ -34,4 +34,4 @@ export function transitionTimezoneTimestamp(timestamp: number) { export function getUserTimezoneTimestamp(timestamp: number | string) { const timezoneOffset = new Date().getTimezoneOffset() * 60 * 1000 return +timestamp - timezoneOffset -} \ No newline at end of file +} diff --git a/chat2db-client/src/utils/eventSource.ts b/chat2db-client/src/utils/eventSource.ts index e5df4c683..55ae01f0c 100644 --- a/chat2db-client/src/utils/eventSource.ts +++ b/chat2db-client/src/utils/eventSource.ts @@ -1,44 +1,112 @@ -import { EventSourcePolyfill } from 'event-source-polyfill'; +import { ChatStateType } from '@/pages/main/workspace/store/aiChatStore'; +import { cancelSSESession, createChatPayload, createSSEConnection, getSSEBaseUrl } from '@/utils/sse'; -const connectToEventSource = (params: { +interface EventSourceOptions { url: string; uid: string; - onOpen: Function; - onMessage: Function; - onError: Function; -}) => { - const { url, uid, onOpen, onMessage, onError } = params; - - if (!url || !onMessage || !onError) { - throw new Error('url, onMessage, and onError are required'); + onOpen?: () => void; + onMessage?: (content: string, thinking?: string) => void; + onStateChange?: (state: ChatStateType, message?: string) => void; + onError?: (error: string) => void; + onDone?: () => void; + onTablesSelected?: (tables: string[]) => void; + onSchemaFetched?: (ddl: string) => void; + onExplain?: (data: { sql: string; plan: string[][]; formatted: string; success: boolean }) => void; +} + +const connectToEventSource = ( + options: EventSourceOptions +): (() => void) => { + const { + url, uid, onOpen, onMessage, onStateChange, + onError, onDone, onTablesSelected, onSchemaFetched, onExplain + } = options; + + if (!url) { + throw new Error('url is required'); } - const DBHUB = localStorage.getItem('DBHUB'); - const p = { - headers: { - uid, - DBHUB, - }, - }; - const eventSource = new EventSourcePolyfill(`${window._BaseURL}${url}`, p); + const sseBaseUrl = getSSEBaseUrl(); + console.log('[SSE] Connecting to:', `${sseBaseUrl}${url}`, 'uid:', uid); + const eventSource = createSSEConnection({ url, uid }); - eventSource.onopen = () => { - onOpen(); - }; + eventSource.addEventListener('open', () => { + console.log('[SSE] Connection opened'); + onOpen?.(); + }); - eventSource.onmessage = (event) => { - onMessage(event.data); - }; + eventSource.addEventListener('message', (event: any) => { + console.log('[SSE] Message received:', event.data); + const data = event.data; + if (data === '[DONE]') { + console.log('[SSE] Stream completed'); + onDone?.(); + eventSource.close(); + return; + } + try { + const parsed = JSON.parse(data); + if (parsed.content || parsed.thinking) { + onMessage?.(parsed.content, parsed.thinking); + } + } catch { + onMessage?.(data); + } + }); - eventSource.onerror = (error) => { - onError(error); - console.error('EventSourcePolyfill error:', error); - }; + eventSource.addEventListener('state', (event: any) => { + console.log('[SSE] State event:', event.data); + try { + const { state, message } = JSON.parse(event.data); + onStateChange?.(state as ChatStateType, message); + } catch (e) { + console.error('Failed to parse state event', e); + } + }); + + eventSource.addEventListener('error', (e) => { + console.error('[SSE] Error:', e); + onError?.('Connection error'); + eventSource.close(); + }); + + eventSource.addEventListener('tables_selected', (event: any) => { + try { + const { tables } = JSON.parse(event.data); + onTablesSelected?.(tables); + } catch (e) { + console.error('Failed to parse tables_selected event', e); + } + }); + + eventSource.addEventListener('schema_fetched', (event: any) => { + try { + const { ddl } = JSON.parse(event.data); + onSchemaFetched?.(ddl); + } catch (e) { + console.error('Failed to parse schema_fetched event', e); + } + }); + + eventSource.addEventListener('explain', (event: any) => { + try { + const data = JSON.parse(event.data); + onExplain?.(data); + } catch (e) { + console.error('Failed to parse explain event', e); + } + }); - // 返回一个关闭 eventSource 的函数,以便在需要时调用它 return () => { eventSource.close(); }; }; +const cancelChatSession = async (sessionId: string): Promise => { + await cancelSSESession(sessionId); +}; + +export { cancelChatSession }; +export { createChatPayload }; + export default connectToEventSource; diff --git a/chat2db-client/src/utils/file.ts b/chat2db-client/src/utils/file.ts index 07a88c86b..3a351964b 100644 --- a/chat2db-client/src/utils/file.ts +++ b/chat2db-client/src/utils/file.ts @@ -1,44 +1,63 @@ -/** - * 文件下载 - * @param url - * @param params - */ -export function downloadFile(url: string, params: any) { - // 创建POST请求 +export interface IDownloadOptions { + onProgress?: (percent: number) => void; +} + +export function downloadFile(url: string, params: any, options?: IDownloadOptions) { + const { onProgress } = options || {}; + fetch(url, { method: 'POST', headers: { - 'Content-Type': 'application/json', // 或者根据服务端的要求设置其他的内容类型 + 'Content-Type': 'application/json', }, - body: JSON.stringify(params), // 将参数转换为JSON字符串 + body: JSON.stringify(params), }) .then((response) => { - // 从content-disposition头中获取文件名 const contentDisposition = response.headers.get('content-disposition'); const filename = contentDisposition ? decodeURIComponent(contentDisposition.split("''")[1]) : 'file.txt'; - // 获取返回的Blob数据 - return response.blob().then((blob) => ({ blob, filename })); + const contentLength = response.headers.get('content-length'); + const total = contentLength ? parseInt(contentLength, 10) : 0; + + if (!response.body) { + return response.blob().then((blob) => ({ blob, filename })); + } + + const reader = response.body.getReader(); + const chunks: Uint8Array[] = []; + let received = 0; + + return new Promise<{ blob: Blob; filename: string }>((resolve) => { + const pump = (): Promise => + reader.read().then(({ done, value }) => { + if (done) { + const blob = new Blob(chunks); + resolve({ blob, filename }); + return; + } + chunks.push(value); + received += value.length; + if (total > 0 && onProgress) { + onProgress(Math.round((received / total) * 100)); + } + return pump(); + }); + pump(); + }); }) .then(({ blob, filename }) => { - // 创建一个代表Blob对象的URL const blobUrl = URL.createObjectURL(blob); - - // 创建一个隐藏的 标签,并设置其 href 属性 const a = document.createElement('a'); a.style.display = 'none'; a.href = blobUrl; - - // 使用从响应头解析的文件名 a.download = filename; - - // 将 标签附加到 DOM,并触发点击事件 document.body.appendChild(a); a.click(); - - // 清理:从 DOM 中移除 标签,并释放Blob URL document.body.removeChild(a); URL.revokeObjectURL(blobUrl); + if (onProgress) { + onProgress(100); + } }) .catch((error) => { console.error('下载文件失败:', error); diff --git a/chat2db-client/src/utils/getTree.ts b/chat2db-client/src/utils/getTree.ts index a8a1e063f..9e9633e47 100644 --- a/chat2db-client/src/utils/getTree.ts +++ b/chat2db-client/src/utils/getTree.ts @@ -1,3 +1,3 @@ export const getTreeConfig = { -} \ No newline at end of file +} diff --git a/chat2db-client/src/utils/index.ts b/chat2db-client/src/utils/index.ts index 790b66c41..f553c3016 100644 --- a/chat2db-client/src/utils/index.ts +++ b/chat2db-client/src/utils/index.ts @@ -123,14 +123,15 @@ export function approximateList( // 获取var变量的值 export const callVar = (css: string) => { - return getComputedStyle(document.documentElement).getPropertyValue(css).trim(); + return getComputedStyle(document.documentElement).getPropertyValue(css) +.trim(); }; // 给我一个 obj[], 和 obj的 key 和 value,给你返index -export function findObjListValue(list: T[], key: K, value: any) { +export function findObjListValue, K extends keyof T>(list: T[], key: K, value: any) { let flag = -1; list.forEach((t: T, index) => { - Object.keys(t).forEach((j: K) => { + (Object.keys(t) as K[]).forEach((j: K) => { if (j === key && t[j] === value) { flag = index; } diff --git a/chat2db-client/src/utils/indexedDbStorage.ts b/chat2db-client/src/utils/indexedDbStorage.ts new file mode 100644 index 000000000..28f5cf3ba --- /dev/null +++ b/chat2db-client/src/utils/indexedDbStorage.ts @@ -0,0 +1,88 @@ +import { StateStorage } from 'zustand/middleware'; + +export const createIndexedDbStorage = (tableName: string, rowKey: string): StateStorage => { + return { + getItem: async (name: string): Promise => { + try { + const db = window._indexedDB?.chat2db; + if (!db) { + return null; + } + return await new Promise((resolve, reject) => { + const transaction = db.transaction(tableName, 'readonly'); + const objectStore = transaction.objectStore(tableName); + const request = objectStore.get(rowKey); + request.onsuccess = () => { + const result = request.result; + if (!result) { + resolve(null); + return; + } + const value = result[name]; + if (typeof value === 'string') { + resolve(value); + } else { + resolve(null); + } + }; + request.onerror = () => reject(request.error); + }); + } catch (e) { + console.error('[IndexedDbStorage] getItem error:', e); + return null; + } + }, + + setItem: async (name: string, value: string): Promise => { + try { + const db = window._indexedDB?.chat2db; + if (!db) { + return; + } + await new Promise((resolve, reject) => { + const transaction = db.transaction(tableName, 'readwrite'); + const objectStore = transaction.objectStore(tableName); + const request = objectStore.get(rowKey); + request.onsuccess = () => { + const existing = request.result || { id: rowKey }; + existing[name] = value; + const putReq = objectStore.put(existing); + putReq.onsuccess = () => resolve(); + putReq.onerror = () => reject(putReq.error); + }; + request.onerror = () => reject(request.error); + }); + } catch (e) { + console.error('[IndexedDbStorage] setItem error:', e); + } + }, + + removeItem: async (name: string): Promise => { + try { + const db = window._indexedDB?.chat2db; + if (!db) { + return; + } + await new Promise((resolve, reject) => { + const transaction = db.transaction(tableName, 'readwrite'); + const objectStore = transaction.objectStore(tableName); + const request = objectStore.get(rowKey); + request.onsuccess = () => { + const existing = request.result; + if (!existing) { + resolve(); + return; + } + delete existing[name]; + const putReq = objectStore.put(existing); + putReq.onsuccess = () => resolve(); + putReq.onerror = () => reject(putReq.error); + }; + request.onerror = () => reject(request.error); + }); + } catch (e) { + console.error('[IndexedDbStorage] removeItem error:', e); + } + }, + }; +}; diff --git a/chat2db-client/src/utils/localStorage.ts b/chat2db-client/src/utils/localStorage.ts index ed4abfdd6..6c7fdb796 100644 --- a/chat2db-client/src/utils/localStorage.ts +++ b/chat2db-client/src/utils/localStorage.ts @@ -1,5 +1,5 @@ import { ThemeType, PrimaryColorType, LangType } from '@/constants'; -import { ICurWorkspaceParams } from '@/models/workspace'; +import { ICurWorkspaceParams } from '@/typings/workspace'; import { getCookie } from '@/utils'; export function getLang(): LangType { diff --git a/chat2db-client/src/utils/sse.ts b/chat2db-client/src/utils/sse.ts new file mode 100644 index 000000000..48d7da247 --- /dev/null +++ b/chat2db-client/src/utils/sse.ts @@ -0,0 +1,65 @@ +import { EventSourcePolyfill } from 'event-source-polyfill'; + +interface ISSEConnectionOptions { + url: string; + uid: string; +} + +export const getSSEBaseUrl = (): string => { + const storedBaseURL = localStorage.getItem('_BaseURL'); + if (storedBaseURL) { + return storedBaseURL; + } + if (location.href.indexOf('dist/index.html') > -1) { + return `http://127.0.0.1:${__APP_PORT__ || '10824'}`; + } + const isDev = process.env.NODE_ENV === 'development'; + if (isDev) { + return 'http://127.0.0.1:10821'; + } + return location.origin; +}; + +export const createSSEConnection = ({ url, uid }: ISSEConnectionOptions) => { + if (!url) { + throw new Error('url is required'); + } + + const DBHUB = localStorage.getItem('DBHUB'); + return new EventSourcePolyfill(`${getSSEBaseUrl()}${url}`, { + headers: { + uid, + DBHUB: DBHUB || '', + }, + heartbeatTimeout: 12000000, + }); +}; + +export const createChatPayload = async (payload: Record): Promise => { + const DBHUB = localStorage.getItem('DBHUB'); + const response = await fetch(`${getSSEBaseUrl()}/api/ai/chat/payload`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + DBHUB: DBHUB || '', + }, + body: JSON.stringify(payload), + }); + + if (!response.ok) { + throw new Error(`Create chat payload failed: ${response.status}`); + } + + const data = await response.json(); + return data.payloadId; +}; + +export const cancelSSESession = async (sessionId: string): Promise => { + const DBHUB = localStorage.getItem('DBHUB'); + await fetch(`${getSSEBaseUrl()}/api/ai/chat/${sessionId}`, { + method: 'DELETE', + headers: { + DBHUB: DBHUB || '', + }, + }); +}; diff --git a/chat2db-client/src/utils/webpack.ts b/chat2db-client/src/utils/webpack.ts index 5b7d26b3f..86cb2f01e 100644 --- a/chat2db-client/src/utils/webpack.ts +++ b/chat2db-client/src/utils/webpack.ts @@ -28,7 +28,7 @@ export function formatDate(date: any, fmt = 'yyyy-MM-dd') { if (!(date instanceof Date) || isNaN(date.getTime())) { return ''; } - var o: any = { + const o: any = { 'M+': date.getMonth() + 1, 'd+': date.getDate(), 'h+': date.getHours(), @@ -38,7 +38,7 @@ export function formatDate(date: any, fmt = 'yyyy-MM-dd') { S: date.getMilliseconds(), }; if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)); - for (var k in o) + for (const k in o) if (new RegExp('(' + k + ')').test(fmt)) fmt = fmt.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length)); return fmt; diff --git a/chat2db-client/yarn.lock b/chat2db-client/yarn.lock index 11ad8c01f..f25366b5e 100644 --- a/chat2db-client/yarn.lock +++ b/chat2db-client/yarn.lock @@ -1,11031 +1,17986 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"7zip-bin@~5.1.1": - version "5.1.1" - resolved "https://registry.npmmirror.com/7zip-bin/-/7zip-bin-5.1.1.tgz#9274ec7460652f9c632c59addf24efb1684ef876" - integrity sha512-sAP4LldeWNz0lNzmTird3uWfFDWWTeg6V/MsmyyLR9X1idwKBWIgt/ZvinqQldJm3LecKEs1emkbquO6PCiLVQ== - -"@aashutoshrathi/word-wrap@^1.2.3": - version "1.2.6" - resolved "https://registry.npmmirror.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" - integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== - -"@ahooksjs/use-request@^2.0.0": - version "2.8.15" - resolved "https://registry.npmmirror.com/@ahooksjs/use-request/-/use-request-2.8.15.tgz#daa32a8395ba75e8deb9f4fde4e221a4a8f525db" - integrity sha512-xhVaM4fyIiAMdVFuuU5i3CFUdFa/IblF+fvITVMFaUEO3w/V5tVCAF6WIA3T03n1/RPuzRkA7Ao1PFtSGtGelw== - dependencies: - lodash.debounce "^4.0.8" - lodash.throttle "^4.1.1" - -"@alloc/quick-lru@^5.2.0": - version "5.2.0" - resolved "https://registry.npmmirror.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz#7bf68b20c0a350f936915fcae06f58e32007ce30" - integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw== - -"@ampproject/remapping@^2.2.0": - version "2.2.1" - resolved "https://registry.npmmirror.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" - integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg== - dependencies: - "@jridgewell/gen-mapping" "^0.3.0" - "@jridgewell/trace-mapping" "^0.3.9" - -"@ant-design/antd-theme-variable@^1.0.0": - version "1.0.0" - resolved "https://registry.npmmirror.com/@ant-design/antd-theme-variable/-/antd-theme-variable-1.0.0.tgz#b930e20fa3de9300bf3987aaccfd11529568b93f" - integrity sha512-0vr5GCwM7xlAl6NxG1lPbABO+SYioNJL3HVy2FA8wTlsIMoZvQwcwsxTw6eLQCiN9V2UQ8kBtfz8DW8utVVE5w== - -"@ant-design/colors@^6.0.0": - version "6.0.0" - resolved "https://registry.npmmirror.com/@ant-design/colors/-/colors-6.0.0.tgz#9b9366257cffcc47db42b9d0203bb592c13c0298" - integrity sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ== - dependencies: - "@ctrl/tinycolor" "^3.4.0" - -"@ant-design/colors@^7.0.0": - version "7.0.0" - resolved "https://registry.npmmirror.com/@ant-design/colors/-/colors-7.0.0.tgz#eb7eecead124c3533aea05d61254f0a17f2b61b3" - integrity sha512-iVm/9PfGCbC0dSMBrz7oiEXZaaGH7ceU40OJEfKmyuzR9R5CRimJYPlRiFtMQGQcbNMea/ePcoIebi4ASGYXtg== - dependencies: - "@ctrl/tinycolor" "^3.4.0" - -"@ant-design/cssinjs@^1.11.1", "@ant-design/cssinjs@^1.9.1": - version "1.12.0" - resolved "https://registry.npmmirror.com/@ant-design/cssinjs/-/cssinjs-1.12.0.tgz#fd7a561a554a7b17c723a96af2bf188b5955c4a4" - integrity sha512-59ZifzlQxVsHSf+n1/Zc+lB7nnxSymwdtuN1biZ5V8mRql9LBbuAyN3TX5/sKWvntBZrDb/yAB6bHgD5JW48ag== - dependencies: - "@babel/runtime" "^7.11.1" - "@emotion/hash" "^0.8.0" - "@emotion/unitless" "^0.7.5" - classnames "^2.3.1" - csstype "^3.0.10" - rc-util "^5.34.1" - stylis "^4.0.13" - -"@ant-design/cssinjs@^1.18.0": - version "1.18.0" - resolved "https://registry.npmmirror.com/@ant-design/cssinjs/-/cssinjs-1.18.0.tgz#92701684cab5fc67bb62bc813ec1b4a33258018d" - integrity sha512-NXzfnNjJgpn+L6d0cD2cS14Tsqs46Bsua6PwVMlmN+F0OEoa9PhJRwUWmI+HyIrc4cgVZVfQTDpXC0p07Jmglw== - dependencies: - "@babel/runtime" "^7.11.1" - "@emotion/hash" "^0.8.0" - "@emotion/unitless" "^0.7.5" - classnames "^2.3.1" - csstype "^3.0.10" - rc-util "^5.35.0" - stylis "^4.0.13" - -"@ant-design/icons-svg@^4.2.1": - version "4.2.1" - resolved "https://registry.npmmirror.com/@ant-design/icons-svg/-/icons-svg-4.2.1.tgz#8630da8eb4471a4aabdaed7d1ff6a97dcb2cf05a" - integrity sha512-EB0iwlKDGpG93hW8f85CTJTs4SvMX7tt5ceupvhALp1IF44SeUFOMhKUOYqpsoYWQKAOuTRDMqn75rEaKDp0Xw== - -"@ant-design/icons-svg@^4.3.0": - version "4.3.1" - resolved "https://registry.npmmirror.com/@ant-design/icons-svg/-/icons-svg-4.3.1.tgz#4b2f65a17d4d32b526baa6414aca2117382bf8da" - integrity sha512-4QBZg8ccyC6LPIRii7A0bZUk3+lEDCLnhB+FVsflGdcWPPmV+j3fire4AwwoqHV/BibgvBmR9ZIo4s867smv+g== - -"@ant-design/icons@^4.7.0": - version "4.8.0" - resolved "https://registry.npmmirror.com/@ant-design/icons/-/icons-4.8.0.tgz#3084e2bb494cac3dad6c0392f77c1efc90ee1fa4" - integrity sha512-T89P2jG2vM7OJ0IfGx2+9FC5sQjtTzRSz+mCHTXkFn/ELZc2YpfStmYHmqzq2Jx55J0F7+O6i5/ZKFSVNWCKNg== - dependencies: - "@ant-design/colors" "^6.0.0" - "@ant-design/icons-svg" "^4.2.1" - "@babel/runtime" "^7.11.2" - classnames "^2.2.6" - rc-util "^5.9.4" - -"@ant-design/icons@^5.0.0": - version "5.1.4" - resolved "https://registry.npmmirror.com/@ant-design/icons/-/icons-5.1.4.tgz#614e29e26d092c2c1c1a2acbc0d84434d8d1474e" - integrity sha512-YHKL7Jx3bM12OxvtiYDon04BsBT/6LGitYEqar3GljzWaAyMOAD8i/uF1Rsi5Us/YNdWWXBGSvZV2OZWMpJlcA== - dependencies: - "@ant-design/colors" "^7.0.0" - "@ant-design/icons-svg" "^4.2.1" - "@babel/runtime" "^7.11.2" - classnames "^2.2.6" - rc-util "^5.31.1" - -"@ant-design/icons@^5.2.6": - version "5.2.6" - resolved "https://registry.npmmirror.com/@ant-design/icons/-/icons-5.2.6.tgz#2d4a9a37f531eb2a20cebec01d6fb69cf593900d" - integrity sha512-4wn0WShF43TrggskBJPRqCD0fcHbzTYjnaoskdiJrVHg86yxoZ8ZUqsXvyn4WUqehRiFKnaclOhqk9w4Ui2KVw== - dependencies: - "@ant-design/colors" "^7.0.0" - "@ant-design/icons-svg" "^4.3.0" - "@babel/runtime" "^7.11.2" - classnames "^2.2.6" - rc-util "^5.31.1" - -"@ant-design/moment-webpack-plugin@^0.0.3": - version "0.0.3" - resolved "https://registry.npmmirror.com/@ant-design/moment-webpack-plugin/-/moment-webpack-plugin-0.0.3.tgz#2524f513b2f0b223b94b99626be281d0a334123f" - integrity sha512-MLm1FUpg02fP615ShQnCUN9la2E4RylDxKyolkGqAWTIHO4HyGM0A5x71AMALEyP/bC+UEEWBGSQ+D4/8hQ+ww== - -"@ant-design/pro-card@2.5.6": - version "2.5.6" - resolved "https://registry.npmmirror.com/@ant-design/pro-card/-/pro-card-2.5.6.tgz#b5b1bcfcad7212ec757aece262b1c631a61a79c9" - integrity sha512-/TjmXasTIo0tYIo90uSDXvp4MyXqdUAGKANkoboYx01D4Ykr/d8wNO2IM41ubS9ywDDaCWAqwdlw58GJKVpI9Q== - dependencies: - "@ant-design/icons" "^5.0.0" - "@ant-design/pro-provider" "2.11.1" - "@ant-design/pro-utils" "2.12.5" - "@babel/runtime" "^7.18.0" - classnames "^2.3.2" - omit.js "^2.0.2" - rc-resize-observer "^1.0.0" - rc-util "^5.4.0" - -"@ant-design/pro-components@^2.0.1": - version "2.6.7" - resolved "https://registry.npmmirror.com/@ant-design/pro-components/-/pro-components-2.6.7.tgz#a012f903a7359244c5673b8180bdce81d64bd532" - integrity sha512-gExeszVr+m6EK7oAifrvmu5w4+3KkHDwcli3hT1W3mqAT+zXVjX4uJXlfT78hxGKcPmHz11HVZIA5yKvhkljaQ== - dependencies: - "@ant-design/pro-card" "2.5.6" - "@ant-design/pro-descriptions" "2.4.7" - "@ant-design/pro-field" "2.10.6" - "@ant-design/pro-form" "2.16.1" - "@ant-design/pro-layout" "7.16.3" - "@ant-design/pro-list" "2.5.7" - "@ant-design/pro-provider" "2.11.1" - "@ant-design/pro-skeleton" "2.1.6" - "@ant-design/pro-table" "3.10.1" - "@ant-design/pro-utils" "2.12.5" - "@babel/runtime" "^7.16.3" - -"@ant-design/pro-descriptions@2.4.7": - version "2.4.7" - resolved "https://registry.npmmirror.com/@ant-design/pro-descriptions/-/pro-descriptions-2.4.7.tgz#047780423f790240ec2833262cf4682b2187d8fe" - integrity sha512-Rn0JU6mxOWjhQZRSchCw6UL6g29DIXuYLZooPjub3drwSAx15BW3hKvDqN20Zq6Xq2xIChZ8moTO+kesbCRuSA== - dependencies: - "@ant-design/pro-field" "2.10.6" - "@ant-design/pro-form" "2.16.1" - "@ant-design/pro-skeleton" "2.1.6" - "@ant-design/pro-utils" "2.12.5" - "@babel/runtime" "^7.18.0" - rc-resize-observer "^0.2.3" - rc-util "^5.0.6" - use-json-comparison "^1.0.5" - -"@ant-design/pro-field@2.10.6": - version "2.10.6" - resolved "https://registry.npmmirror.com/@ant-design/pro-field/-/pro-field-2.10.6.tgz#b037172c16cf04b7bf42bf76abdc45bdb80626ed" - integrity sha512-2MpcB/WyhtCahbPPZe01KJ9e2Kd0cvlUUjJe8jK/kP4HwAZ5eu64URblHSKO6a2rDVRk22yEfOFBa6QmW++ugQ== - dependencies: - "@ant-design/icons" "^5.0.0" - "@ant-design/pro-provider" "2.11.1" - "@ant-design/pro-utils" "2.12.5" - "@babel/runtime" "^7.18.0" - "@chenshuai2144/sketch-color" "^1.0.8" - classnames "^2.3.2" - dayjs "^1.11.9" - lodash.tonumber "^4.0.3" - omit.js "^2.0.2" - rc-util "^5.4.0" - swr "^2.0.0" - -"@ant-design/pro-form@2.16.1": - version "2.16.1" - resolved "https://registry.npmmirror.com/@ant-design/pro-form/-/pro-form-2.16.1.tgz#2b5b0b709274144ed1f54de8d25107191e5c5a81" - integrity sha512-7l5dMQEj7YnJ1MfwdNbRwgP9UYzb9KovFXYYsorShgCMoQT6kZuUlZ6WW8FlYZM0H5zGTNEOxMDtebemcNEXUg== - dependencies: - "@ant-design/icons" "^5.0.0" - "@ant-design/pro-field" "2.10.6" - "@ant-design/pro-provider" "2.11.1" - "@ant-design/pro-utils" "2.12.5" - "@babel/runtime" "^7.18.0" - "@chenshuai2144/sketch-color" "^1.0.7" - "@umijs/use-params" "^1.0.9" - classnames "^2.3.2" - lodash.merge "^4.6.2" - omit.js "^2.0.2" - rc-resize-observer "^1.1.0" - rc-util "^5.0.6" - use-json-comparison "^1.0.5" - use-media-antd-query "^1.1.0" - -"@ant-design/pro-layout@7.16.3": - version "7.16.3" - resolved "https://registry.npmmirror.com/@ant-design/pro-layout/-/pro-layout-7.16.3.tgz#749c0d8c21f8066ef6a212b24bf403bd100275de" - integrity sha512-4iTA52/U0JlOKBsNkAHRVxnLUQbP0yhhIAY7TIOKg9R+64VidEtRlKUikigrNZEO3VUNo3zJ00BiywpHmD92Ug== - dependencies: - "@ant-design/icons" "^5.0.0" - "@ant-design/pro-provider" "2.11.1" - "@ant-design/pro-utils" "2.12.5" - "@babel/runtime" "^7.18.0" - "@umijs/route-utils" "^4.0.0" - "@umijs/use-params" "^1.0.9" - classnames "^2.3.2" - lodash.merge "^4.6.2" - omit.js "^2.0.2" - path-to-regexp "2.4.0" - rc-resize-observer "^1.1.0" - rc-util "^5.0.6" - swr "^2.0.0" - use-json-comparison "^1.0.3" - use-media-antd-query "^1.1.0" - warning "^4.0.3" - -"@ant-design/pro-list@2.5.7": - version "2.5.7" - resolved "https://registry.npmmirror.com/@ant-design/pro-list/-/pro-list-2.5.7.tgz#ecee0f446a0c86c5795dcb4d06c9350dfee2ee52" - integrity sha512-9H2ebA5KB3YB221Ez+mULHKiRwNbTeUqyhp4RRg1pHWD1nvjFiMFb0S+K0dI/EP0OLqOWxvU5CfmucH7k3G9BA== - dependencies: - "@ant-design/icons" "^5.0.0" - "@ant-design/pro-card" "2.5.6" - "@ant-design/pro-field" "2.10.6" - "@ant-design/pro-table" "3.10.1" - "@ant-design/pro-utils" "2.12.5" - "@babel/runtime" "^7.18.0" - classnames "^2.3.2" - dayjs "^1.11.9" - rc-resize-observer "^1.0.0" - rc-util "^4.19.0" - use-media-antd-query "^1.1.0" - -"@ant-design/pro-provider@2.11.1": - version "2.11.1" - resolved "https://registry.npmmirror.com/@ant-design/pro-provider/-/pro-provider-2.11.1.tgz#063b1157766a571fd2c06a6db4f52a5590976ffd" - integrity sha512-A7zXZ+58IGVuIAGvo8Hia9Wz0TFfFMo+tIv16Pu2RF9sMdGg4pE5M87qT7+45TkKodWpnpBd4cIte3jfY5v/LQ== - dependencies: - "@ant-design/cssinjs" "^1.11.1" - "@babel/runtime" "^7.18.0" - "@ctrl/tinycolor" "^3.4.0" - rc-util "^5.0.1" - swr "^2.0.0" - -"@ant-design/pro-skeleton@2.1.6": - version "2.1.6" - resolved "https://registry.npmmirror.com/@ant-design/pro-skeleton/-/pro-skeleton-2.1.6.tgz#ef280bac886e26aeb2a404e002115cc57ea6ede9" - integrity sha512-IQD1rjMvHA2Ca8Ez/w8JWAEpGJSjCUwlm0Xm0KU02xOKw3YNGE8HC8TdZ9TbA3VJIzvi6g/J/UQWImWRgkRFbA== - dependencies: - "@babel/runtime" "^7.18.0" - use-media-antd-query "^1.1.0" - -"@ant-design/pro-table@3.10.1": - version "3.10.1" - resolved "https://registry.npmmirror.com/@ant-design/pro-table/-/pro-table-3.10.1.tgz#1cf8bc64676cee97ce3ccc25fcbb5a2b610c5642" - integrity sha512-jFJavMiYJI7AYSxwlk9ebQlzS5SWpALrDMLJ4s8VCj6L8Q0R3PkwQIBDzRYbM4w/eJ62Io3/hQktWpronhQBKw== - dependencies: - "@ant-design/icons" "^5.0.0" - "@ant-design/pro-card" "2.5.6" - "@ant-design/pro-field" "2.10.6" - "@ant-design/pro-form" "2.16.1" - "@ant-design/pro-provider" "2.11.1" - "@ant-design/pro-utils" "2.12.5" - "@babel/runtime" "^7.18.0" - "@dnd-kit/core" "^6.0.8" - "@dnd-kit/sortable" "^7.0.2" - "@dnd-kit/utilities" "^3.2.1" - classnames "^2.3.2" - dayjs "^1.11.9" - omit.js "^2.0.2" - rc-resize-observer "^1.0.0" - rc-util "^5.0.1" - use-json-comparison "^1.0.5" - -"@ant-design/pro-utils@2.12.5": - version "2.12.5" - resolved "https://registry.npmmirror.com/@ant-design/pro-utils/-/pro-utils-2.12.5.tgz#ae5d54534d27ca79de679c14c5dc10f92fed9be5" - integrity sha512-3/f4sghGELYc4/1fwlGx5o8+szs7SAXTpaXxJaA6C0BfkiuHqosOwCt2dhikdNSKXb3UIbvV0cLcm4qKPuXGRA== - dependencies: - "@ant-design/icons" "^5.0.0" - "@ant-design/pro-provider" "2.11.1" - "@babel/runtime" "^7.18.0" - classnames "^2.3.2" - dayjs "^1.11.9" - rc-util "^5.0.6" - swr "^2.0.0" - -"@ant-design/react-slick@~1.0.2": - version "1.0.2" - resolved "https://registry.npmmirror.com/@ant-design/react-slick/-/react-slick-1.0.2.tgz#241bb412aeacf7ff5d50c61fa5db66773fde6b56" - integrity sha512-Wj8onxL/T8KQLFFiCA4t8eIRGpRR+UPgOdac2sYzonv+i0n3kXHmvHLLiOYL655DQx2Umii9Y9nNgL7ssu5haQ== - 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" - -"@antfu/install-pkg@^0.1.1": - version "0.1.1" - resolved "https://registry.npmmirror.com/@antfu/install-pkg/-/install-pkg-0.1.1.tgz#157bb04f0de8100b9e4c01734db1a6c77e98bbb5" - integrity sha512-LyB/8+bSfa0DFGC06zpCEfs89/XoWZwws5ygEa5D+Xsm3OfI+aXQ86VgVG7Acyef+rSZ5HE7J8rrxzrQeM3PjQ== - dependencies: - execa "^5.1.1" - find-up "^5.0.0" - -"@antfu/utils@^0.7.2": - version "0.7.5" - resolved "https://registry.npmmirror.com/@antfu/utils/-/utils-0.7.5.tgz#c36f37add92a7de57b9c29ae0c1f399706bff345" - integrity sha512-dlR6LdS+0SzOAPx/TPRhnoi7hE251OVeT2Snw0RguNbBSbjUHdWr0l3vcUUDg26rEysT89kCbtw1lVorBXLLCg== - -"@babel/cli@^7.21.0": - version "7.22.9" - resolved "https://registry.npmmirror.com/@babel/cli/-/cli-7.22.9.tgz#501b3614aeda7399371f6d5991404f069b059986" - integrity sha512-nb2O7AThqRo7/E53EGiuAkMaRbb7J5Qp3RvN+dmua1U+kydm0oznkhqbTEG15yk26G/C3yL6OdZjzgl+DMXVVA== - dependencies: - "@jridgewell/trace-mapping" "^0.3.17" - commander "^4.0.1" - convert-source-map "^1.1.0" - fs-readdir-recursive "^1.1.0" - glob "^7.2.0" - make-dir "^2.1.0" - slash "^2.0.0" - optionalDependencies: - "@nicolo-ribaudo/chokidar-2" "2.1.8-no-fsevents.3" - chokidar "^3.4.0" - -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.22.5.tgz#234d98e1551960604f1246e6475891a570ad5658" - integrity sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ== - dependencies: - "@babel/highlight" "^7.22.5" - -"@babel/code-frame@^7.22.13": - version "7.22.13" - resolved "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e" - integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w== - dependencies: - "@babel/highlight" "^7.22.13" - chalk "^2.4.2" - -"@babel/compat-data@^7.20.5", "@babel/compat-data@^7.22.5", "@babel/compat-data@^7.22.6", "@babel/compat-data@^7.22.9": - version "7.22.9" - resolved "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.22.9.tgz#71cdb00a1ce3a329ce4cbec3a44f9fef35669730" - integrity sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ== - -"@babel/core@7.23.2": - version "7.23.2" - resolved "https://registry.npmmirror.com/@babel/core/-/core-7.23.2.tgz#ed10df0d580fff67c5f3ee70fd22e2e4c90a9f94" - integrity sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ== - dependencies: - "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.22.13" - "@babel/generator" "^7.23.0" - "@babel/helper-compilation-targets" "^7.22.15" - "@babel/helper-module-transforms" "^7.23.0" - "@babel/helpers" "^7.23.2" - "@babel/parser" "^7.23.0" - "@babel/template" "^7.22.15" - "@babel/traverse" "^7.23.2" - "@babel/types" "^7.23.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" - -"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.17.9", "@babel/core@^7.19.6", "@babel/core@^7.21.0", "@babel/core@^7.21.4": - version "7.22.9" - resolved "https://registry.npmmirror.com/@babel/core/-/core-7.22.9.tgz#bd96492c68822198f33e8a256061da3cf391f58f" - integrity sha512-G2EgeufBcYw27U4hhoIwFcgc1XU7TlXJ3mv04oOv1WCuo900U/anZSPzEqNjwdjgffkk2Gs0AN0dW1CKVLcG7w== - dependencies: - "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.22.5" - "@babel/generator" "^7.22.9" - "@babel/helper-compilation-targets" "^7.22.9" - "@babel/helper-module-transforms" "^7.22.9" - "@babel/helpers" "^7.22.6" - "@babel/parser" "^7.22.7" - "@babel/template" "^7.22.5" - "@babel/traverse" "^7.22.8" - "@babel/types" "^7.22.5" - convert-source-map "^1.7.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.2" - semver "^6.3.1" - -"@babel/eslint-parser@7.22.15": - version "7.22.15" - resolved "https://registry.npmmirror.com/@babel/eslint-parser/-/eslint-parser-7.22.15.tgz#263f059c476e29ca4972481a17b8b660cb025a34" - integrity sha512-yc8OOBIQk1EcRrpizuARSQS0TWAcOMpEJ1aafhNznaeYkeL+OhqnDObGFylB8ka8VFF/sZc+S4RzHyO+3LjQxg== - dependencies: - "@nicolo-ribaudo/eslint-scope-5-internals" "5.1.1-v1" - eslint-visitor-keys "^2.1.0" - semver "^6.3.1" - -"@babel/generator@^7.22.7", "@babel/generator@^7.22.9": - version "7.22.9" - resolved "https://registry.npmmirror.com/@babel/generator/-/generator-7.22.9.tgz#572ecfa7a31002fa1de2a9d91621fd895da8493d" - integrity sha512-KtLMbmicyuK2Ak/FTCJVbDnkN1SlT8/kceFTiuDiiRUUSMnHMidxSCdG4ndkTOHHpoomWe/4xkvHkEOncwjYIw== - dependencies: - "@babel/types" "^7.22.5" - "@jridgewell/gen-mapping" "^0.3.2" - "@jridgewell/trace-mapping" "^0.3.17" - jsesc "^2.5.1" - -"@babel/generator@^7.23.0": - version "7.23.0" - resolved "https://registry.npmmirror.com/@babel/generator/-/generator-7.23.0.tgz#df5c386e2218be505b34837acbcb874d7a983420" - integrity sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g== - dependencies: - "@babel/types" "^7.23.0" - "@jridgewell/gen-mapping" "^0.3.2" - "@jridgewell/trace-mapping" "^0.3.17" - jsesc "^2.5.1" - -"@babel/helper-annotate-as-pure@^7.16.0", "@babel/helper-annotate-as-pure@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz#e7f06737b197d580a01edf75d97e2c8be99d3882" - integrity sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-builder-binary-assignment-operator-visitor@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.5.tgz#a3f4758efdd0190d8927fcffd261755937c71878" - integrity sha512-m1EP3lVOPptR+2DwD125gziZNcmoNSHGmJROKoy87loWUQyJaVXDgpmruWqDARZSmtYQ+Dl25okU8+qhVzuykw== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-compilation-targets@^7.20.7", "@babel/helper-compilation-targets@^7.22.5", "@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.22.9": - version "7.22.9" - resolved "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.9.tgz#f9d0a7aaaa7cd32a3f31c9316a69f5a9bcacb892" - integrity sha512-7qYrNM6HjpnPHJbopxmb8hSPoZ0gsX8IvUS32JGVoy+pU9e5N0nLr1VjJoR6kA4d9dmGLxNYOjeB8sUDal2WMw== - dependencies: - "@babel/compat-data" "^7.22.9" - "@babel/helper-validator-option" "^7.22.5" - browserslist "^4.21.9" - lru-cache "^5.1.1" - semver "^6.3.1" - -"@babel/helper-compilation-targets@^7.22.15": - version "7.22.15" - resolved "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz#0698fc44551a26cf29f18d4662d5bf545a6cfc52" - integrity sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw== - dependencies: - "@babel/compat-data" "^7.22.9" - "@babel/helper-validator-option" "^7.22.15" - browserslist "^4.21.9" - lru-cache "^5.1.1" - semver "^6.3.1" - -"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.22.5", "@babel/helper-create-class-features-plugin@^7.22.9": - version "7.22.9" - resolved "https://registry.npmmirror.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.9.tgz#c36ea240bb3348f942f08b0fbe28d6d979fab236" - integrity sha512-Pwyi89uO4YrGKxL/eNJ8lfEH55DnRloGPOseaA8NFNL6jAUnn+KccaISiFazCj5IolPPDjGSdzQzXVzODVRqUQ== - dependencies: - "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-environment-visitor" "^7.22.5" - "@babel/helper-function-name" "^7.22.5" - "@babel/helper-member-expression-to-functions" "^7.22.5" - "@babel/helper-optimise-call-expression" "^7.22.5" - "@babel/helper-replace-supers" "^7.22.9" - "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.6" - semver "^6.3.1" - -"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.22.5": - version "7.22.9" - resolved "https://registry.npmmirror.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.9.tgz#9d8e61a8d9366fe66198f57c40565663de0825f6" - integrity sha512-+svjVa/tFwsNSG4NEy1h85+HQ5imbT92Q5/bgtS7P0GTQlP8WuFdqsiABmQouhiFGyV66oGxZFpeYHza1rNsKw== - dependencies: - "@babel/helper-annotate-as-pure" "^7.22.5" - regexpu-core "^5.3.1" - semver "^6.3.1" - -"@babel/helper-define-polyfill-provider@^0.4.1": - version "0.4.1" - resolved "https://registry.npmmirror.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.1.tgz#af1429c4a83ac316a6a8c2cc8ff45cb5d2998d3a" - integrity sha512-kX4oXixDxG197yhX+J3Wp+NpL2wuCFjWQAr6yX2jtCnflK9ulMI51ULFGIrWiX1jGfvAxdHp+XQCcP2bZGPs9A== - dependencies: - "@babel/helper-compilation-targets" "^7.22.6" - "@babel/helper-plugin-utils" "^7.22.5" - debug "^4.1.1" - lodash.debounce "^4.0.8" - resolve "^1.14.2" - -"@babel/helper-environment-visitor@^7.22.20": - version "7.22.20" - resolved "https://registry.npmmirror.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" - integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== - -"@babel/helper-environment-visitor@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz#f06dd41b7c1f44e1f8da6c4055b41ab3a09a7e98" - integrity sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q== - -"@babel/helper-function-name@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz#ede300828905bb15e582c037162f99d5183af1be" - integrity sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ== - dependencies: - "@babel/template" "^7.22.5" - "@babel/types" "^7.22.5" - -"@babel/helper-function-name@^7.23.0": - version "7.23.0" - resolved "https://registry.npmmirror.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" - integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== - dependencies: - "@babel/template" "^7.22.15" - "@babel/types" "^7.23.0" - -"@babel/helper-hoist-variables@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" - integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-member-expression-to-functions@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz#0a7c56117cad3372fbf8d2fb4bf8f8d64a1e76b2" - integrity sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.16.0", "@babel/helper-module-imports@^7.18.6", "@babel/helper-module-imports@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz#1a8f4c9f4027d23f520bd76b364d44434a72660c" - integrity sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-module-imports@^7.22.15": - version "7.22.15" - resolved "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0" - integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w== - dependencies: - "@babel/types" "^7.22.15" - -"@babel/helper-module-transforms@^7.22.5", "@babel/helper-module-transforms@^7.22.9": - version "7.22.9" - resolved "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.9.tgz#92dfcb1fbbb2bc62529024f72d942a8c97142129" - integrity sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ== - dependencies: - "@babel/helper-environment-visitor" "^7.22.5" - "@babel/helper-module-imports" "^7.22.5" - "@babel/helper-simple-access" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/helper-validator-identifier" "^7.22.5" - -"@babel/helper-module-transforms@^7.23.0": - version "7.23.0" - resolved "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz#3ec246457f6c842c0aee62a01f60739906f7047e" - integrity sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw== - dependencies: - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-module-imports" "^7.22.15" - "@babel/helper-simple-access" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/helper-validator-identifier" "^7.22.20" - -"@babel/helper-optimise-call-expression@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz#f21531a9ccbff644fdd156b4077c16ff0c3f609e" - integrity sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" - integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== - -"@babel/helper-remap-async-to-generator@^7.22.5": - version "7.22.9" - resolved "https://registry.npmmirror.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.9.tgz#53a25b7484e722d7efb9c350c75c032d4628de82" - integrity sha512-8WWC4oR4Px+tr+Fp0X3RHDVfINGpF3ad1HIbrc8A77epiR6eMMc6jsgozkzT2uDiOOdoS9cLIQ+XD2XvI2WSmQ== - dependencies: - "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-environment-visitor" "^7.22.5" - "@babel/helper-wrap-function" "^7.22.9" - -"@babel/helper-replace-supers@^7.22.5", "@babel/helper-replace-supers@^7.22.9": - version "7.22.9" - resolved "https://registry.npmmirror.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.9.tgz#cbdc27d6d8d18cd22c81ae4293765a5d9afd0779" - integrity sha512-LJIKvvpgPOPUThdYqcX6IXRuIcTkcAub0IaDRGCZH0p5GPUp7PhRU9QVgFcDDd51BaPkk77ZjqFwh6DZTAEmGg== - dependencies: - "@babel/helper-environment-visitor" "^7.22.5" - "@babel/helper-member-expression-to-functions" "^7.22.5" - "@babel/helper-optimise-call-expression" "^7.22.5" - -"@babel/helper-simple-access@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" - integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-skip-transparent-expression-wrappers@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz#007f15240b5751c537c40e77abb4e89eeaaa8847" - integrity sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-split-export-declaration@^7.22.6": - version "7.22.6" - resolved "https://registry.npmmirror.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" - integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-string-parser@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" - integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== - -"@babel/helper-validator-identifier@^7.22.20": - version "7.22.20" - resolved "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" - integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== - -"@babel/helper-validator-identifier@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz#9544ef6a33999343c8740fa51350f30eeaaaf193" - integrity sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ== - -"@babel/helper-validator-option@^7.22.15": - version "7.22.15" - resolved "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz#694c30dfa1d09a6534cdfcafbe56789d36aba040" - integrity sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA== - -"@babel/helper-validator-option@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz#de52000a15a177413c8234fa3a8af4ee8102d0ac" - integrity sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw== - -"@babel/helper-wrap-function@^7.22.9": - version "7.22.9" - resolved "https://registry.npmmirror.com/@babel/helper-wrap-function/-/helper-wrap-function-7.22.9.tgz#189937248c45b0182c1dcf32f3444ca153944cb9" - integrity sha512-sZ+QzfauuUEfxSEjKFmi3qDSHgLsTPK/pEpoD/qonZKOtTPTLbf59oabPQ4rKekt9lFcj/hTZaOhWwFYrgjk+Q== - dependencies: - "@babel/helper-function-name" "^7.22.5" - "@babel/template" "^7.22.5" - "@babel/types" "^7.22.5" - -"@babel/helpers@^7.22.6": - version "7.22.6" - resolved "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.22.6.tgz#8e61d3395a4f0c5a8060f309fb008200969b5ecd" - integrity sha512-YjDs6y/fVOYFV8hAf1rxd1QvR9wJe1pDBZ2AREKq/SDayfPzgk0PBnVuTCE5X1acEpMMNOVUqoe+OwiZGJ+OaA== - dependencies: - "@babel/template" "^7.22.5" - "@babel/traverse" "^7.22.6" - "@babel/types" "^7.22.5" - -"@babel/helpers@^7.23.2": - version "7.23.2" - resolved "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.23.2.tgz#2832549a6e37d484286e15ba36a5330483cac767" - integrity sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ== - dependencies: - "@babel/template" "^7.22.15" - "@babel/traverse" "^7.23.2" - "@babel/types" "^7.23.0" - -"@babel/highlight@^7.22.13": - version "7.22.20" - resolved "https://registry.npmmirror.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54" - integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg== - dependencies: - "@babel/helper-validator-identifier" "^7.22.20" - chalk "^2.4.2" - js-tokens "^4.0.0" - -"@babel/highlight@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/highlight/-/highlight-7.22.5.tgz#aa6c05c5407a67ebce408162b7ede789b4d22031" - integrity sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw== - dependencies: - "@babel/helper-validator-identifier" "^7.22.5" - chalk "^2.0.0" - js-tokens "^4.0.0" - -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.22.5", "@babel/parser@^7.22.7": - version "7.22.7" - resolved "https://registry.npmmirror.com/@babel/parser/-/parser-7.22.7.tgz#df8cf085ce92ddbdbf668a7f186ce848c9036cae" - integrity sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q== - -"@babel/parser@^7.22.15", "@babel/parser@^7.23.0": - version "7.23.0" - resolved "https://registry.npmmirror.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719" - integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw== - -"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.5.tgz#87245a21cd69a73b0b81bcda98d443d6df08f05e" - integrity sha512-NP1M5Rf+u2Gw9qfSO4ihjcTGW5zXTi36ITLd4/EoAcEhIZ0yjMqmftDNl3QC19CX7olhrjpyU454g/2W7X0jvQ== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.5.tgz#fef09f9499b1f1c930da8a0c419db42167d792ca" - integrity sha512-31Bb65aZaUwqCbWMnZPduIZxCBngHFlzyN6Dq6KAJjtx+lx6ohKHubc61OomYi7XwVD4Ol0XCVz4h+pYFR048g== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" - "@babel/plugin-transform-optional-chaining" "^7.22.5" - -"@babel/plugin-external-helpers@^7.18.6": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-external-helpers/-/plugin-external-helpers-7.22.5.tgz#92b0705b74756123f289388320e0e12c407fdf9a" - integrity sha512-ngnNEWxmykPk82mH4ajZT0qTztr3Je6hrMuKAslZVM8G1YZTENJSYwrIGtt6KOtznug3exmAtF4so/nPqJuA4A== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-proposal-class-properties@^7.18.6": - version "7.18.6" - resolved "https://registry.npmmirror.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz#b110f59741895f7ec21a6fff696ec46265c446a3" - integrity sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-proposal-object-rest-spread@^7.20.7": - version "7.20.7" - resolved "https://registry.npmmirror.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz#aa662940ef425779c75534a5c41e9d936edc390a" - integrity sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg== - dependencies: - "@babel/compat-data" "^7.20.5" - "@babel/helper-compilation-targets" "^7.20.7" - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.20.7" - -"@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2": - version "7.21.0-placeholder-for-preset-env.2" - resolved "https://registry.npmmirror.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz#7844f9289546efa9febac2de4cfe358a050bd703" - integrity sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w== - -"@babel/plugin-proposal-unicode-property-regex@^7.4.4": - version "7.18.6" - resolved "https://registry.npmmirror.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz#af613d2cd5e643643b65cded64207b15c85cb78e" - integrity sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-syntax-async-generators@^7.8.4": - version "7.8.4" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" - integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-bigint@^7.8.3": - version "7.8.3" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" - integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-class-properties@^7.12.13", "@babel/plugin-syntax-class-properties@^7.8.3": - version "7.12.13" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" - integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== - dependencies: - "@babel/helper-plugin-utils" "^7.12.13" - -"@babel/plugin-syntax-class-static-block@^7.14.5": - version "7.14.5" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" - integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-dynamic-import@^7.8.3": - version "7.8.3" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" - integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-export-namespace-from@^7.8.3": - version "7.8.3" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz#028964a9ba80dbc094c915c487ad7c4e7a66465a" - integrity sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-syntax-import-assertions@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.22.5.tgz#07d252e2aa0bc6125567f742cd58619cb14dce98" - integrity sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-syntax-import-attributes@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.5.tgz#ab840248d834410b829f569f5262b9e517555ecb" - integrity sha512-KwvoWDeNKPETmozyFE0P2rOLqh39EoQHNjqizrI5B8Vt0ZNS7M56s7dAiAqbYfiAYOuIzIh96z3iR2ktgu3tEg== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-syntax-import-meta@^7.10.4", "@babel/plugin-syntax-import-meta@^7.8.3": - version "7.10.4" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" - integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-json-strings@^7.8.3": - version "7.8.3" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" - integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-jsx@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz#a6b68e84fb76e759fc3b93e901876ffabbe1d918" - integrity sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-syntax-logical-assignment-operators@^7.10.4", "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": - version "7.10.4" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" - integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": - version "7.8.3" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" - integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-numeric-separator@^7.10.4", "@babel/plugin-syntax-numeric-separator@^7.8.3": - version "7.10.4" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" - integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-object-rest-spread@^7.8.3": - version "7.8.3" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" - integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-catch-binding@^7.8.3": - version "7.8.3" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" - integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-chaining@^7.8.3": - version "7.8.3" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" - integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-private-property-in-object@^7.14.5": - version "7.14.5" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" - integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-top-level-await@^7.14.5", "@babel/plugin-syntax-top-level-await@^7.8.3": - version "7.14.5" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" - integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-typescript@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz#aac8d383b062c5072c647a31ef990c1d0af90272" - integrity sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-syntax-unicode-sets-regex@^7.18.6": - version "7.18.6" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz#d49a3b3e6b52e5be6740022317580234a6a47357" - integrity sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-arrow-functions@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz#e5ba566d0c58a5b2ba2a8b795450641950b71958" - integrity sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-async-generator-functions@^7.22.7": - version "7.22.7" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.7.tgz#053e76c0a903b72b573cb1ab7d6882174d460a1b" - integrity sha512-7HmE7pk/Fmke45TODvxvkxRMV9RazV+ZZzhOL9AG8G29TLrr3jkjwF7uJfxZ30EoXpO+LJkq4oA8NjO2DTnEDg== - dependencies: - "@babel/helper-environment-visitor" "^7.22.5" - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-remap-async-to-generator" "^7.22.5" - "@babel/plugin-syntax-async-generators" "^7.8.4" - -"@babel/plugin-transform-async-to-generator@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz#c7a85f44e46f8952f6d27fe57c2ed3cc084c3775" - integrity sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ== - dependencies: - "@babel/helper-module-imports" "^7.22.5" - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-remap-async-to-generator" "^7.22.5" - -"@babel/plugin-transform-block-scoped-functions@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz#27978075bfaeb9fa586d3cb63a3d30c1de580024" - integrity sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-block-scoping@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.5.tgz#8bfc793b3a4b2742c0983fadc1480d843ecea31b" - integrity sha512-EcACl1i5fSQ6bt+YGuU/XGCeZKStLmyVGytWkpyhCLeQVA0eu6Wtiw92V+I1T/hnezUv7j74dA/Ro69gWcU+hg== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-class-properties@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.5.tgz#97a56e31ad8c9dc06a0b3710ce7803d5a48cca77" - integrity sha512-nDkQ0NfkOhPTq8YCLiWNxp1+f9fCobEjCb0n8WdbNUBc4IB5V7P1QnX9IjpSoquKrXF5SKojHleVNs2vGeHCHQ== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.22.5" - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-class-static-block@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.5.tgz#3e40c46f048403472d6f4183116d5e46b1bff5ba" - integrity sha512-SPToJ5eYZLxlnp1UzdARpOGeC2GbHvr9d/UV0EukuVx8atktg194oe+C5BqQ8jRTkgLRVOPYeXRSBg1IlMoVRA== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.22.5" - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/plugin-syntax-class-static-block" "^7.14.5" - -"@babel/plugin-transform-classes@^7.22.6": - version "7.22.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.6.tgz#e04d7d804ed5b8501311293d1a0e6d43e94c3363" - integrity sha512-58EgM6nuPNG6Py4Z3zSuu0xWu2VfodiMi72Jt5Kj2FECmaYk1RrTXA45z6KBFsu9tRgwQDwIiY4FXTt+YsSFAQ== - dependencies: - "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-compilation-targets" "^7.22.6" - "@babel/helper-environment-visitor" "^7.22.5" - "@babel/helper-function-name" "^7.22.5" - "@babel/helper-optimise-call-expression" "^7.22.5" - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-replace-supers" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.6" - globals "^11.1.0" - -"@babel/plugin-transform-computed-properties@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz#cd1e994bf9f316bd1c2dafcd02063ec261bb3869" - integrity sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/template" "^7.22.5" - -"@babel/plugin-transform-destructuring@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.5.tgz#d3aca7438f6c26c78cdd0b0ba920a336001b27cc" - integrity sha512-GfqcFuGW8vnEqTUBM7UtPd5A4q797LTvvwKxXTgRsFjoqaJiEg9deBG6kWeQYkVEL569NpnmpC0Pkr/8BLKGnQ== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-dotall-regex@^7.22.5", "@babel/plugin-transform-dotall-regex@^7.4.4": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.22.5.tgz#dbb4f0e45766eb544e193fb00e65a1dd3b2a4165" - integrity sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.22.5" - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-duplicate-keys@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.22.5.tgz#b6e6428d9416f5f0bba19c70d1e6e7e0b88ab285" - integrity sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-dynamic-import@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.5.tgz#d6908a8916a810468c4edff73b5b75bda6ad393e" - integrity sha512-0MC3ppTB1AMxd8fXjSrbPa7LT9hrImt+/fcj+Pg5YMD7UQyWp/02+JWpdnCymmsXwIx5Z+sYn1bwCn4ZJNvhqQ== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/plugin-syntax-dynamic-import" "^7.8.3" - -"@babel/plugin-transform-exponentiation-operator@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.22.5.tgz#402432ad544a1f9a480da865fda26be653e48f6a" - integrity sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g== - dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor" "^7.22.5" - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-export-namespace-from@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.5.tgz#57c41cb1d0613d22f548fddd8b288eedb9973a5b" - integrity sha512-X4hhm7FRnPgd4nDA4b/5V280xCx6oL7Oob5+9qVS5C13Zq4bh1qq7LU0GgRU6b5dBWBvhGaXYVB4AcN6+ol6vg== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/plugin-syntax-export-namespace-from" "^7.8.3" - -"@babel/plugin-transform-for-of@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.5.tgz#ab1b8a200a8f990137aff9a084f8de4099ab173f" - integrity sha512-3kxQjX1dU9uudwSshyLeEipvrLjBCVthCgeTp6CzE/9JYrlAIaeekVxRpCWsDDfYTfRZRoCeZatCQvwo+wvK8A== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-function-name@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz#935189af68b01898e0d6d99658db6b164205c143" - integrity sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg== - dependencies: - "@babel/helper-compilation-targets" "^7.22.5" - "@babel/helper-function-name" "^7.22.5" - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-json-strings@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.5.tgz#14b64352fdf7e1f737eed68de1a1468bd2a77ec0" - integrity sha512-DuCRB7fu8MyTLbEQd1ew3R85nx/88yMoqo2uPSjevMj3yoN7CDM8jkgrY0wmVxfJZyJ/B9fE1iq7EQppWQmR5A== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/plugin-syntax-json-strings" "^7.8.3" - -"@babel/plugin-transform-literals@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz#e9341f4b5a167952576e23db8d435849b1dd7920" - integrity sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-logical-assignment-operators@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.5.tgz#66ae5f068fd5a9a5dc570df16f56c2a8462a9d6c" - integrity sha512-MQQOUW1KL8X0cDWfbwYP+TbVbZm16QmQXJQ+vndPtH/BoO0lOKpVoEDMI7+PskYxH+IiE0tS8xZye0qr1lGzSA== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" - -"@babel/plugin-transform-member-expression-literals@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz#4fcc9050eded981a468347dd374539ed3e058def" - integrity sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-modules-amd@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.22.5.tgz#4e045f55dcf98afd00f85691a68fc0780704f526" - integrity sha512-R+PTfLTcYEmb1+kK7FNkhQ1gP4KgjpSO6HfH9+f8/yfp2Nt3ggBjiVpRwmwTlfqZLafYKJACy36yDXlEmI9HjQ== - dependencies: - "@babel/helper-module-transforms" "^7.22.5" - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-modules-commonjs@7.23.0": - version "7.23.0" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.0.tgz#b3dba4757133b2762c00f4f94590cf6d52602481" - integrity sha512-32Xzss14/UVc7k9g775yMIvkVK8xwKE0DPdP5JTapr3+Z9w4tzeOuLNY6BXDQR6BdnzIlXnCGAzsk/ICHBLVWQ== - dependencies: - "@babel/helper-module-transforms" "^7.23.0" - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-simple-access" "^7.22.5" - -"@babel/plugin-transform-modules-commonjs@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.5.tgz#7d9875908d19b8c0536085af7b053fd5bd651bfa" - integrity sha512-B4pzOXj+ONRmuaQTg05b3y/4DuFz3WcCNAXPLb2Q0GT0TrGKGxNKV4jwsXts+StaM0LQczZbOpj8o1DLPDJIiA== - dependencies: - "@babel/helper-module-transforms" "^7.22.5" - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-simple-access" "^7.22.5" - -"@babel/plugin-transform-modules-systemjs@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.5.tgz#18c31410b5e579a0092638f95c896c2a98a5d496" - integrity sha512-emtEpoaTMsOs6Tzz+nbmcePl6AKVtS1yC4YNAeMun9U8YCsgadPNxnOPQ8GhHFB2qdx+LZu9LgoC0Lthuu05DQ== - dependencies: - "@babel/helper-hoist-variables" "^7.22.5" - "@babel/helper-module-transforms" "^7.22.5" - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-validator-identifier" "^7.22.5" - -"@babel/plugin-transform-modules-umd@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.22.5.tgz#4694ae40a87b1745e3775b6a7fe96400315d4f98" - integrity sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ== - dependencies: - "@babel/helper-module-transforms" "^7.22.5" - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-named-capturing-groups-regex@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz#67fe18ee8ce02d57c855185e27e3dc959b2e991f" - integrity sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.22.5" - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-new-target@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.5.tgz#1b248acea54ce44ea06dfd37247ba089fcf9758d" - integrity sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-nullish-coalescing-operator@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.5.tgz#f8872c65776e0b552e0849d7596cddd416c3e381" - integrity sha512-6CF8g6z1dNYZ/VXok5uYkkBBICHZPiGEl7oDnAx2Mt1hlHVHOSIKWJaXHjQJA5VB43KZnXZDIexMchY4y2PGdA== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - -"@babel/plugin-transform-numeric-separator@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.5.tgz#57226a2ed9e512b9b446517ab6fa2d17abb83f58" - integrity sha512-NbslED1/6M+sXiwwtcAB/nieypGw02Ejf4KtDeMkCEpP6gWFMX1wI9WKYua+4oBneCCEmulOkRpwywypVZzs/g== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/plugin-syntax-numeric-separator" "^7.10.4" - -"@babel/plugin-transform-object-rest-spread@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.5.tgz#9686dc3447df4753b0b2a2fae7e8bc33cdc1f2e1" - integrity sha512-Kk3lyDmEslH9DnvCDA1s1kkd3YWQITiBOHngOtDL9Pt6BZjzqb6hiOlb8VfjiiQJ2unmegBqZu0rx5RxJb5vmQ== - dependencies: - "@babel/compat-data" "^7.22.5" - "@babel/helper-compilation-targets" "^7.22.5" - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.22.5" - -"@babel/plugin-transform-object-super@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz#794a8d2fcb5d0835af722173c1a9d704f44e218c" - integrity sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-replace-supers" "^7.22.5" - -"@babel/plugin-transform-optional-catch-binding@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.5.tgz#842080be3076703be0eaf32ead6ac8174edee333" - integrity sha512-pH8orJahy+hzZje5b8e2QIlBWQvGpelS76C63Z+jhZKsmzfNaPQ+LaW6dcJ9bxTpo1mtXbgHwy765Ro3jftmUg== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" - -"@babel/plugin-transform-optional-chaining@^7.22.5", "@babel/plugin-transform-optional-chaining@^7.22.6": - version "7.22.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.6.tgz#4bacfe37001fe1901117672875e931d439811564" - integrity sha512-Vd5HiWml0mDVtcLHIoEU5sw6HOUW/Zk0acLs/SAeuLzkGNOPc9DB4nkUajemhCmTIz3eiaKREZn2hQQqF79YTg== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" - -"@babel/plugin-transform-parameters@^7.20.7", "@babel/plugin-transform-parameters@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.5.tgz#c3542dd3c39b42c8069936e48717a8d179d63a18" - integrity sha512-AVkFUBurORBREOmHRKo06FjHYgjrabpdqRSwq6+C7R5iTCZOsM4QbcB27St0a4U6fffyAOqh3s/qEfybAhfivg== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-private-methods@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.5.tgz#21c8af791f76674420a147ae62e9935d790f8722" - integrity sha512-PPjh4gyrQnGe97JTalgRGMuU4icsZFnWkzicB/fUtzlKUqvsWBKEpPPfr5a2JiyirZkHxnAqkQMO5Z5B2kK3fA== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.22.5" - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-private-property-in-object@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.5.tgz#07a77f28cbb251546a43d175a1dda4cf3ef83e32" - integrity sha512-/9xnaTTJcVoBtSSmrVyhtSvO3kbqS2ODoh2juEU72c3aYonNF0OMGiaz2gjukyKM2wBBYJP38S4JiE0Wfb5VMQ== - dependencies: - "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-create-class-features-plugin" "^7.22.5" - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/plugin-syntax-private-property-in-object" "^7.14.5" - -"@babel/plugin-transform-property-literals@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz#b5ddabd73a4f7f26cd0e20f5db48290b88732766" - integrity sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-react-display-name@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.22.5.tgz#3c4326f9fce31c7968d6cb9debcaf32d9e279a2b" - integrity sha512-PVk3WPYudRF5z4GKMEYUrLjPl38fJSKNaEOkFuoprioowGuWN6w2RKznuFNSlJx7pzzXXStPUnNSOEO0jL5EVw== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-react-jsx-development@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.22.5.tgz#e716b6edbef972a92165cd69d92f1255f7e73e87" - integrity sha512-bDhuzwWMuInwCYeDeMzyi7TaBgRQei6DqxhbyniL7/VG4RSS7HtSL2QbY4eESy1KJqlWt8g3xeEBGPuo+XqC8A== - dependencies: - "@babel/plugin-transform-react-jsx" "^7.22.5" - -"@babel/plugin-transform-react-jsx-self@^7.21.0": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.22.5.tgz#ca2fdc11bc20d4d46de01137318b13d04e481d8e" - integrity sha512-nTh2ogNUtxbiSbxaT4Ds6aXnXEipHweN9YRgOX/oNXdf0cCrGn/+2LozFa3lnPV5D90MkjhgckCPBrsoSc1a7g== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-react-jsx-source@^7.19.6": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.22.5.tgz#49af1615bfdf6ed9d3e9e43e425e0b2b65d15b6c" - integrity sha512-yIiRO6yobeEIaI0RTbIr8iAK9FcBHLtZq0S89ZPjDLQXBA4xvghaKqI0etp/tF3htTM0sazJKKLz9oEiGRtu7w== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-react-jsx@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.22.5.tgz#932c291eb6dd1153359e2a90cb5e557dcf068416" - integrity sha512-rog5gZaVbUip5iWDMTYbVM15XQq+RkUKhET/IHR6oizR+JEoN6CAfTTuHcK4vwUyzca30qqHqEpzBOnaRMWYMA== - dependencies: - "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-module-imports" "^7.22.5" - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/plugin-syntax-jsx" "^7.22.5" - "@babel/types" "^7.22.5" - -"@babel/plugin-transform-react-pure-annotations@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.22.5.tgz#1f58363eef6626d6fa517b95ac66fe94685e32c0" - integrity sha512-gP4k85wx09q+brArVinTXhWiyzLl9UpmGva0+mWyKxk6JZequ05x3eUcIUE+FyttPKJFRRVtAvQaJ6YF9h1ZpA== - dependencies: - "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-regenerator@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.5.tgz#cd8a68b228a5f75fa01420e8cc2fc400f0fc32aa" - integrity sha512-rR7KePOE7gfEtNTh9Qw+iO3Q/e4DEsoQ+hdvM6QUDH7JRJ5qxq5AA52ZzBWbI5i9lfNuvySgOGP8ZN7LAmaiPw== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - regenerator-transform "^0.15.1" - -"@babel/plugin-transform-reserved-words@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.22.5.tgz#832cd35b81c287c4bcd09ce03e22199641f964fb" - integrity sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-shorthand-properties@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz#6e277654be82b5559fc4b9f58088507c24f0c624" - integrity sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-spread@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz#6487fd29f229c95e284ba6c98d65eafb893fea6b" - integrity sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" - -"@babel/plugin-transform-sticky-regex@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.22.5.tgz#295aba1595bfc8197abd02eae5fc288c0deb26aa" - integrity sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-template-literals@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz#8f38cf291e5f7a8e60e9f733193f0bcc10909bff" - integrity sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-typeof-symbol@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.22.5.tgz#5e2ba478da4b603af8673ff7c54f75a97b716b34" - integrity sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-typescript@^7.22.5": - version "7.22.9" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.22.9.tgz#91e08ad1eb1028ecc62662a842e93ecfbf3c7234" - integrity sha512-BnVR1CpKiuD0iobHPaM1iLvcwPYN2uVFAqoLVSpEDKWuOikoCv5HbKLxclhKYUXlWkX86DoZGtqI4XhbOsyrMg== - dependencies: - "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-create-class-features-plugin" "^7.22.9" - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/plugin-syntax-typescript" "^7.22.5" - -"@babel/plugin-transform-unicode-escapes@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.5.tgz#ce0c248522b1cb22c7c992d88301a5ead70e806c" - integrity sha512-biEmVg1IYB/raUO5wT1tgfacCef15Fbzhkx493D3urBI++6hpJ+RFG4SrWMn0NEZLfvilqKf3QDrRVZHo08FYg== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-unicode-property-regex@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.5.tgz#098898f74d5c1e86660dc112057b2d11227f1c81" - integrity sha512-HCCIb+CbJIAE6sXn5CjFQXMwkCClcOfPCzTlilJ8cUatfzwHlWQkbtV0zD338u9dZskwvuOYTuuaMaA8J5EI5A== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.22.5" - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-unicode-regex@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz#ce7e7bb3ef208c4ff67e02a22816656256d7a183" - integrity sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.22.5" - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-unicode-sets-regex@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.5.tgz#77788060e511b708ffc7d42fdfbc5b37c3004e91" - integrity sha512-lhMfi4FC15j13eKrh3DnYHjpGj6UKQHtNKTbtc1igvAhRy4+kLhV07OpLcsN0VgDEw/MjAvJO4BdMJsHwMhzCg== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.22.5" - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/preset-env@^7.20.2": - version "7.22.9" - resolved "https://registry.npmmirror.com/@babel/preset-env/-/preset-env-7.22.9.tgz#57f17108eb5dfd4c5c25a44c1977eba1df310ac7" - integrity sha512-wNi5H/Emkhll/bqPjsjQorSykrlfY5OWakd6AulLvMEytpKasMVUpVy8RL4qBIBs5Ac6/5i0/Rv0b/Fg6Eag/g== - dependencies: - "@babel/compat-data" "^7.22.9" - "@babel/helper-compilation-targets" "^7.22.9" - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-validator-option" "^7.22.5" - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.22.5" - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.22.5" - "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2" - "@babel/plugin-syntax-async-generators" "^7.8.4" - "@babel/plugin-syntax-class-properties" "^7.12.13" - "@babel/plugin-syntax-class-static-block" "^7.14.5" - "@babel/plugin-syntax-dynamic-import" "^7.8.3" - "@babel/plugin-syntax-export-namespace-from" "^7.8.3" - "@babel/plugin-syntax-import-assertions" "^7.22.5" - "@babel/plugin-syntax-import-attributes" "^7.22.5" - "@babel/plugin-syntax-import-meta" "^7.10.4" - "@babel/plugin-syntax-json-strings" "^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-syntax-numeric-separator" "^7.10.4" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" - "@babel/plugin-syntax-private-property-in-object" "^7.14.5" - "@babel/plugin-syntax-top-level-await" "^7.14.5" - "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" - "@babel/plugin-transform-arrow-functions" "^7.22.5" - "@babel/plugin-transform-async-generator-functions" "^7.22.7" - "@babel/plugin-transform-async-to-generator" "^7.22.5" - "@babel/plugin-transform-block-scoped-functions" "^7.22.5" - "@babel/plugin-transform-block-scoping" "^7.22.5" - "@babel/plugin-transform-class-properties" "^7.22.5" - "@babel/plugin-transform-class-static-block" "^7.22.5" - "@babel/plugin-transform-classes" "^7.22.6" - "@babel/plugin-transform-computed-properties" "^7.22.5" - "@babel/plugin-transform-destructuring" "^7.22.5" - "@babel/plugin-transform-dotall-regex" "^7.22.5" - "@babel/plugin-transform-duplicate-keys" "^7.22.5" - "@babel/plugin-transform-dynamic-import" "^7.22.5" - "@babel/plugin-transform-exponentiation-operator" "^7.22.5" - "@babel/plugin-transform-export-namespace-from" "^7.22.5" - "@babel/plugin-transform-for-of" "^7.22.5" - "@babel/plugin-transform-function-name" "^7.22.5" - "@babel/plugin-transform-json-strings" "^7.22.5" - "@babel/plugin-transform-literals" "^7.22.5" - "@babel/plugin-transform-logical-assignment-operators" "^7.22.5" - "@babel/plugin-transform-member-expression-literals" "^7.22.5" - "@babel/plugin-transform-modules-amd" "^7.22.5" - "@babel/plugin-transform-modules-commonjs" "^7.22.5" - "@babel/plugin-transform-modules-systemjs" "^7.22.5" - "@babel/plugin-transform-modules-umd" "^7.22.5" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.22.5" - "@babel/plugin-transform-new-target" "^7.22.5" - "@babel/plugin-transform-nullish-coalescing-operator" "^7.22.5" - "@babel/plugin-transform-numeric-separator" "^7.22.5" - "@babel/plugin-transform-object-rest-spread" "^7.22.5" - "@babel/plugin-transform-object-super" "^7.22.5" - "@babel/plugin-transform-optional-catch-binding" "^7.22.5" - "@babel/plugin-transform-optional-chaining" "^7.22.6" - "@babel/plugin-transform-parameters" "^7.22.5" - "@babel/plugin-transform-private-methods" "^7.22.5" - "@babel/plugin-transform-private-property-in-object" "^7.22.5" - "@babel/plugin-transform-property-literals" "^7.22.5" - "@babel/plugin-transform-regenerator" "^7.22.5" - "@babel/plugin-transform-reserved-words" "^7.22.5" - "@babel/plugin-transform-shorthand-properties" "^7.22.5" - "@babel/plugin-transform-spread" "^7.22.5" - "@babel/plugin-transform-sticky-regex" "^7.22.5" - "@babel/plugin-transform-template-literals" "^7.22.5" - "@babel/plugin-transform-typeof-symbol" "^7.22.5" - "@babel/plugin-transform-unicode-escapes" "^7.22.5" - "@babel/plugin-transform-unicode-property-regex" "^7.22.5" - "@babel/plugin-transform-unicode-regex" "^7.22.5" - "@babel/plugin-transform-unicode-sets-regex" "^7.22.5" - "@babel/preset-modules" "^0.1.5" - "@babel/types" "^7.22.5" - babel-plugin-polyfill-corejs2 "^0.4.4" - babel-plugin-polyfill-corejs3 "^0.8.2" - babel-plugin-polyfill-regenerator "^0.5.1" - core-js-compat "^3.31.0" - semver "^6.3.1" - -"@babel/preset-modules@^0.1.5": - version "0.1.5" - resolved "https://registry.npmmirror.com/@babel/preset-modules/-/preset-modules-0.1.5.tgz#ef939d6e7f268827e1841638dc6ff95515e115d9" - integrity sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" - "@babel/plugin-transform-dotall-regex" "^7.4.4" - "@babel/types" "^7.4.4" - esutils "^2.0.2" - -"@babel/preset-react@^7.18.6": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/preset-react/-/preset-react-7.22.5.tgz#c4d6058fbf80bccad02dd8c313a9aaa67e3c3dd6" - integrity sha512-M+Is3WikOpEJHgR385HbuCITPTaPRaNkibTEa9oiofmJvIsrceb4yp9RL9Kb+TE8LznmeyZqpP+Lopwcx59xPQ== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-validator-option" "^7.22.5" - "@babel/plugin-transform-react-display-name" "^7.22.5" - "@babel/plugin-transform-react-jsx" "^7.22.5" - "@babel/plugin-transform-react-jsx-development" "^7.22.5" - "@babel/plugin-transform-react-pure-annotations" "^7.22.5" - -"@babel/preset-typescript@^7.21.0": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/preset-typescript/-/preset-typescript-7.22.5.tgz#16367d8b01d640e9a507577ed4ee54e0101e51c8" - integrity sha512-YbPaal9LxztSGhmndR46FmAbkJ/1fAsw293tSU+I5E5h+cnJ3d4GTwyUgGYmOXJYdGA+uNePle4qbaRzj2NISQ== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-validator-option" "^7.22.5" - "@babel/plugin-syntax-jsx" "^7.22.5" - "@babel/plugin-transform-modules-commonjs" "^7.22.5" - "@babel/plugin-transform-typescript" "^7.22.5" - -"@babel/regjsgen@^0.8.0": - version "0.8.0" - resolved "https://registry.npmmirror.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" - integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== - -"@babel/runtime@7.23.2": - version "7.23.2" - resolved "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.23.2.tgz#062b0ac103261d68a966c4c7baf2ae3e62ec3885" - integrity sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg== - dependencies: - regenerator-runtime "^0.14.0" - -"@babel/runtime@^7.0.0", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.4", "@babel/runtime@^7.10.5", "@babel/runtime@^7.11.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.16.7", "@babel/runtime@^7.18.0", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.0", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.0", "@babel/runtime@^7.22.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.7.7", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": - version "7.22.6" - resolved "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.22.6.tgz#57d64b9ae3cff1d67eb067ae117dac087f5bd438" - integrity sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ== - dependencies: - regenerator-runtime "^0.13.11" - -"@babel/runtime@^7.23.2", "@babel/runtime@^7.23.4": - version "7.23.5" - resolved "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.23.5.tgz#11edb98f8aeec529b82b211028177679144242db" - integrity sha512-NdUTHcPe4C99WxPub+K9l9tK5/lV4UXIoaHSYgzco9BCyjKAAwzdBI+wWtYqHt7LJdbo74ZjRPJgzVweq1sz0w== - dependencies: - regenerator-runtime "^0.14.0" - -"@babel/template@^7.22.15": - version "7.22.15" - resolved "https://registry.npmmirror.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" - integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w== - dependencies: - "@babel/code-frame" "^7.22.13" - "@babel/parser" "^7.22.15" - "@babel/types" "^7.22.15" - -"@babel/template@^7.22.5", "@babel/template@^7.3.3": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/template/-/template-7.22.5.tgz#0c8c4d944509875849bd0344ff0050756eefc6ec" - integrity sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw== - dependencies: - "@babel/code-frame" "^7.22.5" - "@babel/parser" "^7.22.5" - "@babel/types" "^7.22.5" - -"@babel/traverse@^7.21.2", "@babel/traverse@^7.22.6", "@babel/traverse@^7.22.8", "@babel/traverse@^7.4.5": - version "7.22.8" - resolved "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.22.8.tgz#4d4451d31bc34efeae01eac222b514a77aa4000e" - integrity sha512-y6LPR+wpM2I3qJrsheCTwhIinzkETbplIgPBbwvqPKc+uljeA5gP+3nP8irdYt1mjQaDnlIcG+dw8OjAco4GXw== - dependencies: - "@babel/code-frame" "^7.22.5" - "@babel/generator" "^7.22.7" - "@babel/helper-environment-visitor" "^7.22.5" - "@babel/helper-function-name" "^7.22.5" - "@babel/helper-hoist-variables" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/parser" "^7.22.7" - "@babel/types" "^7.22.5" - debug "^4.1.0" - globals "^11.1.0" - -"@babel/traverse@^7.23.2": - version "7.23.2" - resolved "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8" - integrity sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw== - dependencies: - "@babel/code-frame" "^7.22.13" - "@babel/generator" "^7.23.0" - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-function-name" "^7.23.0" - "@babel/helper-hoist-variables" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/parser" "^7.23.0" - "@babel/types" "^7.23.0" - debug "^4.1.0" - globals "^11.1.0" - -"@babel/types@^7.0.0", "@babel/types@^7.20.0", "@babel/types@^7.20.7", "@babel/types@^7.22.5", "@babel/types@^7.3.3", "@babel/types@^7.4.4": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/types/-/types-7.22.5.tgz#cd93eeaab025880a3a47ec881f4b096a5b786fbe" - integrity sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA== - dependencies: - "@babel/helper-string-parser" "^7.22.5" - "@babel/helper-validator-identifier" "^7.22.5" - to-fast-properties "^2.0.0" - -"@babel/types@^7.22.15", "@babel/types@^7.23.0": - version "7.23.0" - resolved "https://registry.npmmirror.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb" - integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg== - dependencies: - "@babel/helper-string-parser" "^7.22.5" - "@babel/helper-validator-identifier" "^7.22.20" - to-fast-properties "^2.0.0" - -"@bloomberg/record-tuple-polyfill@0.0.4": - version "0.0.4" - resolved "https://registry.npmmirror.com/@bloomberg/record-tuple-polyfill/-/record-tuple-polyfill-0.0.4.tgz#9ef3df44e472ceb9a0a2010d858a526f2021fefa" - integrity sha512-h0OYmPR3A5Dfbetra/GzxBAzQk8sH7LhRkRUTdagX6nrtlUgJGYCTv4bBK33jsTQw9HDd8PE2x1Ma+iRKEDUsw== - -"@chenshuai2144/sketch-color@^1.0.7", "@chenshuai2144/sketch-color@^1.0.8": - version "1.0.9" - resolved "https://registry.npmmirror.com/@chenshuai2144/sketch-color/-/sketch-color-1.0.9.tgz#41144e2d9656bff2143516d4e8e62e5003bd466a" - integrity sha512-obzSy26cb7Pm7OprWyVpgMpIlrZpZ0B7vbrU0RMbvRg0YAI890S5Xy02Aj1Nhl4+KTbi1lVYHt6HQP8Hm9s+1w== - dependencies: - reactcss "^1.2.3" - tinycolor2 "^1.4.2" - -"@csstools/postcss-color-function@^1.1.0": - version "1.1.1" - resolved "https://registry.npmmirror.com/@csstools/postcss-color-function/-/postcss-color-function-1.1.1.tgz#2bd36ab34f82d0497cfacdc9b18d34b5e6f64b6b" - integrity sha512-Bc0f62WmHdtRDjf5f3e2STwRAl89N2CLb+9iAwzrv4L2hncrbDwnQD9PCq0gtAt7pOI2leIV08HIBUd4jxD8cw== - dependencies: - "@csstools/postcss-progressive-custom-properties" "^1.1.0" - postcss-value-parser "^4.2.0" - -"@csstools/postcss-font-format-keywords@^1.0.0": - version "1.0.1" - resolved "https://registry.npmmirror.com/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-1.0.1.tgz#677b34e9e88ae997a67283311657973150e8b16a" - integrity sha512-ZgrlzuUAjXIOc2JueK0X5sZDjCtgimVp/O5CEqTcs5ShWBa6smhWYbS0x5cVc/+rycTDbjjzoP0KTDnUneZGOg== - dependencies: - postcss-value-parser "^4.2.0" - -"@csstools/postcss-hwb-function@^1.0.0": - version "1.0.2" - resolved "https://registry.npmmirror.com/@csstools/postcss-hwb-function/-/postcss-hwb-function-1.0.2.tgz#ab54a9fce0ac102c754854769962f2422ae8aa8b" - integrity sha512-YHdEru4o3Rsbjmu6vHy4UKOXZD+Rn2zmkAmLRfPet6+Jz4Ojw8cbWxe1n42VaXQhD3CQUXXTooIy8OkVbUcL+w== - dependencies: - postcss-value-parser "^4.2.0" - -"@csstools/postcss-ic-unit@^1.0.0": - version "1.0.1" - resolved "https://registry.npmmirror.com/@csstools/postcss-ic-unit/-/postcss-ic-unit-1.0.1.tgz#28237d812a124d1a16a5acc5c3832b040b303e58" - integrity sha512-Ot1rcwRAaRHNKC9tAqoqNZhjdYBzKk1POgWfhN4uCOE47ebGcLRqXjKkApVDpjifL6u2/55ekkpnFcp+s/OZUw== - dependencies: - "@csstools/postcss-progressive-custom-properties" "^1.1.0" - postcss-value-parser "^4.2.0" - -"@csstools/postcss-is-pseudo-class@^2.0.2": - version "2.0.7" - resolved "https://registry.npmmirror.com/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-2.0.7.tgz#846ae6c0d5a1eaa878fce352c544f9c295509cd1" - integrity sha512-7JPeVVZHd+jxYdULl87lvjgvWldYu+Bc62s9vD/ED6/QTGjy0jy0US/f6BG53sVMTBJ1lzKZFpYmofBN9eaRiA== - dependencies: - "@csstools/selector-specificity" "^2.0.0" - postcss-selector-parser "^6.0.10" - -"@csstools/postcss-normalize-display-values@^1.0.0": - version "1.0.1" - resolved "https://registry.npmmirror.com/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-1.0.1.tgz#15da54a36e867b3ac5163ee12c1d7f82d4d612c3" - integrity sha512-jcOanIbv55OFKQ3sYeFD/T0Ti7AMXc9nM1hZWu8m/2722gOTxFg7xYu4RDLJLeZmPUVQlGzo4jhzvTUq3x4ZUw== - dependencies: - postcss-value-parser "^4.2.0" - -"@csstools/postcss-oklab-function@^1.1.0": - version "1.1.1" - resolved "https://registry.npmmirror.com/@csstools/postcss-oklab-function/-/postcss-oklab-function-1.1.1.tgz#88cee0fbc8d6df27079ebd2fa016ee261eecf844" - integrity sha512-nJpJgsdA3dA9y5pgyb/UfEzE7W5Ka7u0CX0/HIMVBNWzWemdcTH3XwANECU6anWv/ao4vVNLTMxhiPNZsTK6iA== - dependencies: - "@csstools/postcss-progressive-custom-properties" "^1.1.0" - postcss-value-parser "^4.2.0" - -"@csstools/postcss-progressive-custom-properties@^1.1.0", "@csstools/postcss-progressive-custom-properties@^1.3.0": - version "1.3.0" - resolved "https://registry.npmmirror.com/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-1.3.0.tgz#542292558384361776b45c85226b9a3a34f276fa" - integrity sha512-ASA9W1aIy5ygskZYuWams4BzafD12ULvSypmaLJT2jvQ8G0M3I8PRQhC0h7mG0Z3LI05+agZjqSR9+K9yaQQjA== - dependencies: - postcss-value-parser "^4.2.0" - -"@csstools/postcss-stepped-value-functions@^1.0.0": - version "1.0.1" - resolved "https://registry.npmmirror.com/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-1.0.1.tgz#f8772c3681cc2befed695e2b0b1d68e22f08c4f4" - integrity sha512-dz0LNoo3ijpTOQqEJLY8nyaapl6umbmDcgj4AD0lgVQ572b2eqA1iGZYTTWhrcrHztWDDRAX2DGYyw2VBjvCvQ== - dependencies: - postcss-value-parser "^4.2.0" - -"@csstools/postcss-unset-value@^1.0.0": - version "1.0.2" - resolved "https://registry.npmmirror.com/@csstools/postcss-unset-value/-/postcss-unset-value-1.0.2.tgz#c99bb70e2cdc7312948d1eb41df2412330b81f77" - integrity sha512-c8J4roPBILnelAsdLr4XOAR/GsTm0GJi4XpcfvoWk3U6KiTCqiFYc63KhRMQQX35jYMp4Ao8Ij9+IZRgMfJp1g== - -"@csstools/selector-specificity@^2.0.0": - version "2.2.0" - resolved "https://registry.npmmirror.com/@csstools/selector-specificity/-/selector-specificity-2.2.0.tgz#2cbcf822bf3764c9658c4d2e568bd0c0cb748016" - integrity sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw== - -"@ctrl/tinycolor@^3.4.0", "@ctrl/tinycolor@^3.6.0": - version "3.6.0" - resolved "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.0.tgz#53fa5fe9c34faee89469e48f91d51a3766108bc8" - integrity sha512-/Z3l6pXthq0JvMYdUFyX9j0MaCltlIn6mfh9jLyQwg5aPKxkyNa0PTHtU1AlFXLNk55ZuAeJRcpvq+tmLfKmaQ== - -"@ctrl/tinycolor@^3.6.1": - version "3.6.1" - resolved "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz#b6c75a56a1947cc916ea058772d666a2c8932f31" - integrity sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA== - -"@develar/schema-utils@~2.6.5": - version "2.6.5" - resolved "https://registry.npmmirror.com/@develar/schema-utils/-/schema-utils-2.6.5.tgz#3ece22c5838402419a6e0425f85742b961d9b6c6" - integrity sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig== - dependencies: - ajv "^6.12.0" - ajv-keywords "^3.4.1" - -"@dnd-kit/accessibility@^3.0.0": - version "3.0.1" - resolved "https://registry.npmmirror.com/@dnd-kit/accessibility/-/accessibility-3.0.1.tgz#3ccbefdfca595b0a23a5dc57d3de96bc6935641c" - integrity sha512-HXRrwS9YUYQO9lFRc/49uO/VICbM+O+ZRpFDe9Pd1rwVv2PCNkRiTZRdxrDgng/UkvdC3Re9r2vwPpXXrWeFzg== - dependencies: - tslib "^2.0.0" - -"@dnd-kit/core@^6.0.8": - version "6.0.8" - resolved "https://registry.npmmirror.com/@dnd-kit/core/-/core-6.0.8.tgz#040ae13fea9787ee078e5f0361f3b49b07f3f005" - integrity sha512-lYaoP8yHTQSLlZe6Rr9qogouGUz9oRUj4AHhDQGQzq/hqaJRpFo65X+JKsdHf8oUFBzx5A+SJPUvxAwTF2OabA== - dependencies: - "@dnd-kit/accessibility" "^3.0.0" - "@dnd-kit/utilities" "^3.2.1" - tslib "^2.0.0" - -"@dnd-kit/modifiers@^6.0.1": - version "6.0.1" - resolved "https://registry.npmmirror.com/@dnd-kit/modifiers/-/modifiers-6.0.1.tgz#9e39b25fd6e323659604cc74488fe044d33188c8" - integrity sha512-rbxcsg3HhzlcMHVHWDuh9LCjpOVAgqbV78wLGI8tziXY3+qcMQ61qVXIvNKQFuhj75dSfD+o+PYZQ/NUk2A23A== - dependencies: - "@dnd-kit/utilities" "^3.2.1" - tslib "^2.0.0" - -"@dnd-kit/sortable@^7.0.2": - version "7.0.2" - resolved "https://registry.npmmirror.com/@dnd-kit/sortable/-/sortable-7.0.2.tgz#791d550872457f3f3c843e00d159b640f982011c" - integrity sha512-wDkBHHf9iCi1veM834Gbk1429bd4lHX4RpAwT0y2cHLf246GAvU2sVw/oxWNpPKQNQRQaeGXhAVgrOl1IT+iyA== - dependencies: - "@dnd-kit/utilities" "^3.2.0" - tslib "^2.0.0" - -"@dnd-kit/utilities@^3.2.0", "@dnd-kit/utilities@^3.2.1": - version "3.2.1" - resolved "https://registry.npmmirror.com/@dnd-kit/utilities/-/utilities-3.2.1.tgz#53f9e2016fd2506ec49e404c289392cfff30332a" - integrity sha512-OOXqISfvBw/1REtkSK2N3Fi2EQiLMlWUlqnOK/UpOISqBZPWpE6TqL+jcPtMOkE8TqYGiURvRdPSI9hltNUjEA== - dependencies: - tslib "^2.0.0" - -"@electron/get@^2.0.0": - version "2.0.3" - resolved "https://registry.npmmirror.com/@electron/get/-/get-2.0.3.tgz#fba552683d387aebd9f3fcadbcafc8e12ee4f960" - integrity sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ== - dependencies: - debug "^4.1.1" - env-paths "^2.2.0" - fs-extra "^8.1.0" - got "^11.8.5" - progress "^2.0.3" - semver "^6.2.0" - sumchecker "^3.0.1" - optionalDependencies: - global-agent "^3.0.0" - -"@electron/universal@1.2.1": - version "1.2.1" - resolved "https://registry.npmmirror.com/@electron/universal/-/universal-1.2.1.tgz#3c2c4ff37063a4e9ab1e6ff57db0bc619bc82339" - integrity sha512-7323HyMh7KBAl/nPDppdLsC87G6RwRU02dy5FPeGB1eS7rUePh55+WNWiDPLhFQqqVPHzh77M69uhmoT8XnwMQ== - dependencies: - "@malept/cross-spawn-promise" "^1.1.0" - asar "^3.1.0" - debug "^4.3.1" - dir-compare "^2.4.0" - fs-extra "^9.0.1" - minimatch "^3.0.4" - plist "^3.0.4" - -"@emotion/hash@^0.8.0": - version "0.8.0" - resolved "https://registry.npmmirror.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413" - integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow== - -"@emotion/is-prop-valid@^1.1.0", "@emotion/is-prop-valid@^1.2.1": - version "1.2.1" - resolved "https://registry.npmmirror.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz#23116cf1ed18bfeac910ec6436561ecb1a3885cc" - integrity sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw== - dependencies: - "@emotion/memoize" "^0.8.1" - -"@emotion/memoize@^0.8.1": - version "0.8.1" - resolved "https://registry.npmmirror.com/@emotion/memoize/-/memoize-0.8.1.tgz#c1ddb040429c6d21d38cc945fe75c818cfb68e17" - integrity sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA== - -"@emotion/stylis@^0.8.4": - version "0.8.5" - resolved "https://registry.npmmirror.com/@emotion/stylis/-/stylis-0.8.5.tgz#deacb389bd6ee77d1e7fcaccce9e16c5c7e78e04" - integrity sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ== - -"@emotion/unitless@^0.7.4", "@emotion/unitless@^0.7.5": - version "0.7.5" - resolved "https://registry.npmmirror.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed" - integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== - -"@emotion/unitless@^0.8.0": - version "0.8.1" - resolved "https://registry.npmmirror.com/@emotion/unitless/-/unitless-0.8.1.tgz#182b5a4704ef8ad91bde93f7a860a88fd92c79a3" - integrity sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ== - -"@esbuild-kit/cjs-loader@^2.4.2": - version "2.4.2" - resolved "https://registry.npmmirror.com/@esbuild-kit/cjs-loader/-/cjs-loader-2.4.2.tgz#cb4dde00fbf744a68c4f20162ea15a8242d0fa54" - integrity sha512-BDXFbYOJzT/NBEtp71cvsrGPwGAMGRB/349rwKuoxNSiKjPraNNnlK6MIIabViCjqZugu6j+xeMDlEkWdHHJSg== - dependencies: - "@esbuild-kit/core-utils" "^3.0.0" - get-tsconfig "^4.4.0" - -"@esbuild-kit/core-utils@^3.0.0": - version "3.1.0" - resolved "https://registry.npmmirror.com/@esbuild-kit/core-utils/-/core-utils-3.1.0.tgz#49945d533dbd5e1b7620aa0fc522c15e6ec089c5" - integrity sha512-Uuk8RpCg/7fdHSceR1M6XbSZFSuMrxcePFuGgyvsBn+u339dk5OeL4jv2EojwTN2st/unJGsVm4qHWjWNmJ/tw== - dependencies: - esbuild "~0.17.6" - source-map-support "^0.5.21" - -"@esbuild-kit/esm-loader@^2.5.5": - version "2.5.5" - resolved "https://registry.npmmirror.com/@esbuild-kit/esm-loader/-/esm-loader-2.5.5.tgz#b82da14fcee3fc1d219869756c06f43f67d1ca71" - integrity sha512-Qwfvj/qoPbClxCRNuac1Du01r9gvNOT+pMYtJDapfB1eoGN1YlJ1BixLyL9WVENRx5RXgNLdfYdx/CuswlGhMw== - dependencies: - "@esbuild-kit/core-utils" "^3.0.0" - get-tsconfig "^4.4.0" - -"@esbuild/android-arm64@0.17.19": - version "0.17.19" - resolved "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz#bafb75234a5d3d1b690e7c2956a599345e84a2fd" - integrity sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA== - -"@esbuild/android-arm@0.17.19": - version "0.17.19" - resolved "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.17.19.tgz#5898f7832c2298bc7d0ab53701c57beb74d78b4d" - integrity sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A== - -"@esbuild/android-x64@0.17.19": - version "0.17.19" - resolved "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.17.19.tgz#658368ef92067866d95fb268719f98f363d13ae1" - integrity sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww== - -"@esbuild/darwin-arm64@0.17.19": - version "0.17.19" - resolved "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz#584c34c5991b95d4d48d333300b1a4e2ff7be276" - integrity sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg== - -"@esbuild/darwin-x64@0.17.19": - version "0.17.19" - resolved "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz#7751d236dfe6ce136cce343dce69f52d76b7f6cb" - integrity sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw== - -"@esbuild/freebsd-arm64@0.17.19": - version "0.17.19" - resolved "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz#cacd171665dd1d500f45c167d50c6b7e539d5fd2" - integrity sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ== - -"@esbuild/freebsd-x64@0.17.19": - version "0.17.19" - resolved "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz#0769456eee2a08b8d925d7c00b79e861cb3162e4" - integrity sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ== - -"@esbuild/linux-arm64@0.17.19": - version "0.17.19" - resolved "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz#38e162ecb723862c6be1c27d6389f48960b68edb" - integrity sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg== - -"@esbuild/linux-arm@0.17.19": - version "0.17.19" - resolved "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz#1a2cd399c50040184a805174a6d89097d9d1559a" - integrity sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA== - -"@esbuild/linux-ia32@0.17.19": - version "0.17.19" - resolved "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz#e28c25266b036ce1cabca3c30155222841dc035a" - integrity sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ== - -"@esbuild/linux-loong64@0.17.19": - version "0.17.19" - resolved "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz#0f887b8bb3f90658d1a0117283e55dbd4c9dcf72" - integrity sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ== - -"@esbuild/linux-mips64el@0.17.19": - version "0.17.19" - resolved "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz#f5d2a0b8047ea9a5d9f592a178ea054053a70289" - integrity sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A== - -"@esbuild/linux-ppc64@0.17.19": - version "0.17.19" - resolved "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz#876590e3acbd9fa7f57a2c7d86f83717dbbac8c7" - integrity sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg== - -"@esbuild/linux-riscv64@0.17.19": - version "0.17.19" - resolved "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz#7f49373df463cd9f41dc34f9b2262d771688bf09" - integrity sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA== - -"@esbuild/linux-s390x@0.17.19": - version "0.17.19" - resolved "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz#e2afd1afcaf63afe2c7d9ceacd28ec57c77f8829" - integrity sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q== - -"@esbuild/linux-x64@0.17.19": - version "0.17.19" - resolved "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz#8a0e9738b1635f0c53389e515ae83826dec22aa4" - integrity sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw== - -"@esbuild/netbsd-x64@0.17.19": - version "0.17.19" - resolved "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz#c29fb2453c6b7ddef9a35e2c18b37bda1ae5c462" - integrity sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q== - -"@esbuild/openbsd-x64@0.17.19": - version "0.17.19" - resolved "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz#95e75a391403cb10297280d524d66ce04c920691" - integrity sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g== - -"@esbuild/sunos-x64@0.17.19": - version "0.17.19" - resolved "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz#722eaf057b83c2575937d3ffe5aeb16540da7273" - integrity sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg== - -"@esbuild/win32-arm64@0.17.19": - version "0.17.19" - resolved "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz#9aa9dc074399288bdcdd283443e9aeb6b9552b6f" - integrity sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag== - -"@esbuild/win32-ia32@0.17.19": - version "0.17.19" - resolved "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz#95ad43c62ad62485e210f6299c7b2571e48d2b03" - integrity sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw== - -"@esbuild/win32-x64@0.17.19": - version "0.17.19" - resolved "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz#8cfaf2ff603e9aabb910e9c0558c26cf32744061" - integrity sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA== - -"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": - version "4.4.0" - resolved "https://registry.npmmirror.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" - integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== - dependencies: - eslint-visitor-keys "^3.3.0" - -"@eslint-community/regexpp@^4.4.0": - version "4.10.0" - resolved "https://registry.npmmirror.com/@eslint-community/regexpp/-/regexpp-4.10.0.tgz#548f6de556857c8bb73bbee70c35dc82a2e74d63" - integrity sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA== - -"@eslint-community/regexpp@^4.5.1", "@eslint-community/regexpp@^4.6.1": - version "4.8.1" - resolved "https://registry.npmmirror.com/@eslint-community/regexpp/-/regexpp-4.8.1.tgz#8c4bb756cc2aa7eaf13cfa5e69c83afb3260c20c" - integrity sha512-PWiOzLIUAjN/w5K17PoF4n6sKBw0gqLHPhywmYHP4t1VFQQVYeb1yWsJwnMVEMl3tUHME7X/SJPZLmtG7XBDxQ== - -"@eslint/eslintrc@^2.1.2": - version "2.1.2" - resolved "https://registry.npmmirror.com/@eslint/eslintrc/-/eslintrc-2.1.2.tgz#c6936b4b328c64496692f76944e755738be62396" - integrity sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g== - dependencies: - ajv "^6.12.4" - debug "^4.3.2" - espree "^9.6.0" - globals "^13.19.0" - ignore "^5.2.0" - import-fresh "^3.2.1" - js-yaml "^4.1.0" - minimatch "^3.1.2" - strip-json-comments "^3.1.1" - -"@eslint/js@8.49.0": - version "8.49.0" - resolved "https://registry.npmmirror.com/@eslint/js/-/js-8.49.0.tgz#86f79756004a97fa4df866835093f1df3d03c333" - integrity sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w== - -"@floating-ui/core@^0.6.2": - version "0.6.2" - resolved "https://registry.npmmirror.com/@floating-ui/core/-/core-0.6.2.tgz#f2813f0e5f3d5ed7af5029e1a082203dadf02b7d" - integrity sha512-jktYRmZwmau63adUG3GKOAVCofBXkk55S/zQ94XOorAHhwqFIOFAy1rSp2N0Wp6/tGbe9V3u/ExlGZypyY17rg== - -"@floating-ui/dom@^0.4.5": - version "0.4.5" - resolved "https://registry.npmmirror.com/@floating-ui/dom/-/dom-0.4.5.tgz#2e88d16646119cc67d44683f75ee99840475bbfa" - integrity sha512-b+prvQgJt8pieaKYMSJBXHxX/DYwdLsAWxKYqnO5dO2V4oo/TYBZJAUQCVNjTWWsrs6o4VDrNcP9+E70HAhJdw== - dependencies: - "@floating-ui/core" "^0.6.2" - -"@floating-ui/react-dom-interactions@^0.3.1": - version "0.3.1" - resolved "https://registry.npmmirror.com/@floating-ui/react-dom-interactions/-/react-dom-interactions-0.3.1.tgz#abc0cb4b18e6f095397e50f9846572eee4e34554" - integrity sha512-tP2KEh7EHJr5hokSBHcPGojb+AorDNUf0NYfZGg/M+FsMvCOOsSEeEF0O1NDfETIzDnpbHnCs0DuvCFhSMSStg== - dependencies: - "@floating-ui/react-dom" "^0.6.3" - aria-hidden "^1.1.3" - point-in-polygon "^1.1.0" - use-isomorphic-layout-effect "^1.1.1" - -"@floating-ui/react-dom@^0.6.3": - version "0.6.3" - resolved "https://registry.npmmirror.com/@floating-ui/react-dom/-/react-dom-0.6.3.tgz#7b64cfd4fd12e4a0515dbf1b2be16e48c9a06c5a" - integrity sha512-hC+pS5D6AgS2wWjbmSQ6UR6Kpy+drvWGJIri6e1EDGADTPsCaa4KzCgmCczHrQeInx9tqs81EyDmbKJYY2swKg== - dependencies: - "@floating-ui/dom" "^0.4.5" - use-isomorphic-layout-effect "^1.1.1" - -"@formatjs/intl-displaynames@^1.2.0": - version "1.2.10" - resolved "https://registry.npmmirror.com/@formatjs/intl-displaynames/-/intl-displaynames-1.2.10.tgz#bb9625cca90b099978cd967c6a98aaf4e23fc878" - integrity sha512-GROA2RP6+7Ouu0WnHFF78O5XIU7pBfI19WM1qm93l6MFWibUk67nCfVCK3VAYJkLy8L8ZxjkYT11VIAfvSz8wg== - dependencies: - "@formatjs/intl-utils" "^2.3.0" - -"@formatjs/intl-listformat@^1.4.1": - version "1.4.8" - resolved "https://registry.npmmirror.com/@formatjs/intl-listformat/-/intl-listformat-1.4.8.tgz#70b81005e7dcf74329cb5b314a940ce5fce36cd0" - integrity sha512-WNMQlEg0e50VZrGIkgD5n7+DAMGt3boKi1GJALfhFMymslJb5i+5WzWxyj/3a929Z6MAFsmzRIJjKuv+BxKAOQ== - dependencies: - "@formatjs/intl-utils" "^2.3.0" - -"@formatjs/intl-relativetimeformat@^4.5.9": - version "4.5.16" - resolved "https://registry.npmmirror.com/@formatjs/intl-relativetimeformat/-/intl-relativetimeformat-4.5.16.tgz#7449cef3213dd66d25924ca41f125f87b58df95a" - integrity sha512-IQ0haY97oHAH5OYUdykNiepdyEWj3SAT+Fp9ZpR85ov2JNiFx+12WWlxlVS8ehdyncC2ZMt/SwFIy2huK2+6/A== - dependencies: - "@formatjs/intl-utils" "^2.3.0" - -"@formatjs/intl-unified-numberformat@^3.2.0": - version "3.3.7" - resolved "https://registry.npmmirror.com/@formatjs/intl-unified-numberformat/-/intl-unified-numberformat-3.3.7.tgz#9995a24568908188e716d81a1de5b702b2ee00e2" - integrity sha512-KnWgLRHzCAgT9eyt3OS34RHoyD7dPDYhRcuKn+/6Kv2knDF8Im43J6vlSW6Hm1w63fNq3ZIT1cFk7RuVO3Psag== - dependencies: - "@formatjs/intl-utils" "^2.3.0" - -"@formatjs/intl-utils@^2.2.0", "@formatjs/intl-utils@^2.3.0": - version "2.3.0" - resolved "https://registry.npmmirror.com/@formatjs/intl-utils/-/intl-utils-2.3.0.tgz#2dc8c57044de0340eb53a7ba602e59abf80dc799" - integrity sha512-KWk80UPIzPmUg+P0rKh6TqspRw0G6eux1PuJr+zz47ftMaZ9QDwbGzHZbtzWkl5hgayM/qrKRutllRC7D/vVXQ== - -"@humanwhocodes/config-array@^0.11.11": - version "0.11.11" - resolved "https://registry.npmmirror.com/@humanwhocodes/config-array/-/config-array-0.11.11.tgz#88a04c570dbbc7dd943e4712429c3df09bc32844" - integrity sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA== - dependencies: - "@humanwhocodes/object-schema" "^1.2.1" - debug "^4.1.1" - minimatch "^3.0.5" - -"@humanwhocodes/module-importer@^1.0.1": - version "1.0.1" - resolved "https://registry.npmmirror.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" - integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== - -"@humanwhocodes/object-schema@^1.2.1": - version "1.2.1" - resolved "https://registry.npmmirror.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" - integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== - -"@iconify/types@^2.0.0": - version "2.0.0" - resolved "https://registry.npmmirror.com/@iconify/types/-/types-2.0.0.tgz#ab0e9ea681d6c8a1214f30cd741fe3a20cc57f57" - integrity sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg== - -"@iconify/utils@2.1.1": - version "2.1.1" - resolved "https://registry.npmmirror.com/@iconify/utils/-/utils-2.1.1.tgz#a75387fff55aa9c95841968c7de05a210010c1ac" - integrity sha512-H8xz74JDzDw8f0qLxwIaxFMnFkbXTZNWEufOk3WxaLFHV4h0A2FjIDgNk5LzC0am4jssnjdeJJdRs3UFu3582Q== - dependencies: - "@antfu/install-pkg" "^0.1.1" - "@antfu/utils" "^0.7.2" - "@iconify/types" "^2.0.0" - debug "^4.3.4" - kolorist "^1.6.0" - local-pkg "^0.4.2" - -"@istanbuljs/load-nyc-config@^1.0.0": - version "1.1.0" - resolved "https://registry.npmmirror.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" - integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== - dependencies: - camelcase "^5.3.1" - find-up "^4.1.0" - get-package-type "^0.1.0" - js-yaml "^3.13.1" - resolve-from "^5.0.0" - -"@istanbuljs/schema@^0.1.2": - version "0.1.3" - resolved "https://registry.npmmirror.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" - integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== - -"@jest/schemas@^29.6.0": - version "29.6.0" - resolved "https://registry.npmmirror.com/@jest/schemas/-/schemas-29.6.0.tgz#0f4cb2c8e3dca80c135507ba5635a4fd755b0040" - integrity sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ== - dependencies: - "@sinclair/typebox" "^0.27.8" - -"@jest/transform@^29.6.1": - version "29.6.1" - resolved "https://registry.npmmirror.com/@jest/transform/-/transform-29.6.1.tgz#acb5606019a197cb99beda3c05404b851f441c92" - integrity sha512-URnTneIU3ZjRSaf906cvf6Hpox3hIeJXRnz3VDSw5/X93gR8ycdfSIEy19FlVx8NFmpN7fe3Gb1xF+NjXaQLWg== - dependencies: - "@babel/core" "^7.11.6" - "@jest/types" "^29.6.1" - "@jridgewell/trace-mapping" "^0.3.18" - babel-plugin-istanbul "^6.1.1" - chalk "^4.0.0" - convert-source-map "^2.0.0" - fast-json-stable-stringify "^2.1.0" - graceful-fs "^4.2.9" - jest-haste-map "^29.6.1" - jest-regex-util "^29.4.3" - jest-util "^29.6.1" - micromatch "^4.0.4" - pirates "^4.0.4" - slash "^3.0.0" - write-file-atomic "^4.0.2" - -"@jest/types@27.5.1": - version "27.5.1" - resolved "https://registry.npmmirror.com/@jest/types/-/types-27.5.1.tgz#3c79ec4a8ba61c170bf937bcf9e98a9df175ec80" - integrity sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^16.0.0" - chalk "^4.0.0" - -"@jest/types@^29.6.1": - version "29.6.1" - resolved "https://registry.npmmirror.com/@jest/types/-/types-29.6.1.tgz#ae79080278acff0a6af5eb49d063385aaa897bf2" - integrity sha512-tPKQNMPuXgvdOn2/Lg9HNfUvjYVGolt04Hp03f5hAk878uwOLikN+JzeLY0HcVgKgFl9Hs3EIqpu3WX27XNhnw== - dependencies: - "@jest/schemas" "^29.6.0" - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^17.0.8" - chalk "^4.0.0" - -"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": - version "0.3.3" - resolved "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" - integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== - dependencies: - "@jridgewell/set-array" "^1.0.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.9" - -"@jridgewell/resolve-uri@3.1.0": - version "3.1.0" - resolved "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" - integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== - -"@jridgewell/set-array@^1.0.1": - version "1.1.2" - resolved "https://registry.npmmirror.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" - integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== - -"@jridgewell/source-map@^0.3.3": - version "0.3.5" - resolved "https://registry.npmmirror.com/@jridgewell/source-map/-/source-map-0.3.5.tgz#a3bb4d5c6825aab0d281268f47f6ad5853431e91" - integrity sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ== - dependencies: - "@jridgewell/gen-mapping" "^0.3.0" - "@jridgewell/trace-mapping" "^0.3.9" - -"@jridgewell/sourcemap-codec@1.4.14": - version "1.4.14" - resolved "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" - integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== - -"@jridgewell/sourcemap-codec@^1.4.10": - version "1.4.15" - resolved "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" - integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== - -"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.9": - version "0.3.18" - resolved "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz#25783b2086daf6ff1dcb53c9249ae480e4dd4cd6" - integrity sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA== - dependencies: - "@jridgewell/resolve-uri" "3.1.0" - "@jridgewell/sourcemap-codec" "1.4.14" - -"@loadable/component@5.15.2": - version "5.15.2" - resolved "https://registry.npmmirror.com/@loadable/component/-/component-5.15.2.tgz#b6c418d592e0a64f16b1d614ca9d3b1443d3b498" - integrity sha512-ryFAZOX5P2vFkUdzaAtTG88IGnr9qxSdvLRvJySXcUA4B4xVWurUNADu3AnKPksxOZajljqTrDEDcYjeL4lvLw== - dependencies: - "@babel/runtime" "^7.7.7" - hoist-non-react-statics "^3.3.1" - react-is "^16.12.0" - -"@malept/cross-spawn-promise@^1.1.0": - version "1.1.1" - resolved "https://registry.npmmirror.com/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.1.tgz#504af200af6b98e198bce768bc1730c6936ae01d" - integrity sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ== - dependencies: - cross-spawn "^7.0.1" - -"@malept/flatpak-bundler@^0.4.0": - version "0.4.0" - resolved "https://registry.npmmirror.com/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz#e8a32c30a95d20c2b1bb635cc580981a06389858" - integrity sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q== - dependencies: - debug "^4.1.1" - fs-extra "^9.0.0" - lodash "^4.17.15" - tmp-promise "^3.0.2" - -"@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3": - version "2.1.8-no-fsevents.3" - resolved "https://registry.npmmirror.com/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz#323d72dd25103d0c4fbdce89dadf574a787b1f9b" - integrity sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ== - -"@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1": - version "5.1.1-v1" - resolved "https://registry.npmmirror.com/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz#dbf733a965ca47b1973177dc0bb6c889edcfb129" - integrity sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg== - dependencies: - eslint-scope "5.1.1" - -"@nicolo-ribaudo/semver-v6@^6.3.3": - version "6.3.3" - resolved "https://registry.npmmirror.com/@nicolo-ribaudo/semver-v6/-/semver-v6-6.3.3.tgz#ea6d23ade78a325f7a52750aab1526b02b628c29" - integrity sha512-3Yc1fUTs69MG/uZbJlLSI3JISMn2UV2rg+1D/vROUqZyh3l6iYHCs7GMp+M40ZD7yOdDbYjJcU1oTJhrc+dGKg== - -"@nodelib/fs.scandir@2.1.5": - version "2.1.5" - resolved "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" - integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== - dependencies: - "@nodelib/fs.stat" "2.0.5" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": - version "2.0.5" - resolved "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" - integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== - -"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": - version "1.2.8" - resolved "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" - integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== - dependencies: - "@nodelib/fs.scandir" "2.1.5" - fastq "^1.6.0" - -"@pkgr/utils@^2.3.1": - version "2.4.2" - resolved "https://registry.npmmirror.com/@pkgr/utils/-/utils-2.4.2.tgz#9e638bbe9a6a6f165580dc943f138fd3309a2cbc" - integrity sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw== - dependencies: - cross-spawn "^7.0.3" - fast-glob "^3.3.0" - is-glob "^4.0.3" - open "^9.1.0" - picocolors "^1.0.0" - tslib "^2.6.0" - -"@popperjs/core@^2.9.1": - version "2.11.8" - resolved "https://registry.npmmirror.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" - integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== - -"@rc-component/color-picker@~1.4.1": - version "1.4.1" - resolved "https://registry.npmmirror.com/@rc-component/color-picker/-/color-picker-1.4.1.tgz#dcab0b660e9c4ed63a7582db68ed4a77c862cb93" - integrity sha512-vh5EWqnsayZa/JwUznqDaPJz39jznx/YDbyBuVJntv735tKXKwEUZZb2jYEldOg+NKWZwtALjGMrNeGBmqFoEw== - dependencies: - "@babel/runtime" "^7.10.1" - "@ctrl/tinycolor" "^3.6.0" - classnames "^2.2.6" - rc-util "^5.30.0" - -"@rc-component/context@^1.4.0": - version "1.4.0" - resolved "https://registry.npmmirror.com/@rc-component/context/-/context-1.4.0.tgz#dc6fb021d6773546af8f016ae4ce9aea088395e8" - integrity sha512-kFcNxg9oLRMoL3qki0OMxK+7g5mypjgaaJp/pkOis/6rVxma9nJBF/8kCIuTYHUQNr0ii7MxqE33wirPZLJQ2w== - dependencies: - "@babel/runtime" "^7.10.1" - rc-util "^5.27.0" - -"@rc-component/mini-decimal@^1.0.1": - version "1.1.0" - resolved "https://registry.npmmirror.com/@rc-component/mini-decimal/-/mini-decimal-1.1.0.tgz#7b7a362b14a0a54cb5bc6fd2b82731f29f11d9b0" - integrity sha512-jS4E7T9Li2GuYwI6PyiVXmxTiM6b07rlD9Ge8uGZSCz3WlzcG5ZK7g5bbuKNeZ9pgUuPK/5guV781ujdVpm4HQ== - dependencies: - "@babel/runtime" "^7.18.0" - -"@rc-component/mutate-observer@^1.1.0": - version "1.1.0" - resolved "https://registry.npmmirror.com/@rc-component/mutate-observer/-/mutate-observer-1.1.0.tgz#ee53cc88b78aade3cd0653609215a44779386fd8" - integrity sha512-QjrOsDXQusNwGZPf4/qRQasg7UFEj06XiCJ8iuiq/Io7CrHrgVi6Uuetw60WAMG1799v+aM8kyc+1L/GBbHSlw== - dependencies: - "@babel/runtime" "^7.18.0" - classnames "^2.3.2" - rc-util "^5.24.4" - -"@rc-component/portal@^1.0.0-8", "@rc-component/portal@^1.0.0-9", "@rc-component/portal@^1.0.2", "@rc-component/portal@^1.1.0", "@rc-component/portal@^1.1.1": - version "1.1.2" - resolved "https://registry.npmmirror.com/@rc-component/portal/-/portal-1.1.2.tgz#55db1e51d784e034442e9700536faaa6ab63fc71" - integrity sha512-6f813C0IsasTZms08kfA8kPAGxbbkYToa8ALaiDIGGECU4i9hj8Plgbx0sNJDrey3EtHO30hmdaxtT0138xZcg== - dependencies: - "@babel/runtime" "^7.18.0" - classnames "^2.3.2" - rc-util "^5.24.4" - -"@rc-component/tour@~1.11.0": - version "1.11.1" - resolved "https://registry.npmmirror.com/@rc-component/tour/-/tour-1.11.1.tgz#bb47af908fb6fcaf395402901a1fb505ef8fb1e6" - integrity sha512-c9Lw3/oVinj5D64Rsp8aDLOXcgdViE+hq7bj0Qoo8fTuQEh9sSpUw5OZcum943JkjeIE4hLcc5FD4a5ANtMJ4w== - dependencies: - "@babel/runtime" "^7.18.0" - "@rc-component/portal" "^1.0.0-9" - "@rc-component/trigger" "^1.3.6" - classnames "^2.3.2" - rc-util "^5.24.4" - -"@rc-component/trigger@^1.17.0", "@rc-component/trigger@^1.18.0", "@rc-component/trigger@^1.18.2": - version "1.18.2" - resolved "https://registry.npmmirror.com/@rc-component/trigger/-/trigger-1.18.2.tgz#dc52c4c66fa8aaccaf0710498f2429fc05454e3b" - integrity sha512-jRLYgFgjLEPq3MvS87fIhcfuywFSRDaDrYw1FLku7Cm4esszvzTbA0JBsyacAyLrK9rF3TiHFcvoEDMzoD3CTA== - 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.38.0" - -"@rc-component/trigger@^1.3.6", "@rc-component/trigger@^1.5.0", "@rc-component/trigger@^1.7.0": - version "1.14.2" - resolved "https://registry.npmmirror.com/@rc-component/trigger/-/trigger-1.14.2.tgz#cd13af95769a0cda21cec00cfe85422c3fd72f65" - integrity sha512-hQtC/HfSL6zsY4w0b3YtWgXf4TpYLvjbQIW8ohdVwJ8OScL3piVtt3SCTS+AMSwjQu4C+XGioFXK98UGR6ookg== - dependencies: - "@babel/runtime" "^7.18.3" - "@rc-component/portal" "^1.1.0" - classnames "^2.3.2" - rc-align "^4.0.0" - rc-motion "^2.0.0" - rc-resize-observer "^1.3.1" - rc-util "^5.33.0" - -"@sinclair/typebox@^0.27.8": - version "0.27.8" - resolved "https://registry.npmmirror.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" - integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== - -"@sindresorhus/is@^4.0.0": - version "4.6.0" - resolved "https://registry.npmmirror.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" - integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw== - -"@stylelint/postcss-css-in-js@^0.38.0": - version "0.38.0" - resolved "https://registry.npmmirror.com/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.38.0.tgz#eabb061df932744db766f11a153ae1c465b6263c" - integrity sha512-XOz5CAe49kS95p5yRd+DAIWDojTjfmyAQ4bbDlXMdbZTQ5t0ThjSLvWI6JI2uiS7MFurVBkZ6zUqcimzcLTBoQ== - dependencies: - "@babel/core" "^7.17.9" - -"@svgr/babel-plugin-add-jsx-attribute@^6.5.1": - version "6.5.1" - resolved "https://registry.npmmirror.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-6.5.1.tgz#74a5d648bd0347bda99d82409d87b8ca80b9a1ba" - integrity sha512-9PYGcXrAxitycIjRmZB+Q0JaN07GZIWaTBIGQzfaZv+qr1n8X1XUEJ5rZ/vx6OVD9RRYlrNnXWExQXcmZeD/BQ== - -"@svgr/babel-plugin-remove-jsx-attribute@*": - version "8.0.0" - resolved "https://registry.npmmirror.com/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz#69177f7937233caca3a1afb051906698f2f59186" - integrity sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA== - -"@svgr/babel-plugin-remove-jsx-empty-expression@*": - version "8.0.0" - resolved "https://registry.npmmirror.com/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz#c2c48104cfd7dcd557f373b70a56e9e3bdae1d44" - integrity sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA== - -"@svgr/babel-plugin-replace-jsx-attribute-value@^6.5.1": - version "6.5.1" - resolved "https://registry.npmmirror.com/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-6.5.1.tgz#fb9d22ea26d2bc5e0a44b763d4c46d5d3f596c60" - integrity sha512-8DPaVVE3fd5JKuIC29dqyMB54sA6mfgki2H2+swh+zNJoynC8pMPzOkidqHOSc6Wj032fhl8Z0TVn1GiPpAiJg== - -"@svgr/babel-plugin-svg-dynamic-title@^6.5.1": - version "6.5.1" - resolved "https://registry.npmmirror.com/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-6.5.1.tgz#01b2024a2b53ffaa5efceaa0bf3e1d5a4c520ce4" - integrity sha512-FwOEi0Il72iAzlkaHrlemVurgSQRDFbk0OC8dSvD5fSBPHltNh7JtLsxmZUhjYBZo2PpcU/RJvvi6Q0l7O7ogw== - -"@svgr/babel-plugin-svg-em-dimensions@^6.5.1": - version "6.5.1" - resolved "https://registry.npmmirror.com/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-6.5.1.tgz#dd3fa9f5b24eb4f93bcf121c3d40ff5facecb217" - integrity sha512-gWGsiwjb4tw+ITOJ86ndY/DZZ6cuXMNE/SjcDRg+HLuCmwpcjOktwRF9WgAiycTqJD/QXqL2f8IzE2Rzh7aVXA== - -"@svgr/babel-plugin-transform-react-native-svg@^6.5.1": - version "6.5.1" - resolved "https://registry.npmmirror.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-6.5.1.tgz#1d8e945a03df65b601551097d8f5e34351d3d305" - integrity sha512-2jT3nTayyYP7kI6aGutkyfJ7UMGtuguD72OjeGLwVNyfPRBD8zQthlvL+fAbAKk5n9ZNcvFkp/b1lZ7VsYqVJg== - -"@svgr/babel-plugin-transform-svg-component@^6.5.1": - version "6.5.1" - resolved "https://registry.npmmirror.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-6.5.1.tgz#48620b9e590e25ff95a80f811544218d27f8a250" - integrity sha512-a1p6LF5Jt33O3rZoVRBqdxL350oge54iZWHNI6LJB5tQ7EelvD/Mb1mfBiZNAan0dt4i3VArkFRjA4iObuNykQ== - -"@svgr/babel-preset@^6.5.1": - version "6.5.1" - resolved "https://registry.npmmirror.com/@svgr/babel-preset/-/babel-preset-6.5.1.tgz#b90de7979c8843c5c580c7e2ec71f024b49eb828" - integrity sha512-6127fvO/FF2oi5EzSQOAjo1LE3OtNVh11R+/8FXa+mHx1ptAaS4cknIjnUA7e6j6fwGGJ17NzaTJFUwOV2zwCw== - dependencies: - "@svgr/babel-plugin-add-jsx-attribute" "^6.5.1" - "@svgr/babel-plugin-remove-jsx-attribute" "*" - "@svgr/babel-plugin-remove-jsx-empty-expression" "*" - "@svgr/babel-plugin-replace-jsx-attribute-value" "^6.5.1" - "@svgr/babel-plugin-svg-dynamic-title" "^6.5.1" - "@svgr/babel-plugin-svg-em-dimensions" "^6.5.1" - "@svgr/babel-plugin-transform-react-native-svg" "^6.5.1" - "@svgr/babel-plugin-transform-svg-component" "^6.5.1" - -"@svgr/core@6.5.1": - version "6.5.1" - resolved "https://registry.npmmirror.com/@svgr/core/-/core-6.5.1.tgz#d3e8aa9dbe3fbd747f9ee4282c1c77a27410488a" - integrity sha512-/xdLSWxK5QkqG524ONSjvg3V/FkNyCv538OIBdQqPNaAta3AsXj/Bd2FbvR87yMbXO2hFSWiAe/Q6IkVPDw+mw== - dependencies: - "@babel/core" "^7.19.6" - "@svgr/babel-preset" "^6.5.1" - "@svgr/plugin-jsx" "^6.5.1" - camelcase "^6.2.0" - cosmiconfig "^7.0.1" - -"@svgr/hast-util-to-babel-ast@^6.5.1": - version "6.5.1" - resolved "https://registry.npmmirror.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-6.5.1.tgz#81800bd09b5bcdb968bf6ee7c863d2288fdb80d2" - integrity sha512-1hnUxxjd83EAxbL4a0JDJoD3Dao3hmjvyvyEV8PzWmLK3B9m9NPlW7GKjFyoWE8nM7HnXzPcmmSyOW8yOddSXw== - dependencies: - "@babel/types" "^7.20.0" - entities "^4.4.0" - -"@svgr/plugin-jsx@^6.5.1": - version "6.5.1" - resolved "https://registry.npmmirror.com/@svgr/plugin-jsx/-/plugin-jsx-6.5.1.tgz#0e30d1878e771ca753c94e69581c7971542a7072" - integrity sha512-+UdQxI3jgtSjCykNSlEMuy1jSRQlGC7pqBCPvkG/2dATdWo082zHTTK3uhnAju2/6XpE6B5mZ3z4Z8Ns01S8Gw== - dependencies: - "@babel/core" "^7.19.6" - "@svgr/babel-preset" "^6.5.1" - "@svgr/hast-util-to-babel-ast" "^6.5.1" - svg-parser "^2.0.4" - -"@svgr/plugin-svgo@^6.5.1": - version "6.5.1" - resolved "https://registry.npmmirror.com/@svgr/plugin-svgo/-/plugin-svgo-6.5.1.tgz#0f91910e988fc0b842f88e0960c2862e022abe84" - integrity sha512-omvZKf8ixP9z6GWgwbtmP9qQMPX4ODXi+wzbVZgomNFsUIlHA1sf4fThdwTWSsZGgvGAG6yE+b/F5gWUkcZ/iQ== - dependencies: - cosmiconfig "^7.0.1" - deepmerge "^4.2.2" - svgo "^2.8.0" - -"@szmarczak/http-timer@^4.0.5": - version "4.0.6" - resolved "https://registry.npmmirror.com/@szmarczak/http-timer/-/http-timer-4.0.6.tgz#b4a914bb62e7c272d4e5989fe4440f812ab1d807" - integrity sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w== - dependencies: - defer-to-connect "^2.0.0" - -"@tanstack/match-sorter-utils@^8.7.0": - version "8.8.4" - resolved "https://registry.npmmirror.com/@tanstack/match-sorter-utils/-/match-sorter-utils-8.8.4.tgz#0b2864d8b7bac06a9f84cb903d405852cc40a457" - integrity sha512-rKH8LjZiszWEvmi01NR72QWZ8m4xmXre0OOwlRGnjU01Eqz/QnN+cqpty2PJ0efHblq09+KilvyR7lsbzmXVEw== - dependencies: - remove-accents "0.4.2" - -"@tanstack/query-core@4.29.25": - version "4.29.25" - resolved "https://registry.npmmirror.com/@tanstack/query-core/-/query-core-4.29.25.tgz#605d357968a740544af6754004eed1dfd4587cb8" - integrity sha512-DI4y4VC6Uw4wlTpOocEXDky69xeOScME1ezLKsj+hOk7DguC9fkqXtp6Hn39BVb9y0b5IBrY67q6kIX623Zj4Q== - -"@tanstack/react-query-devtools@^4.24.10": - version "4.29.25" - resolved "https://registry.npmmirror.com/@tanstack/react-query-devtools/-/react-query-devtools-4.29.25.tgz#89d2ce3848ff4ff8873978e63ec5a063dbf63616" - integrity sha512-XlrGUqmjv1O+6Ny23rAiyNSWYKep90SKT3IixDQRnIuTGaZej+hVCOh7wZSxq6qkzadIvsblc4SLtyJsOiIXBQ== - dependencies: - "@tanstack/match-sorter-utils" "^8.7.0" - superjson "^1.10.0" - use-sync-external-store "^1.2.0" - -"@tanstack/react-query@^4.24.10": - version "4.29.25" - resolved "https://registry.npmmirror.com/@tanstack/react-query/-/react-query-4.29.25.tgz#64df3260b65760fbd3c81ffae23b7b3802c71aa6" - integrity sha512-c1+Ezu+XboYrdAMdusK2fTdRqXPMgPAnyoTrzHOZQqr8Hqz6PNvV9DSKl8agUo6nXX4np7fdWabIprt+838dLg== - dependencies: - "@tanstack/query-core" "4.29.25" - use-sync-external-store "^1.2.0" - -"@tootallnate/once@2": - version "2.0.0" - resolved "https://registry.npmmirror.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" - integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== - -"@trysound/sax@0.2.0": - version "0.2.0" - resolved "https://registry.npmmirror.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" - integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== - -"@types/babel__core@^7.1.14": - version "7.20.1" - resolved "https://registry.npmmirror.com/@types/babel__core/-/babel__core-7.20.1.tgz#916ecea274b0c776fec721e333e55762d3a9614b" - integrity sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw== - dependencies: - "@babel/parser" "^7.20.7" - "@babel/types" "^7.20.7" - "@types/babel__generator" "*" - "@types/babel__template" "*" - "@types/babel__traverse" "*" - -"@types/babel__generator@*": - version "7.6.4" - resolved "https://registry.npmmirror.com/@types/babel__generator/-/babel__generator-7.6.4.tgz#1f20ce4c5b1990b37900b63f050182d28c2439b7" - integrity sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg== - dependencies: - "@babel/types" "^7.0.0" - -"@types/babel__template@*": - version "7.4.1" - resolved "https://registry.npmmirror.com/@types/babel__template/-/babel__template-7.4.1.tgz#3d1a48fd9d6c0edfd56f2ff578daed48f36c8969" - integrity sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g== - dependencies: - "@babel/parser" "^7.1.0" - "@babel/types" "^7.0.0" - -"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": - version "7.20.1" - resolved "https://registry.npmmirror.com/@types/babel__traverse/-/babel__traverse-7.20.1.tgz#dd6f1d2411ae677dcb2db008c962598be31d6acf" - integrity sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg== - dependencies: - "@babel/types" "^7.20.7" - -"@types/cacheable-request@^6.0.1": - version "6.0.3" - resolved "https://registry.npmmirror.com/@types/cacheable-request/-/cacheable-request-6.0.3.tgz#a430b3260466ca7b5ca5bfd735693b36e7a9d183" - integrity sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw== - dependencies: - "@types/http-cache-semantics" "*" - "@types/keyv" "^3.1.4" - "@types/node" "*" - "@types/responselike" "^1.0.0" - -"@types/classnames@^2.2.9": - version "2.3.1" - resolved "https://registry.npmmirror.com/@types/classnames/-/classnames-2.3.1.tgz#3c2467aa0f1a93f1f021e3b9bcf938bd5dfdc0dd" - integrity sha512-zeOWb0JGBoVmlQoznvqXbE0tEC/HONsnoUNH19Hc96NFsTAwTXbTqb8FMYkru1F/iqp7a18Ws3nWJvtA1sHD1A== - dependencies: - classnames "*" - -"@types/debug@^4.1.6": - version "4.1.8" - resolved "https://registry.npmmirror.com/@types/debug/-/debug-4.1.8.tgz#cef723a5d0a90990313faec2d1e22aee5eecb317" - integrity sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ== - dependencies: - "@types/ms" "*" - -"@types/event-source-polyfill@^1.0.1": - version "1.0.1" - resolved "https://registry.npmmirror.com/@types/event-source-polyfill/-/event-source-polyfill-1.0.1.tgz#ffcb4ca17bc35bc1ca5d3e047fe833292bb73c32" - integrity sha512-dls8b0lUgJ/miRApF0efboQ9QZnAm7ofTO6P1ILu8bRPxUFKDxVwFf8+TeuuErmNui6blpltyr7+eV72dbQXlQ== - -"@types/fs-extra@^9.0.11": - version "9.0.13" - resolved "https://registry.npmmirror.com/@types/fs-extra/-/fs-extra-9.0.13.tgz#7594fbae04fe7f1918ce8b3d213f74ff44ac1f45" - integrity sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA== - dependencies: - "@types/node" "*" - -"@types/glob@^7.1.1": - version "7.2.0" - resolved "https://registry.npmmirror.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb" - integrity sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA== - dependencies: - "@types/minimatch" "*" - "@types/node" "*" - -"@types/graceful-fs@^4.1.3": - version "4.1.6" - resolved "https://registry.npmmirror.com/@types/graceful-fs/-/graceful-fs-4.1.6.tgz#e14b2576a1c25026b7f02ede1de3b84c3a1efeae" - integrity sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw== - dependencies: - "@types/node" "*" - -"@types/hapi__joi@17.1.9": - version "17.1.9" - resolved "https://registry.npmmirror.com/@types/hapi__joi/-/hapi__joi-17.1.9.tgz#fb4001df38aba1cd2406ce4b17d4e6fc3b0bd036" - integrity sha512-oOMFT8vmCTFncsF1engrs04jatz8/Anwx3De9uxnOK4chgSEgWBvFtpSoJo8u3784JNO+ql5tzRR6phHoRnscQ== - -"@types/hoist-non-react-statics@^3.3.1": - version "3.3.1" - resolved "https://registry.npmmirror.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" - integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA== - dependencies: - "@types/react" "*" - hoist-non-react-statics "^3.3.0" - -"@types/html-minifier-terser@^6.0.0": - version "6.1.0" - resolved "https://registry.npmmirror.com/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#4fc33a00c1d0c16987b1a20cf92d20614c55ac35" - integrity sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg== - -"@types/http-cache-semantics@*": - version "4.0.3" - resolved "https://registry.npmmirror.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.3.tgz#a3ff232bf7d5c55f38e4e45693eda2ebb545794d" - integrity sha512-V46MYLFp08Wf2mmaBhvgjStM3tPa+2GAdy/iqoX+noX1//zje2x4XmrIU0cAwyClATsTmahbtoQ2EwP7I5WSiA== - -"@types/invariant@^2.2.31": - version "2.2.35" - resolved "https://registry.npmmirror.com/@types/invariant/-/invariant-2.2.35.tgz#cd3ebf581a6557452735688d8daba6cf0bd5a3be" - integrity sha512-DxX1V9P8zdJPYQat1gHyY0xj3efl8gnMVjiM9iCY6y27lj+PoQWkgjt8jDqmovPqULkKVpKRg8J36iQiA+EtEg== - -"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": - version "2.0.4" - resolved "https://registry.npmmirror.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" - integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== - -"@types/istanbul-lib-report@*": - version "3.0.0" - resolved "https://registry.npmmirror.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" - integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== - dependencies: - "@types/istanbul-lib-coverage" "*" - -"@types/istanbul-reports@^3.0.0": - version "3.0.1" - resolved "https://registry.npmmirror.com/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz#9153fe98bba2bd565a63add9436d6f0d7f8468ff" - integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== - dependencies: - "@types/istanbul-lib-report" "*" - -"@types/js-cookie@^2.x.x": - version "2.2.7" - resolved "https://registry.npmmirror.com/@types/js-cookie/-/js-cookie-2.2.7.tgz#226a9e31680835a6188e887f3988e60c04d3f6a3" - integrity sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA== - -"@types/json-schema@^7.0.12": - version "7.0.13" - resolved "https://registry.npmmirror.com/@types/json-schema/-/json-schema-7.0.13.tgz#02c24f4363176d2d18fc8b70b9f3c54aba178a85" - integrity sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ== - -"@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": - version "7.0.12" - resolved "https://registry.npmmirror.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb" - integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA== - -"@types/json5@^0.0.29": - version "0.0.29" - resolved "https://registry.npmmirror.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" - integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== - -"@types/keyv@^3.1.4": - version "3.1.4" - resolved "https://registry.npmmirror.com/@types/keyv/-/keyv-3.1.4.tgz#3ccdb1c6751b0c7e52300bcdacd5bcbf8faa75b6" - integrity sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg== - dependencies: - "@types/node" "*" - -"@types/lodash@^4.14.195": - version "4.14.195" - resolved "https://registry.npmmirror.com/@types/lodash/-/lodash-4.14.195.tgz#bafc975b252eb6cea78882ce8a7b6bf22a6de632" - integrity sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg== - -"@types/minimatch@*": - version "5.1.2" - resolved "https://registry.npmmirror.com/@types/minimatch/-/minimatch-5.1.2.tgz#07508b45797cb81ec3f273011b054cd0755eddca" - integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA== - -"@types/ms@*": - version "0.7.31" - resolved "https://registry.npmmirror.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197" - integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA== - -"@types/node@*": - version "20.4.2" - resolved "https://registry.npmmirror.com/@types/node/-/node-20.4.2.tgz#129cc9ae69f93824f92fac653eebfb4812ab4af9" - integrity sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw== - -"@types/node@^16.11.26": - version "16.18.59" - resolved "https://registry.npmmirror.com/@types/node/-/node-16.18.59.tgz#4cdbd631be6d9be266a96fb17b5d0d7ad6bbe26c" - integrity sha512-PJ1w2cNeKUEdey4LiPra0ZuxZFOGvetswE8qHRriV/sUkL5Al4tTmPV9D2+Y/TPIxTHHgxTfRjZVKWhPw/ORhQ== - -"@types/parse-json@^4.0.0": - version "4.0.0" - resolved "https://registry.npmmirror.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" - integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== - -"@types/plist@^3.0.1": - version "3.0.2" - resolved "https://registry.npmmirror.com/@types/plist/-/plist-3.0.2.tgz#61b3727bba0f5c462fe333542534a0c3e19ccb01" - integrity sha512-ULqvZNGMv0zRFvqn8/4LSPtnmN4MfhlPNtJCTpKuIIxGVGZ2rYWzFXrvEBoh9CVyqSE7D6YFRJ1hydLHI6kbWw== - dependencies: - "@types/node" "*" - xmlbuilder ">=11.0.1" - -"@types/prop-types@*": - version "15.7.5" - resolved "https://registry.npmmirror.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" - integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== - -"@types/react-dom@^18.0.11": - version "18.2.7" - resolved "https://registry.npmmirror.com/@types/react-dom/-/react-dom-18.2.7.tgz#67222a08c0a6ae0a0da33c3532348277c70abb63" - integrity sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA== - dependencies: - "@types/react" "*" - -"@types/react@*", "@types/react@^18.0.33": - version "18.2.15" - resolved "https://registry.npmmirror.com/@types/react/-/react-18.2.15.tgz#14792b35df676c20ec3cf595b262f8c615a73066" - integrity sha512-oEjE7TQt1fFTFSbf8kkNuc798ahTUzn3Le67/PWjE8MAfYAD/qB7O8hSTcromLFqHCt9bcdOg5GXMokzTjJ5SA== - dependencies: - "@types/prop-types" "*" - "@types/scheduler" "*" - csstype "^3.0.2" - -"@types/responselike@^1.0.0": - version "1.0.2" - resolved "https://registry.npmmirror.com/@types/responselike/-/responselike-1.0.2.tgz#8de1b0477fd7c12df77e50832fa51701a8414bd6" - integrity sha512-/4YQT5Kp6HxUDb4yhRkm0bJ7TbjvTddqX7PZ5hz6qV3pxSo72f/6YPRo+Mu2DU307tm9IioO69l7uAwn5XNcFA== - dependencies: - "@types/node" "*" - -"@types/scheduler@*": - version "0.16.3" - resolved "https://registry.npmmirror.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5" - integrity sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ== - -"@types/semver@^7.3.12": - version "7.5.0" - resolved "https://registry.npmmirror.com/@types/semver/-/semver-7.5.0.tgz#591c1ce3a702c45ee15f47a42ade72c2fd78978a" - integrity sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw== - -"@types/semver@^7.5.0": - version "7.5.2" - resolved "https://registry.npmmirror.com/@types/semver/-/semver-7.5.2.tgz#31f6eec1ed7ec23f4f05608d3a2d381df041f564" - integrity sha512-7aqorHYgdNO4DM36stTiGO3DvKoex9TQRwsJU6vMaFGyqpBA1MNZkz+PG3gaNUPpTAOYhT1WR7M1JyA3fbS9Cw== - -"@types/stylis@^4.0.2": - version "4.2.0" - resolved "https://registry.npmmirror.com/@types/stylis/-/stylis-4.2.0.tgz#199a3f473f0c3a6f6e4e1b17cdbc967f274bdc6b" - integrity sha512-n4sx2bqL0mW1tvDf/loQ+aMX7GQD3lc3fkCMC55VFNDu/vBOabO+LTIeXKM14xK0ppk5TUGcWRjiSpIlUpghKw== - -"@types/use-sync-external-store@^0.0.3": - version "0.0.3" - resolved "https://registry.npmmirror.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz#b6725d5f4af24ace33b36fafd295136e75509f43" - integrity sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA== - -"@types/uuid@^9.0.1": - version "9.0.2" - resolved "https://registry.npmmirror.com/@types/uuid/-/uuid-9.0.2.tgz#ede1d1b1e451548d44919dc226253e32a6952c4b" - integrity sha512-kNnC1GFBLuhImSnV7w4njQkUiJi0ZXUycu1rUaouPqiKlXkh77JKgdRnTAp1x5eBwcIwbtI+3otwzuIDEuDoxQ== - -"@types/verror@^1.10.3": - version "1.10.6" - resolved "https://registry.npmmirror.com/@types/verror/-/verror-1.10.6.tgz#3e600c62d210c5826460858f84bcbb65805460bb" - integrity sha512-NNm+gdePAX1VGvPcGZCDKQZKYSiAWigKhKaz5KF94hG6f2s8de9Ow5+7AbXoeKxL8gavZfk4UquSAygOF2duEQ== - -"@types/yargs-parser@*": - version "21.0.0" - resolved "https://registry.npmmirror.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" - integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== - -"@types/yargs@^16.0.0": - version "16.0.5" - resolved "https://registry.npmmirror.com/@types/yargs/-/yargs-16.0.5.tgz#12cc86393985735a283e387936398c2f9e5f88e3" - integrity sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ== - dependencies: - "@types/yargs-parser" "*" - -"@types/yargs@^17.0.1", "@types/yargs@^17.0.8": - version "17.0.24" - resolved "https://registry.npmmirror.com/@types/yargs/-/yargs-17.0.24.tgz#b3ef8d50ad4aa6aecf6ddc97c580a00f5aa11902" - integrity sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw== - dependencies: - "@types/yargs-parser" "*" - -"@types/yauzl@^2.9.1": - version "2.10.2" - resolved "https://registry.npmmirror.com/@types/yauzl/-/yauzl-2.10.2.tgz#dab926ef9b41a898bc943f11bca6b0bad6d4b729" - integrity sha512-Km7XAtUIduROw7QPgvcft0lIupeG8a8rdKL8RiSyKvlE7dYY31fEn41HVuQsRFDuROA8tA4K2UVL+WdfFmErBA== - dependencies: - "@types/node" "*" - -"@typescript-eslint/eslint-plugin@^5.62.0": - version "5.62.0" - resolved "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz#aeef0328d172b9e37d9bab6dbc13b87ed88977db" - integrity sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag== - dependencies: - "@eslint-community/regexpp" "^4.4.0" - "@typescript-eslint/scope-manager" "5.62.0" - "@typescript-eslint/type-utils" "5.62.0" - "@typescript-eslint/utils" "5.62.0" - debug "^4.3.4" - graphemer "^1.4.0" - ignore "^5.2.0" - natural-compare-lite "^1.4.0" - semver "^7.3.7" - tsutils "^3.21.0" - -"@typescript-eslint/eslint-plugin@^6.7.2": - version "6.7.2" - resolved "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.2.tgz#f18cc75c9cceac8080a9dc2e7d166008c5207b9f" - integrity sha512-ooaHxlmSgZTM6CHYAFRlifqh1OAr3PAQEwi7lhYhaegbnXrnh7CDcHmc3+ihhbQC7H0i4JF0psI5ehzkF6Yl6Q== - dependencies: - "@eslint-community/regexpp" "^4.5.1" - "@typescript-eslint/scope-manager" "6.7.2" - "@typescript-eslint/type-utils" "6.7.2" - "@typescript-eslint/utils" "6.7.2" - "@typescript-eslint/visitor-keys" "6.7.2" - debug "^4.3.4" - graphemer "^1.4.0" - ignore "^5.2.4" - natural-compare "^1.4.0" - semver "^7.5.4" - ts-api-utils "^1.0.1" - -"@typescript-eslint/parser@^5.62.0": - version "5.62.0" - resolved "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-5.62.0.tgz#1b63d082d849a2fcae8a569248fbe2ee1b8a56c7" - integrity sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA== - dependencies: - "@typescript-eslint/scope-manager" "5.62.0" - "@typescript-eslint/types" "5.62.0" - "@typescript-eslint/typescript-estree" "5.62.0" - debug "^4.3.4" - -"@typescript-eslint/parser@^6.7.2": - version "6.7.2" - resolved "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-6.7.2.tgz#e0ae93771441b9518e67d0660c79e3a105497af4" - integrity sha512-KA3E4ox0ws+SPyxQf9iSI25R6b4Ne78ORhNHeVKrPQnoYsb9UhieoiRoJgrzgEeKGOXhcY1i8YtOeCHHTDa6Fw== - dependencies: - "@typescript-eslint/scope-manager" "6.7.2" - "@typescript-eslint/types" "6.7.2" - "@typescript-eslint/typescript-estree" "6.7.2" - "@typescript-eslint/visitor-keys" "6.7.2" - debug "^4.3.4" - -"@typescript-eslint/scope-manager@5.62.0": - version "5.62.0" - resolved "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz#d9457ccc6a0b8d6b37d0eb252a23022478c5460c" - integrity sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w== - dependencies: - "@typescript-eslint/types" "5.62.0" - "@typescript-eslint/visitor-keys" "5.62.0" - -"@typescript-eslint/scope-manager@6.7.2": - version "6.7.2" - resolved "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-6.7.2.tgz#cf59a2095d2f894770c94be489648ad1c78dc689" - integrity sha512-bgi6plgyZjEqapr7u2mhxGR6E8WCzKNUFWNh6fkpVe9+yzRZeYtDTbsIBzKbcxI+r1qVWt6VIoMSNZ4r2A+6Yw== - dependencies: - "@typescript-eslint/types" "6.7.2" - "@typescript-eslint/visitor-keys" "6.7.2" - -"@typescript-eslint/type-utils@5.62.0": - version "5.62.0" - resolved "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz#286f0389c41681376cdad96b309cedd17d70346a" - integrity sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew== - dependencies: - "@typescript-eslint/typescript-estree" "5.62.0" - "@typescript-eslint/utils" "5.62.0" - debug "^4.3.4" - tsutils "^3.21.0" - -"@typescript-eslint/type-utils@6.7.2": - version "6.7.2" - resolved "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-6.7.2.tgz#ed921c9db87d72fa2939fee242d700561454f367" - integrity sha512-36F4fOYIROYRl0qj95dYKx6kybddLtsbmPIYNK0OBeXv2j9L5nZ17j9jmfy+bIDHKQgn2EZX+cofsqi8NPATBQ== - dependencies: - "@typescript-eslint/typescript-estree" "6.7.2" - "@typescript-eslint/utils" "6.7.2" - debug "^4.3.4" - ts-api-utils "^1.0.1" - -"@typescript-eslint/types@5.62.0": - version "5.62.0" - resolved "https://registry.npmmirror.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" - integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ== - -"@typescript-eslint/types@6.7.2": - version "6.7.2" - resolved "https://registry.npmmirror.com/@typescript-eslint/types/-/types-6.7.2.tgz#75a615a6dbeca09cafd102fe7f465da1d8a3c066" - integrity sha512-flJYwMYgnUNDAN9/GAI3l8+wTmvTYdv64fcH8aoJK76Y+1FCZ08RtI5zDerM/FYT5DMkAc+19E4aLmd5KqdFyg== - -"@typescript-eslint/typescript-estree@5.62.0": - version "5.62.0" - resolved "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b" - integrity sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA== - dependencies: - "@typescript-eslint/types" "5.62.0" - "@typescript-eslint/visitor-keys" "5.62.0" - debug "^4.3.4" - globby "^11.1.0" - is-glob "^4.0.3" - semver "^7.3.7" - tsutils "^3.21.0" - -"@typescript-eslint/typescript-estree@6.7.2": - version "6.7.2" - resolved "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.2.tgz#ce5883c23b581a5caf878af641e49dd0349238c7" - integrity sha512-kiJKVMLkoSciGyFU0TOY0fRxnp9qq1AzVOHNeN1+B9erKFCJ4Z8WdjAkKQPP+b1pWStGFqezMLltxO+308dJTQ== - dependencies: - "@typescript-eslint/types" "6.7.2" - "@typescript-eslint/visitor-keys" "6.7.2" - debug "^4.3.4" - globby "^11.1.0" - is-glob "^4.0.3" - semver "^7.5.4" - ts-api-utils "^1.0.1" - -"@typescript-eslint/utils@5.62.0", "@typescript-eslint/utils@^5.10.0": - version "5.62.0" - resolved "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86" - integrity sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ== - dependencies: - "@eslint-community/eslint-utils" "^4.2.0" - "@types/json-schema" "^7.0.9" - "@types/semver" "^7.3.12" - "@typescript-eslint/scope-manager" "5.62.0" - "@typescript-eslint/types" "5.62.0" - "@typescript-eslint/typescript-estree" "5.62.0" - eslint-scope "^5.1.1" - semver "^7.3.7" - -"@typescript-eslint/utils@6.7.2": - version "6.7.2" - resolved "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-6.7.2.tgz#b9ef0da6f04932167a9222cb4ac59cb187165ebf" - integrity sha512-ZCcBJug/TS6fXRTsoTkgnsvyWSiXwMNiPzBUani7hDidBdj1779qwM1FIAmpH4lvlOZNF3EScsxxuGifjpLSWQ== - dependencies: - "@eslint-community/eslint-utils" "^4.4.0" - "@types/json-schema" "^7.0.12" - "@types/semver" "^7.5.0" - "@typescript-eslint/scope-manager" "6.7.2" - "@typescript-eslint/types" "6.7.2" - "@typescript-eslint/typescript-estree" "6.7.2" - semver "^7.5.4" - -"@typescript-eslint/visitor-keys@5.62.0": - version "5.62.0" - resolved "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e" - integrity sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw== - dependencies: - "@typescript-eslint/types" "5.62.0" - eslint-visitor-keys "^3.3.0" - -"@typescript-eslint/visitor-keys@6.7.2": - version "6.7.2" - resolved "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.2.tgz#4cb2bd786f1f459731b0ad1584c9f73e1c7a4d5c" - integrity sha512-uVw9VIMFBUTz8rIeaUT3fFe8xIUx8r4ywAdlQv1ifH+6acn/XF8Y6rwJ7XNmkNMDrTW+7+vxFFPIF40nJCVsMQ== - dependencies: - "@typescript-eslint/types" "6.7.2" - eslint-visitor-keys "^3.4.1" - -"@umijs/ast@4.0.87": - version "4.0.87" - resolved "https://registry.npmmirror.com/@umijs/ast/-/ast-4.0.87.tgz#056b1a8fbb834d4e80426552b3c0fbe03c5c5ba3" - integrity sha512-L5ZUBx2z3vy4zd2eob4QeBiT2LC7X+n2hcx+x11sgcS3czXsxXAG66Tq1/PmAsg9Lh7ApC9Bj+H/KX9QyfaINg== - dependencies: - "@umijs/bundler-utils" "4.0.87" - -"@umijs/babel-preset-umi@4.0.87": - version "4.0.87" - resolved "https://registry.npmmirror.com/@umijs/babel-preset-umi/-/babel-preset-umi-4.0.87.tgz#d62f43c5bd87af81acf10883b12868deeacef8a4" - integrity sha512-7Zh/n0uiBhF+IgRzx1lmDGa1STZUgjy4GtW5M3yfl6vewjDilnQWEQAZP24nS9PllNaVNT7umu52hOCJTEGyIA== - dependencies: - "@babel/runtime" "7.23.2" - "@bloomberg/record-tuple-polyfill" "0.0.4" - "@umijs/bundler-utils" "4.0.87" - "@umijs/utils" "4.0.87" - babel-plugin-styled-components "2.1.1" - core-js "3.28.0" - -"@umijs/bundler-esbuild@4.0.87": - version "4.0.87" - resolved "https://registry.npmmirror.com/@umijs/bundler-esbuild/-/bundler-esbuild-4.0.87.tgz#b5760ae6aee1d3a0279b70c018190a2c891fd1da" - integrity sha512-vw7A7FF97c/mIrYcHfP4Ql+tpHLyYDLmwxiHIMQCTqE6AI6ut6D4NDXyrXjWWWSJYsAG0AuFzchFplBGHOSe8w== - dependencies: - "@umijs/bundler-utils" "4.0.87" - "@umijs/utils" "4.0.87" - enhanced-resolve "5.9.3" - postcss "^8.4.21" - postcss-flexbugs-fixes "5.0.2" - postcss-preset-env "7.5.0" - -"@umijs/bundler-utils@4.0.72": - version "4.0.72" - resolved "https://registry.npmmirror.com/@umijs/bundler-utils/-/bundler-utils-4.0.72.tgz#8f993baca6374cda137ae1e33805b74e8937945e" - integrity sha512-ROGNx6dy3tiMwhC29F6xvWC9O3F4CXnND2raupljTk+QDuvc1hmwUiB/gmCWrts/98cKN2959js03ivIPn9NNw== - dependencies: - "@umijs/utils" "4.0.72" - esbuild "0.17.19" - regenerate "^1.4.2" - regenerate-unicode-properties "10.1.0" - spdy "^4.0.2" - -"@umijs/bundler-utils@4.0.87": - version "4.0.87" - resolved "https://registry.npmmirror.com/@umijs/bundler-utils/-/bundler-utils-4.0.87.tgz#2779ac8498d676fda558edc95e398a546f9d4b30" - integrity sha512-srn/u1K8ZQGp30k+lbkJWw7KCCOFdYwxC8Kkdq1T8t4a3MqC6motFxsbbHzGLUSKMBzFhcOSV/RGLSyHQ/WJuQ== - dependencies: - "@umijs/utils" "4.0.87" - esbuild "0.17.19" - regenerate "^1.4.2" - regenerate-unicode-properties "10.1.1" - spdy "^4.0.2" - -"@umijs/bundler-vite@4.0.87": - version "4.0.87" - resolved "https://registry.npmmirror.com/@umijs/bundler-vite/-/bundler-vite-4.0.87.tgz#2adc774eb8047ad381ebbf7e2d2a566f25b6b02f" - integrity sha512-7wqevmol3jEIxQfOzkJdmRD/T302aBrWmxihTIQwyaBZHRoMu76LJsnBIkpTw5ffDv0gwhNxEHW11ET6YklQ0w== - dependencies: - "@svgr/core" "6.5.1" - "@umijs/bundler-utils" "4.0.87" - "@umijs/utils" "4.0.87" - "@vitejs/plugin-react" "4.0.0" - core-js "3.28.0" - less "4.1.3" - postcss-preset-env "7.5.0" - rollup-plugin-visualizer "5.9.0" - systemjs "^6.14.1" - vite "4.3.1" - -"@umijs/bundler-webpack@4.0.87": - version "4.0.87" - resolved "https://registry.npmmirror.com/@umijs/bundler-webpack/-/bundler-webpack-4.0.87.tgz#5cd14fb08fcc9d7c8199a3aa02f24c35bd47b8e2" - integrity sha512-s2dzSiGbN4ws+MtpGIm3/I2w4/WIhIIDitWncjjBRarwpVraDJdPflWVIlVAsYkRlq1brJMoa4mdLPkVWyw65w== - dependencies: - "@svgr/core" "6.5.1" - "@svgr/plugin-jsx" "^6.5.1" - "@svgr/plugin-svgo" "^6.5.1" - "@types/hapi__joi" "17.1.9" - "@umijs/babel-preset-umi" "4.0.87" - "@umijs/bundler-utils" "4.0.87" - "@umijs/case-sensitive-paths-webpack-plugin" "^1.0.1" - "@umijs/mfsu" "4.0.87" - "@umijs/react-refresh-webpack-plugin" "0.5.11" - "@umijs/utils" "4.0.87" - cors "^2.8.5" - css-loader "6.7.1" - es5-imcompatible-versions "^0.1.78" - fork-ts-checker-webpack-plugin "8.0.0" - jest-worker "29.4.3" - lightningcss "1.19.0" - node-libs-browser "2.2.1" - postcss "^8.4.21" - postcss-preset-env "7.5.0" - react-error-overlay "6.0.9" - react-refresh "0.14.0" - -"@umijs/case-sensitive-paths-webpack-plugin@^1.0.1": - version "1.0.1" - resolved "https://registry.npmmirror.com/@umijs/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-1.0.1.tgz#02655299f52912289f2df28fbeaea636e748c1df" - integrity sha512-kDKJ8yTarxwxGJDInG33hOpaQRZ//XpNuuznQ/1Mscypw6kappzFmrBr2dOYave++K7JHouoANF354UpbEQw0Q== - -"@umijs/core@4.0.87": - version "4.0.87" - resolved "https://registry.npmmirror.com/@umijs/core/-/core-4.0.87.tgz#add5587200c8e5d3b2cf668cca8eb2d0c814e73f" - integrity sha512-LEVnrurQOiICMHwxDxyW8+ANTx841bXj2tYUI2tEv+yenrgeZnkUa7790B+UBhUuhImTXpihUAr3QhmIz63nqA== - dependencies: - "@umijs/bundler-utils" "4.0.87" - "@umijs/utils" "4.0.87" - -"@umijs/did-you-know@1.0.3": - version "1.0.3" - resolved "https://registry.npmmirror.com/@umijs/did-you-know/-/did-you-know-1.0.3.tgz#c7cc40f404dec6fe5500d16c9f87d8c1ddfbc781" - integrity sha512-9EZ+rgY9+2HEaE+Z9dGkal2ccw8L4uuz77tCB5WpskW7NBZX5nOj82sqF/shEtA5tU3SWO/Mi4n35K3iONvDtw== - -"@umijs/es-module-parser-darwin-arm64@0.0.7": - version "0.0.7" - resolved "https://registry.npmmirror.com/@umijs/es-module-parser-darwin-arm64/-/es-module-parser-darwin-arm64-0.0.7.tgz#7278f3487c586a3ee63bbf45f8504490bef2ebe0" - integrity sha512-1QeNupekuVYVvL4UHyCRq4ISP2PNk4rDd9UOPONW+KpqTyP9p7RfgGpwB0VLPaFSu2ADtm0XZyIaYEGPY6zuDw== - -"@umijs/es-module-parser-darwin-x64@0.0.7": - version "0.0.7" - resolved "https://registry.npmmirror.com/@umijs/es-module-parser-darwin-x64/-/es-module-parser-darwin-x64-0.0.7.tgz#e6e154ad19909d817ce36a65b3bcb1a4d99168f3" - integrity sha512-FBFmfigmToPc9qBCW7wHiTYpqnLdPbAvoMGOydzAu2NspdPEF7TfILcr8vCPNbNe3vCobS+T/YM1dP+SagERlA== - -"@umijs/es-module-parser-linux-arm-gnueabihf@0.0.7": - version "0.0.7" - resolved "https://registry.npmmirror.com/@umijs/es-module-parser-linux-arm-gnueabihf/-/es-module-parser-linux-arm-gnueabihf-0.0.7.tgz#6ab6b28f5abbb97b84cb4c9665279cd0dc67f7fe" - integrity sha512-AXfmg3htkadLGsXUyiyrTig4omGCWIN4l+HS7Qapqv0wlfFYSpC0KPemjyBQgzXO70tDcT+1FNhGjIy+yr2pIQ== - -"@umijs/es-module-parser-linux-arm64-gnu@0.0.7": - version "0.0.7" - resolved "https://registry.npmmirror.com/@umijs/es-module-parser-linux-arm64-gnu/-/es-module-parser-linux-arm64-gnu-0.0.7.tgz#a3c3bac9d6718a362f4612b16826c4daaff24a6a" - integrity sha512-2wSdChFc39fPJwvS8tRq+jx8qNlIwrjRk1hb3N5o0rJR+rqt+ceAyNPbYwpNBmUHW7xtmDQvJUeinvr7hIBP+w== - -"@umijs/es-module-parser-linux-arm64-musl@0.0.7": - version "0.0.7" - resolved "https://registry.npmmirror.com/@umijs/es-module-parser-linux-arm64-musl/-/es-module-parser-linux-arm64-musl-0.0.7.tgz#0c9ea0d46e7e14eafb6e93b940c9f3887025ee5a" - integrity sha512-cqQffARWkmQ3n1RYNKZR3aD6X8YaP6u1maASjDgPQOpZMAlv/OSDrM/7iGujWTs0PD0haockNG9/DcP6lgPHMw== - -"@umijs/es-module-parser-linux-x64-gnu@0.0.7": - version "0.0.7" - resolved "https://registry.npmmirror.com/@umijs/es-module-parser-linux-x64-gnu/-/es-module-parser-linux-x64-gnu-0.0.7.tgz#07c35db7eba4ff7b6f34ce7539fe38875ae27129" - integrity sha512-PHrKHtT665Za0Ydjch4ACrNpRU+WIIden12YyF1CtMdhuLDSoU6UfdhF3NoDbgEUcXVDX/ftOqmj0SbH3R1uew== - -"@umijs/es-module-parser-linux-x64-musl@0.0.7": - version "0.0.7" - resolved "https://registry.npmmirror.com/@umijs/es-module-parser-linux-x64-musl/-/es-module-parser-linux-x64-musl-0.0.7.tgz#0954cdde0d3a0c15f22d8712f52f31142b1b577e" - integrity sha512-cyZvUK5lcECLWzLp/eU1lFlCETcz+LEb+wrdARQSST1dgoIGZsT4cqM1WzYmdZNk3o883tiZizLt58SieEiHBQ== - -"@umijs/es-module-parser-win32-arm64-msvc@0.0.7": - version "0.0.7" - resolved "https://registry.npmmirror.com/@umijs/es-module-parser-win32-arm64-msvc/-/es-module-parser-win32-arm64-msvc-0.0.7.tgz#a3d07a733843e2b287a8135714fbd51950ae0de5" - integrity sha512-V7WxnUI88RboSl0RWLNQeKBT7EDW35fW6Tn92zqtoHHxrhAIL9DtDyvC8REP4qTxeZ6Oej/Ax5I6IjsLx3yTOg== - -"@umijs/es-module-parser-win32-x64-msvc@0.0.7": - version "0.0.7" - resolved "https://registry.npmmirror.com/@umijs/es-module-parser-win32-x64-msvc/-/es-module-parser-win32-x64-msvc-0.0.7.tgz#0b7dbfd611c1f6e2a067d56a0da69f129d42e408" - integrity sha512-X3Pqy0l38hg6wMPquPeMHuoHU+Cx+wzyz32SVYCta+RPJQ7n9PjrEBiIuVAw5+GJZjSABN7LVr8u/n0RZT9EQA== - -"@umijs/es-module-parser@0.0.7": - version "0.0.7" - resolved "https://registry.npmmirror.com/@umijs/es-module-parser/-/es-module-parser-0.0.7.tgz#7f6d7573a1725204dd6f2a67bb883cf20deed8e4" - integrity sha512-x47CMi/Hw7Nkz3RXTUqlldH/UM+Tcmw2PziV3k+itJqTFJc8oVx3lzdUgCnG+eL3ZtmLPbOEBhPb30V0NytNDQ== - optionalDependencies: - "@umijs/es-module-parser-darwin-arm64" "0.0.7" - "@umijs/es-module-parser-darwin-x64" "0.0.7" - "@umijs/es-module-parser-linux-arm-gnueabihf" "0.0.7" - "@umijs/es-module-parser-linux-arm64-gnu" "0.0.7" - "@umijs/es-module-parser-linux-arm64-musl" "0.0.7" - "@umijs/es-module-parser-linux-x64-gnu" "0.0.7" - "@umijs/es-module-parser-linux-x64-musl" "0.0.7" - "@umijs/es-module-parser-win32-arm64-msvc" "0.0.7" - "@umijs/es-module-parser-win32-x64-msvc" "0.0.7" - -"@umijs/history@5.3.1": - version "5.3.1" - resolved "https://registry.npmmirror.com/@umijs/history/-/history-5.3.1.tgz#947217594203bf9fd332f95e6113f50855d655b7" - integrity sha512-/e0cEGrR2bIWQD7pRl3dl9dcyRGeC9hoW0OCvUTT/hjY0EfUrkd6G8ZanVghPMpDuY5usxq9GVcvrT8KNXLWvA== - dependencies: - "@babel/runtime" "^7.7.6" - query-string "^6.13.6" - -"@umijs/lint@4.0.87": - version "4.0.87" - resolved "https://registry.npmmirror.com/@umijs/lint/-/lint-4.0.87.tgz#27001bc6a2d906402fe2ee21e1e5d03b7caa509c" - integrity sha512-TvdBzyqVvOYFUsWccaLcp8r0dS1QpZ75usFXCWJli4HeDZXPj9FlAqdGfbRM9SORj27pwI+55KilDfBZDJHx2g== - dependencies: - "@babel/core" "7.23.2" - "@babel/eslint-parser" "7.22.15" - "@stylelint/postcss-css-in-js" "^0.38.0" - "@typescript-eslint/eslint-plugin" "^5.62.0" - "@typescript-eslint/parser" "^5.62.0" - "@umijs/babel-preset-umi" "4.0.87" - eslint-plugin-jest "27.2.3" - eslint-plugin-react "7.33.2" - eslint-plugin-react-hooks "4.6.0" - postcss "^8.4.21" - postcss-syntax "0.36.2" - stylelint-config-standard "25.0.0" - -"@umijs/mfsu@4.0.87": - version "4.0.87" - resolved "https://registry.npmmirror.com/@umijs/mfsu/-/mfsu-4.0.87.tgz#d23dc72c0b6f624b8a584988c7ccfdd05674dee6" - integrity sha512-ROSY/WdjZX0/1jmUwW25mlIXlPt1XTWO3u0cORK4Cq/mAQTd4X66qH2tyvndKlYgMkn02XddGoXhMF2S+MaahA== - dependencies: - "@umijs/bundler-esbuild" "4.0.87" - "@umijs/bundler-utils" "4.0.87" - "@umijs/utils" "4.0.87" - enhanced-resolve "5.9.3" - is-equal "^1.6.4" - -"@umijs/plugin-run@4.0.87": - version "4.0.87" - resolved "https://registry.npmmirror.com/@umijs/plugin-run/-/plugin-run-4.0.87.tgz#b8a15f331130ce625ac73228019808534716073e" - integrity sha512-naaPGsJcNgJFnjyhuJ/H65D9jcX4JaTVeQjdg+IlN+EzV3yHbbNcxClJZHW9K+nyWTnXKJBa1ZSRImExoTOIvA== - dependencies: - tsx "^3.12.2" - -"@umijs/plugins@^4.0.55": - version "4.0.72" - resolved "https://registry.npmmirror.com/@umijs/plugins/-/plugins-4.0.72.tgz#ef2f50a47f86ffa7bd876f6eddde6b890f7038e2" - integrity sha512-AaCEjVdvSF+ilnATisBApMYMWVsLvBD4BUjkNV3SIaKHCLpSDeVNVyIFBm4A8tWLz+tz0p25Cu7SWGuggqQdQA== - dependencies: - "@ahooksjs/use-request" "^2.0.0" - "@ant-design/antd-theme-variable" "^1.0.0" - "@ant-design/cssinjs" "^1.9.1" - "@ant-design/icons" "^4.7.0" - "@ant-design/moment-webpack-plugin" "^0.0.3" - "@ant-design/pro-components" "^2.0.1" - "@tanstack/react-query" "^4.24.10" - "@tanstack/react-query-devtools" "^4.24.10" - "@umijs/bundler-utils" "4.0.72" - "@umijs/valtio" "1.0.3" - antd-dayjs-webpack-plugin "^1.0.6" - axios "^0.27.2" - babel-plugin-import "^1.13.6" - dayjs "^1.11.7" - dva-core "^2.0.4" - dva-immer "^1.0.0" - dva-loading "^3.0.22" - event-emitter "~0.3.5" - fast-deep-equal "3.1.3" - intl "1.2.5" - lodash "^4.17.21" - moment "^2.29.4" - qiankun "^2.10.1" - react-intl "3.12.1" - react-redux "^8.0.5" - redux "^4.2.1" - styled-components "6.0.0-rc.0" - tslib "^2" - warning "^4.0.3" - -"@umijs/preset-umi@4.0.87": - version "4.0.87" - resolved "https://registry.npmmirror.com/@umijs/preset-umi/-/preset-umi-4.0.87.tgz#24f490d720260c8b14e672dd79f0283a6c8237da" - integrity sha512-2sY5UIh8RPAZIW+LiZpnW5HDoInRD/uniupzbck9i1PZALPSePwhzUqDMeAsCFkW9jBs8sfbBmKab+bIhTxUCg== - dependencies: - "@iconify/utils" "2.1.1" - "@svgr/core" "6.5.1" - "@umijs/ast" "4.0.87" - "@umijs/babel-preset-umi" "4.0.87" - "@umijs/bundler-esbuild" "4.0.87" - "@umijs/bundler-utils" "4.0.87" - "@umijs/bundler-vite" "4.0.87" - "@umijs/bundler-webpack" "4.0.87" - "@umijs/core" "4.0.87" - "@umijs/did-you-know" "1.0.3" - "@umijs/es-module-parser" "0.0.7" - "@umijs/history" "5.3.1" - "@umijs/mfsu" "4.0.87" - "@umijs/plugin-run" "4.0.87" - "@umijs/renderer-react" "4.0.87" - "@umijs/server" "4.0.87" - "@umijs/ui" "3.0.1" - "@umijs/utils" "4.0.87" - "@umijs/zod2ts" "4.0.87" - babel-plugin-dynamic-import-node "2.3.3" - click-to-react-component "^1.0.8" - core-js "3.28.0" - current-script-polyfill "1.0.0" - enhanced-resolve "5.9.3" - fast-glob "3.2.12" - html-webpack-plugin "5.5.0" - less-plugin-resolve "1.0.0" - path-to-regexp "1.7.0" - postcss "^8.4.21" - postcss-prefix-selector "1.16.0" - react "18.1.0" - react-dom "18.1.0" - react-router "6.3.0" - react-router-dom "6.3.0" - regenerator-runtime "0.13.11" - -"@umijs/react-refresh-webpack-plugin@0.5.11": - version "0.5.11" - resolved "https://registry.npmmirror.com/@umijs/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.11.tgz#e45dd3004d2c02bb562425d91f9b6014469598c2" - integrity sha512-RtFvB+/GmjRhpHcqNgnw8iWZpTlxOnmNxi8eDcecxMmxmSgeDj25LV0jr4Q6rOhv3GTIfVGBhkwz+khGT5tfmg== - dependencies: - ansi-html-community "^0.0.8" - common-path-prefix "^3.0.0" - core-js-pure "^3.23.3" - error-stack-parser "^2.0.6" - find-up "^5.0.0" - html-entities "^2.1.0" - loader-utils "^2.0.4" - schema-utils "^3.0.0" - source-map "^0.7.3" - -"@umijs/renderer-react@4.0.87": - version "4.0.87" - resolved "https://registry.npmmirror.com/@umijs/renderer-react/-/renderer-react-4.0.87.tgz#30ec792de2f7e878ec7dcde56494696e072fe726" - integrity sha512-OOcB4bvmTAg8xdTFT9kxJXGUSulgFV7L6QNm2PK1cpbkn10xMVmKI6yW2jdr2Inn1uOgl/YSuWUnTbEIBSsM5Q== - dependencies: - "@babel/runtime" "7.23.2" - "@loadable/component" "5.15.2" - history "5.3.0" - react-helmet-async "1.3.0" - react-router-dom "6.3.0" - -"@umijs/route-utils@^4.0.0": - version "4.0.1" - resolved "https://registry.npmmirror.com/@umijs/route-utils/-/route-utils-4.0.1.tgz#156df5b3f2328059722d3ee7dd8f65e18c3cde8b" - integrity sha512-+1ixf1BTOLuH+ORb4x8vYMPeIt38n9q0fJDwhv9nSxrV46mxbLF0nmELIo9CKQB2gHfuC4+hww6xejJ6VYnBHQ== - -"@umijs/server@4.0.87": - version "4.0.87" - resolved "https://registry.npmmirror.com/@umijs/server/-/server-4.0.87.tgz#6585a637a0c9f6fc26517a7a8a4cde8e72346055" - integrity sha512-Q0fQob00Q9el+qIXB1jvV8WWhPWlz8oVdwUDxmijub52d0RVAfMsV0pHNNAB6j5y8TkX++hJ22TvW8a2aZs5PA== - dependencies: - "@umijs/bundler-utils" "4.0.87" - history "5.3.0" - react "18.1.0" - react-dom "18.1.0" - react-router-dom "6.3.0" - -"@umijs/test@4.0.87": - version "4.0.87" - resolved "https://registry.npmmirror.com/@umijs/test/-/test-4.0.87.tgz#b437639019cc05a0b7e914d42baa95d3ec8daf85" - integrity sha512-EiFRQquAuI0Wh2BhWO/Cws8aC673iSLaxLLE+maWAvM1531oO3klCATWQlRjFQhsoOIrFMGX2CuCMmryROTSuQ== - dependencies: - "@babel/plugin-transform-modules-commonjs" "7.23.0" - "@jest/types" "27.5.1" - "@umijs/bundler-utils" "4.0.87" - "@umijs/utils" "4.0.87" - babel-jest "^29.4.3" - esbuild "0.17.19" - identity-obj-proxy "3.0.0" - isomorphic-unfetch "4.0.2" - -"@umijs/ui@3.0.1": - version "3.0.1" - resolved "https://registry.npmmirror.com/@umijs/ui/-/ui-3.0.1.tgz#64ae7ef36bf9374823f7361a7a844876d96c9e06" - integrity sha512-zcz37AJH0xt/6XVVbyO/hmsK9Hq4vH23HZ4KYVi5A8rbM9KeJkJigTS7ELOdArawZhVNGe+h3a5Oixs4a2QsWw== - -"@umijs/use-params@^1.0.9": - version "1.0.9" - resolved "https://registry.npmmirror.com/@umijs/use-params/-/use-params-1.0.9.tgz#0ae4a87f4922d8e8e3fb4495b0f8f4de9ca38c52" - integrity sha512-QlN0RJSBVQBwLRNxbxjQ5qzqYIGn+K7USppMoIOVlf7fxXHsnQZ2bEsa6Pm74bt6DVQxpUE8HqvdStn6Y9FV1w== - -"@umijs/utils@4.0.72": - version "4.0.72" - resolved "https://registry.npmmirror.com/@umijs/utils/-/utils-4.0.72.tgz#e37ee3db7499c77b11c60973a49c3e22ab2aa268" - integrity sha512-+BOOGCipnr3iEzAliYrfFQeyQd3DrT1vMMXlsBqyD3Qh1owrSb/FsTvFTUYU0jrVgBc3MR5UneEBcPbqxq36Pw== - dependencies: - chokidar "3.5.3" - pino "7.11.0" - -"@umijs/utils@4.0.87": - version "4.0.87" - resolved "https://registry.npmmirror.com/@umijs/utils/-/utils-4.0.87.tgz#93cfe69a5895cdad827ea7c1d5bbf2cb0bb996ac" - integrity sha512-0gQetd/LIYzny/T10RhgbiHnHwFvscv9okbhAUepi0qZg0/LNsOAPWwCwODeK/HMXNlknPEU+nc4EvkuowZmlQ== - dependencies: - chokidar "3.5.3" - pino "7.11.0" - -"@umijs/valtio@1.0.3": - version "1.0.3" - resolved "https://registry.npmmirror.com/@umijs/valtio/-/valtio-1.0.3.tgz#0e02bc11dad2d79935e864cad36228c42d670be6" - integrity sha512-fjr1UMZLFOO+uy5YtLVcmvr+m2ZlU9rp04yXlCaPrKkdBg/UNPBVo6YS9TBx2v0a62gYaztLL3Put3dcNGH5tQ== - dependencies: - valtio "1.9.0" - -"@umijs/zod2ts@4.0.87": - version "4.0.87" - resolved "https://registry.npmmirror.com/@umijs/zod2ts/-/zod2ts-4.0.87.tgz#460c5f2395fd5563d460e5b2803dc73fca624975" - integrity sha512-oGesuMLfwsvWaVywJyEztY7K44/xL/Xp48Q4XjHhF5OlEeKcwmvezp7E7GOZgPrGXihdlDru0Dde1fdiM2LJnw== - -"@vitejs/plugin-react@4.0.0": - version "4.0.0" - resolved "https://registry.npmmirror.com/@vitejs/plugin-react/-/plugin-react-4.0.0.tgz#46d1c37c507447d10467be1c111595174555ef28" - integrity sha512-HX0XzMjL3hhOYm+0s95pb0Z7F8O81G7joUHgfDd/9J/ZZf5k4xX6QAMFkKsHFxaHlf6X7GD7+XuaZ66ULiJuhQ== - dependencies: - "@babel/core" "^7.21.4" - "@babel/plugin-transform-react-jsx-self" "^7.21.0" - "@babel/plugin-transform-react-jsx-source" "^7.19.6" - react-refresh "^0.14.0" - -"@xmldom/xmldom@^0.8.8": - version "0.8.9" - resolved "https://registry.npmmirror.com/@xmldom/xmldom/-/xmldom-0.8.9.tgz#b6ef7457e826be8049667ae673eda7876eb049be" - integrity sha512-4VSbbcMoxc4KLjb1gs96SRmi7w4h1SF+fCoiK0XaQX62buCc1G5d0DC5bJ9xJBNPDSVCmIrcl8BiYxzjrqaaJA== - -acorn-jsx@^5.3.2: - version "5.3.2" - resolved "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" - integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== - -acorn@^8.8.2, acorn@^8.9.0: - version "8.10.0" - resolved "https://registry.npmmirror.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" - integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== - -add-dom-event-listener@^1.1.0: - version "1.1.0" - resolved "https://registry.npmmirror.com/add-dom-event-listener/-/add-dom-event-listener-1.1.0.tgz#6a92db3a0dd0abc254e095c0f1dc14acbbaae310" - integrity sha512-WCxx1ixHT0GQU9hb0KI/mhgRQhnU+U3GvwY6ZvVjYq8rsihIGoaIOUbY0yMPBxLH5MDtr0kz3fisWGNcbWW7Jw== - dependencies: - object-assign "4.x" - -agent-base@6: - version "6.0.2" - resolved "https://registry.npmmirror.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" - integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== - dependencies: - debug "4" - -ahooks-v3-count@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/ahooks-v3-count/-/ahooks-v3-count-1.0.0.tgz#ddeb392e009ad6e748905b3cbf63a9fd8262ca80" - integrity sha512-V7uUvAwnimu6eh/PED4mCDjE7tokeZQLKlxg9lCTMPhN+NjsSbtdacByVlR1oluXQzD3MOw55wylDmQo4+S9ZQ== - -ahooks@^3.7.8: - version "3.7.8" - resolved "https://registry.npmmirror.com/ahooks/-/ahooks-3.7.8.tgz#3fa3c491cd153e884a32b0c4192fc72cf84c4332" - integrity sha512-e/NMlQWoCjaUtncNFIZk3FG1ImSkV/JhScQSkTqnftakRwdfZWSw6zzoWSG9OMYqPNs2MguDYBUFFC6THelWXA== - dependencies: - "@babel/runtime" "^7.21.0" - "@types/js-cookie" "^2.x.x" - ahooks-v3-count "^1.0.0" - dayjs "^1.9.1" - intersection-observer "^0.12.0" - js-cookie "^2.x.x" - lodash "^4.17.21" - resize-observer-polyfill "^1.5.1" - screenfull "^5.0.0" - tslib "^2.4.1" - -ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: - version "3.5.2" - resolved "https://registry.npmmirror.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" - integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== - -ajv@^6.10.0, ajv@^6.12.0, ajv@^6.12.4, ajv@^6.12.5: - version "6.12.6" - resolved "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== - 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" - -ali-react-table@^2.6.1: - version "2.6.1" - resolved "https://registry.npmmirror.com/ali-react-table/-/ali-react-table-2.6.1.tgz#ad6d07269943125bc71c98b81aa011413e70020c" - integrity sha512-YK/s+1fw7ckJDkm5Ln+DJXSXBMyZHlL6ixMSBt9iXroS8roplKuCVkDRIBDBtSv1bqe967BoAcG5MT06mPMQqA== - dependencies: - "@popperjs/core" "^2.9.1" - "@types/classnames" "^2.2.9" - classnames "^2.2.6" - resize-observer-polyfill "^1.5.1" - rxjs "^6.5.4" - styled-components "^3.4.10 || ^5.0.1" - -ansi-html-community@^0.0.8: - version "0.0.8" - resolved "https://registry.npmmirror.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz#69fbc4d6ccbe383f9736934ae34c3f8290f1bf41" - integrity sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw== - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -antd-dayjs-webpack-plugin@^1.0.6: - version "1.0.6" - resolved "https://registry.npmmirror.com/antd-dayjs-webpack-plugin/-/antd-dayjs-webpack-plugin-1.0.6.tgz#7d98bcb51422248b8cd4a32e352a0425a3bffa3a" - integrity sha512-UlK3BfA0iE2c5+Zz/Bd2iPAkT6cICtrKG4/swSik5MZweBHtgmu1aUQCHvICdiv39EAShdZy/edfP6mlkS/xXg== - -antd@^5.12.1: - version "5.12.1" - resolved "https://registry.npmmirror.com/antd/-/antd-5.12.1.tgz#ab155517a607b4f30f1ea7569ce4cf82697e9aa6" - integrity sha512-lDTg4U/4MxDD4OK0sLM3D0ge+5nHKj27dUj4ufF1FhQKPcRkVnkCWJ43gb1Cn+S3ybvz7yfsiEv0v+QqWJgPlA== - dependencies: - "@ant-design/colors" "^7.0.0" - "@ant-design/cssinjs" "^1.18.0" - "@ant-design/icons" "^5.2.6" - "@ant-design/react-slick" "~1.0.2" - "@babel/runtime" "^7.23.4" - "@ctrl/tinycolor" "^3.6.1" - "@rc-component/color-picker" "~1.4.1" - "@rc-component/mutate-observer" "^1.1.0" - "@rc-component/tour" "~1.11.0" - "@rc-component/trigger" "^1.18.2" - classnames "^2.3.2" - copy-to-clipboard "^3.3.3" - dayjs "^1.11.1" - qrcode.react "^3.1.0" - rc-cascader "~3.20.0" - rc-checkbox "~3.1.0" - rc-collapse "~3.7.2" - rc-dialog "~9.3.4" - rc-drawer "~6.5.2" - rc-dropdown "~4.1.0" - rc-field-form "~1.41.0" - rc-image "~7.5.1" - rc-input "~1.3.6" - rc-input-number "~8.4.0" - rc-mentions "~2.9.1" - rc-menu "~9.12.2" - rc-motion "^2.9.0" - rc-notification "~5.3.0" - rc-pagination "~4.0.1" - rc-picker "~3.14.6" - rc-progress "~3.5.1" - rc-rate "~2.12.0" - rc-resize-observer "^1.4.0" - rc-segmented "~2.2.2" - rc-select "~14.10.0" - rc-slider "~10.5.0" - rc-steps "~6.0.1" - rc-switch "~4.1.0" - rc-table "~7.36.0" - rc-tabs "~12.14.1" - rc-textarea "~1.5.3" - rc-tooltip "~6.1.2" - rc-tree "~5.8.2" - rc-tree-select "~5.15.0" - rc-upload "~4.3.5" - rc-util "^5.38.1" - scroll-into-view-if-needed "^3.1.0" - throttle-debounce "^5.0.0" - -any-promise@^1.0.0: - version "1.3.0" - resolved "https://registry.npmmirror.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" - integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== - -anymatch@^3.0.3, anymatch@~3.1.2: - version "3.1.3" - resolved "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" - integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -app-builder-bin@4.0.0: - version "4.0.0" - resolved "https://registry.npmmirror.com/app-builder-bin/-/app-builder-bin-4.0.0.tgz#1df8e654bd1395e4a319d82545c98667d7eed2f0" - integrity sha512-xwdG0FJPQMe0M0UA4Tz0zEB8rBJTRA5a476ZawAqiBkMv16GRK5xpXThOjMaEOFnZ6zabejjG4J3da0SXG63KA== - -app-builder-lib@23.6.0: - version "23.6.0" - resolved "https://registry.npmmirror.com/app-builder-lib/-/app-builder-lib-23.6.0.tgz#03cade02838c077db99d86212d61c5fc1d6da1a8" - integrity sha512-dQYDuqm/rmy8GSCE6Xl/3ShJg6Ab4bZJMT8KaTKGzT436gl1DN4REP3FCWfXoh75qGTJ+u+WsdnnpO9Jl8nyMA== - dependencies: - "7zip-bin" "~5.1.1" - "@develar/schema-utils" "~2.6.5" - "@electron/universal" "1.2.1" - "@malept/flatpak-bundler" "^0.4.0" - async-exit-hook "^2.0.1" - bluebird-lst "^1.0.9" - builder-util "23.6.0" - builder-util-runtime "9.1.1" - chromium-pickle-js "^0.2.0" - debug "^4.3.4" - ejs "^3.1.7" - electron-osx-sign "^0.6.0" - electron-publish "23.6.0" - form-data "^4.0.0" - fs-extra "^10.1.0" - hosted-git-info "^4.1.0" - is-ci "^3.0.0" - isbinaryfile "^4.0.10" - js-yaml "^4.1.0" - lazy-val "^1.0.5" - minimatch "^3.1.2" - read-config-file "6.2.0" - sanitize-filename "^1.6.3" - semver "^7.3.7" - tar "^6.1.11" - temp-file "^3.4.0" - -arg@^5.0.2: - version "5.0.2" - resolved "https://registry.npmmirror.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c" - integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg== - -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.npmmirror.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - -argparse@^2.0.1: - version "2.0.1" - resolved "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" - integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== - -aria-hidden@^1.1.3: - version "1.2.3" - resolved "https://registry.npmmirror.com/aria-hidden/-/aria-hidden-1.2.3.tgz#14aeb7fb692bbb72d69bebfa47279c1fd725e954" - integrity sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ== - dependencies: - tslib "^2.0.0" - -array-buffer-byte-length@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz#fabe8bc193fea865f317fe7807085ee0dee5aead" - integrity sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A== - dependencies: - call-bind "^1.0.2" - is-array-buffer "^3.0.1" - -array-includes@^3.1.6: - version "3.1.6" - resolved "https://registry.npmmirror.com/array-includes/-/array-includes-3.1.6.tgz#9e9e720e194f198266ba9e18c29e6a9b0e4b225f" - integrity sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" - get-intrinsic "^1.1.3" - is-string "^1.0.7" - -array-tree-filter@^2.1.0: - version "2.1.0" - resolved "https://registry.npmmirror.com/array-tree-filter/-/array-tree-filter-2.1.0.tgz#873ac00fec83749f255ac8dd083814b4f6329190" - integrity sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw== - -array-union@^2.1.0: - version "2.1.0" - resolved "https://registry.npmmirror.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" - integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== - -array.prototype.find@^2.2.1: - version "2.2.2" - resolved "https://registry.npmmirror.com/array.prototype.find/-/array.prototype.find-2.2.2.tgz#e862cf891e725d8f2a10e5e42d750629faaabd32" - integrity sha512-DRumkfW97iZGOfn+lIXbkVrXL04sfYKX+EfOodo8XboR5sxPDVvOjZTF/rysusa9lmhmSOeD6Vp6RKQP+eP4Tg== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - es-shim-unscopables "^1.0.0" - -array.prototype.findlastindex@^1.2.2: - version "1.2.3" - resolved "https://registry.npmmirror.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz#b37598438f97b579166940814e2c0493a4f50207" - integrity sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - es-shim-unscopables "^1.0.0" - get-intrinsic "^1.2.1" - -array.prototype.flat@^1.3.1: - version "1.3.1" - resolved "https://registry.npmmirror.com/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz#ffc6576a7ca3efc2f46a143b9d1dda9b4b3cf5e2" - integrity sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" - es-shim-unscopables "^1.0.0" - -array.prototype.flatmap@^1.3.1: - version "1.3.1" - resolved "https://registry.npmmirror.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz#1aae7903c2100433cb8261cd4ed310aab5c4a183" - integrity sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" - es-shim-unscopables "^1.0.0" - -array.prototype.tosorted@^1.1.1: - version "1.1.1" - resolved "https://registry.npmmirror.com/array.prototype.tosorted/-/array.prototype.tosorted-1.1.1.tgz#ccf44738aa2b5ac56578ffda97c03fd3e23dd532" - integrity sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" - es-shim-unscopables "^1.0.0" - get-intrinsic "^1.1.3" - -arraybuffer.prototype.slice@^1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.1.tgz#9b5ea3868a6eebc30273da577eb888381c0044bb" - integrity sha512-09x0ZWFEjj4WD8PDbykUwo3t9arLn8NIzmmYEJFpYekOAQjpkGSyrQhNoRTcwwcFRu+ycWF78QZ63oWTqSjBcw== - dependencies: - array-buffer-byte-length "^1.0.0" - call-bind "^1.0.2" - define-properties "^1.2.0" - get-intrinsic "^1.2.1" - is-array-buffer "^3.0.2" - is-shared-array-buffer "^1.0.2" - -arraybuffer.prototype.slice@^1.0.2: - version "1.0.2" - resolved "https://registry.npmmirror.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz#98bd561953e3e74bb34938e77647179dfe6e9f12" - integrity sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw== - dependencies: - array-buffer-byte-length "^1.0.0" - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - get-intrinsic "^1.2.1" - is-array-buffer "^3.0.2" - is-shared-array-buffer "^1.0.2" - -asar@^3.1.0: - version "3.2.0" - resolved "https://registry.npmmirror.com/asar/-/asar-3.2.0.tgz#e6edb5edd6f627ebef04db62f771c61bea9c1221" - integrity sha512-COdw2ZQvKdFGFxXwX3oYh2/sOsJWJegrdJCGxnN4MZ7IULgRBp9P6665aqj9z1v9VwP4oP1hRBojRDQ//IGgAg== - dependencies: - chromium-pickle-js "^0.2.0" - commander "^5.0.0" - glob "^7.1.6" - minimatch "^3.0.4" - optionalDependencies: - "@types/glob" "^7.1.1" - -asn1.js@^5.2.0: - version "5.4.1" - resolved "https://registry.npmmirror.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07" - integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA== - dependencies: - bn.js "^4.0.0" - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - safer-buffer "^2.1.0" - -assert-plus@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" - integrity sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw== - -assert@^1.1.1: - version "1.5.0" - resolved "https://registry.npmmirror.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb" - integrity sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA== - dependencies: - object-assign "^4.1.1" - util "0.10.3" - -astral-regex@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" - integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== - -async-exit-hook@^2.0.1: - version "2.0.1" - resolved "https://registry.npmmirror.com/async-exit-hook/-/async-exit-hook-2.0.1.tgz#8bd8b024b0ec9b1c01cccb9af9db29bd717dfaf3" - integrity sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw== - -async-validator@^4.1.0: - version "4.2.5" - resolved "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz#c96ea3332a521699d0afaaceed510a54656c6339" - integrity sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg== - -async@^3.2.3: - version "3.2.4" - resolved "https://registry.npmmirror.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" - integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ== - -asynciterator.prototype@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz#8c5df0514936cdd133604dfcc9d3fb93f09b2b62" - integrity sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg== - dependencies: - has-symbols "^1.0.3" - -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== - -at-least-node@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" - integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== - -atomic-sleep@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" - integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ== - -autoprefixer@^10.4.6: - version "10.4.14" - resolved "https://registry.npmmirror.com/autoprefixer/-/autoprefixer-10.4.14.tgz#e28d49902f8e759dd25b153264e862df2705f79d" - integrity sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ== - dependencies: - browserslist "^4.21.5" - caniuse-lite "^1.0.30001464" - fraction.js "^4.2.0" - normalize-range "^0.1.2" - picocolors "^1.0.0" - postcss-value-parser "^4.2.0" - -available-typed-arrays@^1.0.5: - version "1.0.5" - resolved "https://registry.npmmirror.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" - integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== - -axios@^0.27.2: - version "0.27.2" - resolved "https://registry.npmmirror.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972" - integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ== - dependencies: - follow-redirects "^1.14.9" - form-data "^4.0.0" - -babel-jest@^29.4.3: - version "29.6.1" - resolved "https://registry.npmmirror.com/babel-jest/-/babel-jest-29.6.1.tgz#a7141ad1ed5ec50238f3cd36127636823111233a" - integrity sha512-qu+3bdPEQC6KZSPz+4Fyjbga5OODNcp49j6GKzG1EKbkfyJBxEYGVUmVGpwCSeGouG52R4EgYMLb6p9YeEEQ4A== - dependencies: - "@jest/transform" "^29.6.1" - "@types/babel__core" "^7.1.14" - babel-plugin-istanbul "^6.1.1" - babel-preset-jest "^29.5.0" - chalk "^4.0.0" - graceful-fs "^4.2.9" - slash "^3.0.0" - -babel-plugin-dynamic-import-node@2.3.3: - version "2.3.3" - resolved "https://registry.npmmirror.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3" - integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ== - dependencies: - object.assign "^4.1.0" - -babel-plugin-import@^1.13.6: - version "1.13.6" - resolved "https://registry.npmmirror.com/babel-plugin-import/-/babel-plugin-import-1.13.6.tgz#4ff2aa3b9759e6a4458ce59890da3684fe3dda9d" - integrity sha512-N7FYnGh0DFsvDRkAPsvFq/metVfVD7P2h1rokOPpEH4cZbdRHCW+2jbXt0nnuqowkm/xhh2ww1anIdEpfYa7ZA== - dependencies: - "@babel/helper-module-imports" "^7.0.0" - -babel-plugin-istanbul@^6.1.1: - version "6.1.1" - resolved "https://registry.npmmirror.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" - integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@istanbuljs/load-nyc-config" "^1.0.0" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-instrument "^5.0.4" - test-exclude "^6.0.0" - -babel-plugin-jest-hoist@^29.5.0: - version "29.5.0" - resolved "https://registry.npmmirror.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz#a97db437936f441ec196990c9738d4b88538618a" - integrity sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w== - dependencies: - "@babel/template" "^7.3.3" - "@babel/types" "^7.3.3" - "@types/babel__core" "^7.1.14" - "@types/babel__traverse" "^7.0.6" - -babel-plugin-polyfill-corejs2@^0.4.4: - version "0.4.4" - resolved "https://registry.npmmirror.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.4.tgz#9f9a0e1cd9d645cc246a5e094db5c3aa913ccd2b" - integrity sha512-9WeK9snM1BfxB38goUEv2FLnA6ja07UMfazFHzCXUb3NyDZAwfXvQiURQ6guTTMeHcOsdknULm1PDhs4uWtKyA== - dependencies: - "@babel/compat-data" "^7.22.6" - "@babel/helper-define-polyfill-provider" "^0.4.1" - "@nicolo-ribaudo/semver-v6" "^6.3.3" - -babel-plugin-polyfill-corejs3@^0.8.2: - version "0.8.2" - resolved "https://registry.npmmirror.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.2.tgz#d406c5738d298cd9c66f64a94cf8d5904ce4cc5e" - integrity sha512-Cid+Jv1BrY9ReW9lIfNlNpsI53N+FN7gE+f73zLAUbr9C52W4gKLWSByx47pfDJsEysojKArqOtOKZSVIIUTuQ== - dependencies: - "@babel/helper-define-polyfill-provider" "^0.4.1" - core-js-compat "^3.31.0" - -babel-plugin-polyfill-regenerator@^0.5.1: - version "0.5.1" - resolved "https://registry.npmmirror.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.1.tgz#ace7a5eced6dff7d5060c335c52064778216afd3" - integrity sha512-L8OyySuI6OSQ5hFy9O+7zFjyr4WhAfRjLIOkhQGYl+emwJkd/S4XXT1JpfrgR1jrQ1NcGiOh+yAdGlF8pnC3Jw== - dependencies: - "@babel/helper-define-polyfill-provider" "^0.4.1" - -babel-plugin-styled-components@2.1.1: - version "2.1.1" - resolved "https://registry.npmmirror.com/babel-plugin-styled-components/-/babel-plugin-styled-components-2.1.1.tgz#cd977cc0ff8410d5cbfdd142e42576e9c8794b87" - integrity sha512-c8lJlszObVQPguHkI+akXv8+Jgb9Ccujx0EetL7oIvwU100LxO6XAGe45qry37wUL40a5U9f23SYrivro2XKhA== - dependencies: - "@babel/helper-annotate-as-pure" "^7.16.0" - "@babel/helper-module-imports" "^7.16.0" - babel-plugin-syntax-jsx "^6.18.0" - lodash "^4.17.21" - picomatch "^2.3.0" - -"babel-plugin-styled-components@>= 1.12.0": - version "2.1.4" - resolved "https://registry.npmmirror.com/babel-plugin-styled-components/-/babel-plugin-styled-components-2.1.4.tgz#9a1f37c7f32ef927b4b008b529feb4a2c82b1092" - integrity sha512-Xgp9g+A/cG47sUyRwwYxGM4bR/jDRg5N6it/8+HxCnbT5XNKSKDT9xm4oag/osgqjC2It/vH0yXsomOG6k558g== - dependencies: - "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-module-imports" "^7.22.5" - "@babel/plugin-syntax-jsx" "^7.22.5" - lodash "^4.17.21" - picomatch "^2.3.1" - -babel-plugin-syntax-jsx@^6.18.0: - version "6.18.0" - resolved "https://registry.npmmirror.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946" - integrity sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw== - -babel-preset-current-node-syntax@^1.0.0: - version "1.0.1" - resolved "https://registry.npmmirror.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" - integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== - dependencies: - "@babel/plugin-syntax-async-generators" "^7.8.4" - "@babel/plugin-syntax-bigint" "^7.8.3" - "@babel/plugin-syntax-class-properties" "^7.8.3" - "@babel/plugin-syntax-import-meta" "^7.8.3" - "@babel/plugin-syntax-json-strings" "^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators" "^7.8.3" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-syntax-numeric-separator" "^7.8.3" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" - "@babel/plugin-syntax-top-level-await" "^7.8.3" - -babel-preset-jest@^29.5.0: - version "29.5.0" - resolved "https://registry.npmmirror.com/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz#57bc8cc88097af7ff6a5ab59d1cd29d52a5916e2" - integrity sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg== - dependencies: - babel-plugin-jest-hoist "^29.5.0" - babel-preset-current-node-syntax "^1.0.0" - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -base64-js@^1.0.2, base64-js@^1.3.1, base64-js@^1.5.1: - version "1.5.1" - resolved "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" - integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== - -big-integer@^1.6.44: - version "1.6.51" - resolved "https://registry.npmmirror.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" - integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== - -big.js@^5.2.2: - version "5.2.2" - resolved "https://registry.npmmirror.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" - integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== - -binary-extensions@^2.0.0: - version "2.2.0" - resolved "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" - integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== - -bluebird-lst@^1.0.9: - version "1.0.9" - resolved "https://registry.npmmirror.com/bluebird-lst/-/bluebird-lst-1.0.9.tgz#a64a0e4365658b9ab5fe875eb9dfb694189bb41c" - integrity sha512-7B1Rtx82hjnSD4PGLAjVWeYH3tHAcVUmChh85a3lltKQm6FresXh9ErQo6oAv6CqxttczC3/kEg8SY5NluPuUw== - dependencies: - bluebird "^3.5.5" - -bluebird@^3.5.0, bluebird@^3.5.5: - version "3.7.2" - resolved "https://registry.npmmirror.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" - integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== - -bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9: - version "4.12.0" - resolved "https://registry.npmmirror.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" - integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== - -bn.js@^5.0.0, bn.js@^5.1.1: - version "5.2.1" - resolved "https://registry.npmmirror.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" - integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== - -boolbase@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" - integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== - -boolean@^3.0.1: - version "3.2.0" - resolved "https://registry.npmmirror.com/boolean/-/boolean-3.2.0.tgz#9e5294af4e98314494cbb17979fa54ca159f116b" - integrity sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw== - -bplist-parser@^0.2.0: - version "0.2.0" - resolved "https://registry.npmmirror.com/bplist-parser/-/bplist-parser-0.2.0.tgz#43a9d183e5bf9d545200ceac3e712f79ebbe8d0e" - integrity sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw== - dependencies: - big-integer "^1.6.44" - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -brace-expansion@^2.0.1: - version "2.0.1" - resolved "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" - integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== - dependencies: - balanced-match "^1.0.0" - -braces@^3.0.2, braces@~3.0.2: - version "3.0.2" - resolved "https://registry.npmmirror.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== - dependencies: - fill-range "^7.0.1" - -brorand@^1.0.1, brorand@^1.1.0: - version "1.1.0" - resolved "https://registry.npmmirror.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" - integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== - -browserify-aes@^1.0.0, browserify-aes@^1.0.4: - version "1.2.0" - resolved "https://registry.npmmirror.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" - integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== - dependencies: - buffer-xor "^1.0.3" - cipher-base "^1.0.0" - create-hash "^1.1.0" - evp_bytestokey "^1.0.3" - inherits "^2.0.1" - safe-buffer "^5.0.1" - -browserify-cipher@^1.0.0: - version "1.0.1" - resolved "https://registry.npmmirror.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" - integrity sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w== - dependencies: - browserify-aes "^1.0.4" - browserify-des "^1.0.0" - evp_bytestokey "^1.0.0" - -browserify-des@^1.0.0: - version "1.0.2" - resolved "https://registry.npmmirror.com/browserify-des/-/browserify-des-1.0.2.tgz#3af4f1f59839403572f1c66204375f7a7f703e9c" - integrity sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A== - dependencies: - cipher-base "^1.0.1" - des.js "^1.0.0" - inherits "^2.0.1" - safe-buffer "^5.1.2" - -browserify-rsa@^4.0.0, browserify-rsa@^4.0.1: - version "4.1.0" - resolved "https://registry.npmmirror.com/browserify-rsa/-/browserify-rsa-4.1.0.tgz#b2fd06b5b75ae297f7ce2dc651f918f5be158c8d" - integrity sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog== - dependencies: - bn.js "^5.0.0" - randombytes "^2.0.1" - -browserify-sign@^4.0.0: - version "4.2.1" - resolved "https://registry.npmmirror.com/browserify-sign/-/browserify-sign-4.2.1.tgz#eaf4add46dd54be3bb3b36c0cf15abbeba7956c3" - integrity sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg== - dependencies: - bn.js "^5.1.1" - browserify-rsa "^4.0.1" - create-hash "^1.2.0" - create-hmac "^1.1.7" - elliptic "^6.5.3" - inherits "^2.0.4" - parse-asn1 "^5.1.5" - readable-stream "^3.6.0" - safe-buffer "^5.2.0" - -browserify-zlib@^0.2.0: - version "0.2.0" - resolved "https://registry.npmmirror.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" - integrity sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA== - dependencies: - pako "~1.0.5" - -browserslist@^4.20.3, browserslist@^4.21.5, browserslist@^4.21.9: - version "4.21.9" - resolved "https://registry.npmmirror.com/browserslist/-/browserslist-4.21.9.tgz#e11bdd3c313d7e2a9e87e8b4b0c7872b13897635" - integrity sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg== - dependencies: - caniuse-lite "^1.0.30001503" - electron-to-chromium "^1.4.431" - node-releases "^2.0.12" - update-browserslist-db "^1.0.11" - -bser@2.1.1: - version "2.1.1" - resolved "https://registry.npmmirror.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" - integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== - dependencies: - node-int64 "^0.4.0" - -buffer-alloc-unsafe@^1.1.0: - version "1.1.0" - resolved "https://registry.npmmirror.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0" - integrity sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg== - -buffer-alloc@^1.2.0: - version "1.2.0" - resolved "https://registry.npmmirror.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec" - integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow== - dependencies: - buffer-alloc-unsafe "^1.1.0" - buffer-fill "^1.0.0" - -buffer-crc32@~0.2.3: - version "0.2.13" - resolved "https://registry.npmmirror.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" - integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== - -buffer-equal@1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe" - integrity sha512-tcBWO2Dl4e7Asr9hTGcpVrCe+F7DubpmqWCTbj4FHLmjqO2hIaC383acQubWtRJhdceqs5uBHs6Es+Sk//RKiQ== - -buffer-fill@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c" - integrity sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ== - -buffer-from@^1.0.0: - version "1.1.2" - resolved "https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - -buffer-xor@^1.0.3: - version "1.0.3" - resolved "https://registry.npmmirror.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" - integrity sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ== - -buffer@^4.3.0: - version "4.9.2" - resolved "https://registry.npmmirror.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" - integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg== - dependencies: - base64-js "^1.0.2" - ieee754 "^1.1.4" - isarray "^1.0.0" - -buffer@^5.1.0: - version "5.7.1" - resolved "https://registry.npmmirror.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" - integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.1.13" - -builder-util-runtime@9.1.1: - version "9.1.1" - resolved "https://registry.npmmirror.com/builder-util-runtime/-/builder-util-runtime-9.1.1.tgz#2da7b34e78a64ad14ccd070d6eed4662d893bd60" - integrity sha512-azRhYLEoDvRDR8Dhis4JatELC/jUvYjm4cVSj7n9dauGTOM2eeNn9KS0z6YA6oDsjI1xphjNbY6PZZeHPzzqaw== - dependencies: - debug "^4.3.4" - sax "^1.2.4" - -builder-util@23.6.0: - version "23.6.0" - resolved "https://registry.npmmirror.com/builder-util/-/builder-util-23.6.0.tgz#1880ec6da7da3fd6fa19b8bd71df7f39e8d17dd9" - integrity sha512-QiQHweYsh8o+U/KNCZFSvISRnvRctb8m/2rB2I1JdByzvNKxPeFLlHFRPQRXab6aYeXc18j9LpsDLJ3sGQmWTQ== - dependencies: - "7zip-bin" "~5.1.1" - "@types/debug" "^4.1.6" - "@types/fs-extra" "^9.0.11" - app-builder-bin "4.0.0" - bluebird-lst "^1.0.9" - builder-util-runtime "9.1.1" - chalk "^4.1.1" - cross-spawn "^7.0.3" - debug "^4.3.4" - fs-extra "^10.0.0" - http-proxy-agent "^5.0.0" - https-proxy-agent "^5.0.0" - is-ci "^3.0.0" - js-yaml "^4.1.0" - source-map-support "^0.5.19" - stat-mode "^1.0.0" - temp-file "^3.4.0" - -builtin-status-codes@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" - integrity sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ== - -bundle-name@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/bundle-name/-/bundle-name-3.0.0.tgz#ba59bcc9ac785fb67ccdbf104a2bf60c099f0e1a" - integrity sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw== - dependencies: - run-applescript "^5.0.0" - -cacheable-lookup@^5.0.3: - version "5.0.4" - resolved "https://registry.npmmirror.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" - integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA== - -cacheable-request@^7.0.2: - version "7.0.4" - resolved "https://registry.npmmirror.com/cacheable-request/-/cacheable-request-7.0.4.tgz#7a33ebf08613178b403635be7b899d3e69bbe817" - integrity sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg== - dependencies: - clone-response "^1.0.2" - get-stream "^5.1.0" - http-cache-semantics "^4.0.0" - keyv "^4.0.0" - lowercase-keys "^2.0.0" - normalize-url "^6.0.1" - responselike "^2.0.0" - -call-bind@^1.0.0, call-bind@^1.0.2: - version "1.0.2" - resolved "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" - integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== - dependencies: - function-bind "^1.1.1" - get-intrinsic "^1.0.2" - -callsites@^3.0.0: - version "3.1.0" - resolved "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" - integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== - -camel-case@^4.1.2: - version "4.1.2" - resolved "https://registry.npmmirror.com/camel-case/-/camel-case-4.1.2.tgz#9728072a954f805228225a6deea6b38461e1bd5a" - integrity sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw== - dependencies: - pascal-case "^3.1.2" - tslib "^2.0.3" - -camelcase-css@^2.0.1: - version "2.0.1" - resolved "https://registry.npmmirror.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" - integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== - -camelcase@^5.3.1: - version "5.3.1" - resolved "https://registry.npmmirror.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" - integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== - -camelcase@^6.2.0: - version "6.3.0" - resolved "https://registry.npmmirror.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" - integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== - -camelize@^1.0.0: - version "1.0.1" - resolved "https://registry.npmmirror.com/camelize/-/camelize-1.0.1.tgz#89b7e16884056331a35d6b5ad064332c91daa6c3" - integrity sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ== - -caniuse-lite@^1.0.30001464, caniuse-lite@^1.0.30001503: - version "1.0.30001517" - resolved "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001517.tgz#90fabae294215c3495807eb24fc809e11dc2f0a8" - integrity sha512-Vdhm5S11DaFVLlyiKu4hiUTkpZu+y1KA/rZZqVQfOD5YdDT/eQKlkt7NaE0WGOFgX32diqt9MiP9CAiFeRklaA== - -chalk@^2.0.0, chalk@^2.4.2: - version "2.4.2" - resolved "https://registry.npmmirror.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.1, chalk@^4.1.2: - version "4.1.2" - resolved "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -chokidar@3.5.3, chokidar@^3.4.0, chokidar@^3.5.3: - version "3.5.3" - resolved "https://registry.npmmirror.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" - integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== - 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" - optionalDependencies: - fsevents "~2.3.2" - -chownr@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" - integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== - -chromium-pickle-js@^0.2.0: - version "0.2.0" - resolved "https://registry.npmmirror.com/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz#04a106672c18b085ab774d983dfa3ea138f22205" - integrity sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw== - -ci-info@^3.2.0: - version "3.8.0" - resolved "https://registry.npmmirror.com/ci-info/-/ci-info-3.8.0.tgz#81408265a5380c929f0bc665d62256628ce9ef91" - integrity sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw== - -cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: - version "1.0.4" - resolved "https://registry.npmmirror.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" - integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - -classnames@*, classnames@2.x, classnames@^2.2.1, classnames@^2.2.3, classnames@^2.2.5, classnames@^2.2.6, classnames@^2.3.1, classnames@^2.3.2: - version "2.3.2" - resolved "https://registry.npmmirror.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924" - integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw== - -classnames@2.3.1: - version "2.3.1" - resolved "https://registry.npmmirror.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e" - integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA== - -clean-css@^5.2.2: - version "5.3.2" - resolved "https://registry.npmmirror.com/clean-css/-/clean-css-5.3.2.tgz#70ecc7d4d4114921f5d298349ff86a31a9975224" - integrity sha512-JVJbM+f3d3Q704rF4bqQ5UUyTtuJ0JRKNbTKVEeujCCBoMdkEi+V+e8oktO9qGQNSvHrFTM6JZRXrUvGR1czww== - dependencies: - source-map "~0.6.0" - -cli-truncate@^2.1.0: - version "2.1.0" - resolved "https://registry.npmmirror.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7" - integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg== - dependencies: - slice-ansi "^3.0.0" - string-width "^4.2.0" - -click-to-react-component@^1.0.8: - version "1.0.8" - resolved "https://registry.npmmirror.com/click-to-react-component/-/click-to-react-component-1.0.8.tgz#bcad2f4551dde67c54cec77e02791c7ecda50e5a" - integrity sha512-YBNYOp00udy+NBEnUmM/3Df0Yco1iHNQ8k0ltlJVcDYK9AuYt14xPoJicBh/BokLqbzkci1p+pbdY5r4JXZC4g== - dependencies: - "@floating-ui/react-dom-interactions" "^0.3.1" - htm "^3.1.0" - react-merge-refs "^1.1.0" - -cliui@^8.0.1: - version "8.0.1" - resolved "https://registry.npmmirror.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" - integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.1" - wrap-ansi "^7.0.0" - -clone-response@^1.0.2: - version "1.0.3" - resolved "https://registry.npmmirror.com/clone-response/-/clone-response-1.0.3.tgz#af2032aa47816399cf5f0a1d0db902f517abb8c3" - integrity sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA== - dependencies: - mimic-response "^1.0.0" - -color-convert@^1.9.0: - version "1.9.3" - resolved "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.npmmirror.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== - -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -colors@1.0.3: - version "1.0.3" - resolved "https://registry.npmmirror.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" - integrity sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw== - -combined-stream@^1.0.8: - version "1.0.8" - resolved "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - -commander@2.9.0: - version "2.9.0" - resolved "https://registry.npmmirror.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" - integrity sha512-bmkUukX8wAOjHdN26xj5c4ctEV22TQ7dQYhSmuckKhToXrkUn0iIaolHdIxYYqD55nhpSPA9zPQ1yP57GdXP2A== - dependencies: - graceful-readlink ">= 1.0.0" - -commander@^2.19.0, commander@^2.20.0: - version "2.20.3" - resolved "https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" - integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== - -commander@^4.0.0, commander@^4.0.1: - version "4.1.1" - resolved "https://registry.npmmirror.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" - integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== - -commander@^5.0.0: - version "5.1.0" - resolved "https://registry.npmmirror.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" - integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== - -commander@^7.2.0: - version "7.2.0" - resolved "https://registry.npmmirror.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" - integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== - -commander@^8.3.0: - version "8.3.0" - resolved "https://registry.npmmirror.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" - integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== - -common-path-prefix@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/common-path-prefix/-/common-path-prefix-3.0.0.tgz#7d007a7e07c58c4b4d5f433131a19141b29f11e0" - integrity sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w== - -compare-version@^0.1.2: - version "0.1.2" - resolved "https://registry.npmmirror.com/compare-version/-/compare-version-0.1.2.tgz#0162ec2d9351f5ddd59a9202cba935366a725080" - integrity sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A== - -compute-scroll-into-view@^3.0.2: - version "3.0.3" - resolved "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-3.0.3.tgz#c418900a5c56e2b04b885b54995df164535962b1" - integrity sha512-nadqwNxghAGTamwIqQSG433W6OADZx2vCo3UXHNrzTRHK/htu+7+L0zhjEoaeaQVNAi3YgqWDv8+tzf0hRfR+A== - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== - -concurrently@^8.1.0: - version "8.2.0" - resolved "https://registry.npmmirror.com/concurrently/-/concurrently-8.2.0.tgz#cdc9f621a4d913366600355d68254df2c5e782f3" - integrity sha512-nnLMxO2LU492mTUj9qX/az/lESonSZu81UznYDoXtz1IQf996ixVqPAgHXwvHiHCAef/7S8HIK+fTFK7Ifk8YA== - dependencies: - chalk "^4.1.2" - date-fns "^2.30.0" - lodash "^4.17.21" - rxjs "^7.8.1" - shell-quote "^1.8.1" - spawn-command "0.0.2" - supports-color "^8.1.1" - tree-kill "^1.2.2" - yargs "^17.7.2" - -confusing-browser-globals@^1.0.10: - version "1.0.11" - resolved "https://registry.npmmirror.com/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz#ae40e9b57cdd3915408a2805ebd3a5585608dc81" - integrity sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA== - -console-browserify@^1.1.0: - version "1.2.0" - resolved "https://registry.npmmirror.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" - integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA== - -constants-browserify@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" - integrity sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ== - -convert-source-map@^1.1.0, convert-source-map@^1.7.0: - version "1.9.0" - resolved "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" - integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== - -convert-source-map@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" - integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== - -copy-anything@^2.0.1: - version "2.0.6" - resolved "https://registry.npmmirror.com/copy-anything/-/copy-anything-2.0.6.tgz#092454ea9584a7b7ad5573062b2a87f5900fc480" - integrity sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw== - dependencies: - is-what "^3.14.1" - -copy-anything@^3.0.2: - version "3.0.5" - resolved "https://registry.npmmirror.com/copy-anything/-/copy-anything-3.0.5.tgz#2d92dce8c498f790fa7ad16b01a1ae5a45b020a0" - integrity sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w== - dependencies: - is-what "^4.1.8" - -copy-to-clipboard@^3.3.3: - version "3.3.3" - resolved "https://registry.npmmirror.com/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz#55ac43a1db8ae639a4bd99511c148cdd1b83a1b0" - integrity sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA== - dependencies: - toggle-selection "^1.0.6" - -core-js-compat@^3.31.0: - version "3.31.1" - resolved "https://registry.npmmirror.com/core-js-compat/-/core-js-compat-3.31.1.tgz#5084ad1a46858df50ff89ace152441a63ba7aae0" - integrity sha512-wIDWd2s5/5aJSdpOJHfSibxNODxoGoWOBHt8JSPB41NOE94M7kuTPZCYLOlTtuoXTsBPKobpJ6T+y0SSy5L9SA== - dependencies: - browserslist "^4.21.9" - -core-js-pure@^3.23.3: - version "3.31.1" - resolved "https://registry.npmmirror.com/core-js-pure/-/core-js-pure-3.31.1.tgz#73d154958881873bc19381df80bddb20c8d0cdb5" - integrity sha512-w+C62kvWti0EPs4KPMCMVv9DriHSXfQOCQ94bGGBiEW5rrbtt/Rz8n5Krhfw9cpFyzXBjf3DB3QnPdEzGDY4Fw== - -core-js@3.28.0: - version "3.28.0" - resolved "https://registry.npmmirror.com/core-js/-/core-js-3.28.0.tgz#ed8b9e99c273879fdfff0edfc77ee709a5800e4a" - integrity sha512-GiZn9D4Z/rSYvTeg1ljAIsEqFm0LaN9gVtwDCrKL80zHtS31p9BAjmTxVqTQDMpwlMolJZOFntUG2uwyj7DAqw== - -core-util-is@1.0.2: - version "1.0.2" - resolved "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ== - -core-util-is@~1.0.0: - version "1.0.3" - resolved "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" - integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== - -cors@^2.8.5: - version "2.8.5" - resolved "https://registry.npmmirror.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" - integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== - dependencies: - object-assign "^4" - vary "^1" - -cosmiconfig@^7.0.1: - version "7.1.0" - resolved "https://registry.npmmirror.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6" - integrity sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA== - dependencies: - "@types/parse-json" "^4.0.0" - import-fresh "^3.2.1" - parse-json "^5.0.0" - path-type "^4.0.0" - yaml "^1.10.0" - -crc@^3.8.0: - version "3.8.0" - resolved "https://registry.npmmirror.com/crc/-/crc-3.8.0.tgz#ad60269c2c856f8c299e2c4cc0de4556914056c6" - integrity sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ== - dependencies: - buffer "^5.1.0" - -create-ecdh@^4.0.0: - version "4.0.4" - resolved "https://registry.npmmirror.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e" - integrity sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A== - dependencies: - bn.js "^4.1.0" - elliptic "^6.5.3" - -create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: - version "1.2.0" - resolved "https://registry.npmmirror.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" - integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== - dependencies: - cipher-base "^1.0.1" - inherits "^2.0.1" - md5.js "^1.3.4" - ripemd160 "^2.0.1" - sha.js "^2.4.0" - -create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: - version "1.1.7" - resolved "https://registry.npmmirror.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" - integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== - dependencies: - cipher-base "^1.0.3" - create-hash "^1.1.0" - inherits "^2.0.1" - ripemd160 "^2.0.0" - safe-buffer "^5.0.1" - sha.js "^2.4.8" - -cross-env@^7.0.3: - version "7.0.3" - resolved "https://registry.npmmirror.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf" - integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw== - dependencies: - cross-spawn "^7.0.1" - -cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -crypto-browserify@^3.11.0: - version "3.12.0" - resolved "https://registry.npmmirror.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" - integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== - dependencies: - browserify-cipher "^1.0.0" - browserify-sign "^4.0.0" - create-ecdh "^4.0.0" - create-hash "^1.1.0" - create-hmac "^1.1.0" - diffie-hellman "^5.0.0" - inherits "^2.0.1" - pbkdf2 "^3.0.3" - public-encrypt "^4.0.0" - randombytes "^2.0.0" - randomfill "^1.0.3" - -css-blank-pseudo@^3.0.3: - version "3.0.3" - resolved "https://registry.npmmirror.com/css-blank-pseudo/-/css-blank-pseudo-3.0.3.tgz#36523b01c12a25d812df343a32c322d2a2324561" - integrity sha512-VS90XWtsHGqoM0t4KpH053c4ehxZ2E6HtGI7x68YFV0pTo/QmkV/YFA+NnlvK8guxZVNWGQhVNJGC39Q8XF4OQ== - dependencies: - postcss-selector-parser "^6.0.9" - -css-color-keywords@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05" - integrity sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg== - -css-has-pseudo@^3.0.4: - version "3.0.4" - resolved "https://registry.npmmirror.com/css-has-pseudo/-/css-has-pseudo-3.0.4.tgz#57f6be91ca242d5c9020ee3e51bbb5b89fc7af73" - integrity sha512-Vse0xpR1K9MNlp2j5w1pgWIJtm1a8qS0JwS9goFYcImjlHEmywP9VUF05aGBXzGpDJF86QXk4L0ypBmwPhGArw== - dependencies: - postcss-selector-parser "^6.0.9" - -css-loader@6.7.1: - version "6.7.1" - resolved "https://registry.npmmirror.com/css-loader/-/css-loader-6.7.1.tgz#e98106f154f6e1baf3fc3bc455cb9981c1d5fd2e" - integrity sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw== - dependencies: - icss-utils "^5.1.0" - postcss "^8.4.7" - postcss-modules-extract-imports "^3.0.0" - postcss-modules-local-by-default "^4.0.0" - postcss-modules-scope "^3.0.0" - postcss-modules-values "^4.0.0" - postcss-value-parser "^4.2.0" - semver "^7.3.5" - -css-prefers-color-scheme@^6.0.3: - version "6.0.3" - resolved "https://registry.npmmirror.com/css-prefers-color-scheme/-/css-prefers-color-scheme-6.0.3.tgz#ca8a22e5992c10a5b9d315155e7caee625903349" - integrity sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA== - -css-select@^4.1.3: - version "4.3.0" - resolved "https://registry.npmmirror.com/css-select/-/css-select-4.3.0.tgz#db7129b2846662fd8628cfc496abb2b59e41529b" - integrity sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ== - dependencies: - boolbase "^1.0.0" - css-what "^6.0.1" - domhandler "^4.3.1" - domutils "^2.8.0" - nth-check "^2.0.1" - -css-to-react-native@^3.0.0, css-to-react-native@^3.2.0: - version "3.2.0" - resolved "https://registry.npmmirror.com/css-to-react-native/-/css-to-react-native-3.2.0.tgz#cdd8099f71024e149e4f6fe17a7d46ecd55f1e32" - integrity sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ== - dependencies: - camelize "^1.0.0" - css-color-keywords "^1.0.0" - postcss-value-parser "^4.0.2" - -css-tree@^1.1.2, css-tree@^1.1.3: - version "1.1.3" - resolved "https://registry.npmmirror.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" - integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== - dependencies: - mdn-data "2.0.14" - source-map "^0.6.1" - -css-what@^6.0.1: - version "6.1.0" - resolved "https://registry.npmmirror.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" - integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== - -cssdb@^6.6.1: - version "6.6.3" - resolved "https://registry.npmmirror.com/cssdb/-/cssdb-6.6.3.tgz#1f331a2fab30c18d9f087301e6122a878bb1e505" - integrity sha512-7GDvDSmE+20+WcSMhP17Q1EVWUrLlbxxpMDqG731n8P99JhnQZHR9YvtjPvEHfjFUjvQJvdpKCjlKOX+xe4UVA== - -cssesc@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" - integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== - -csso@^4.2.0: - version "4.2.0" - resolved "https://registry.npmmirror.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529" - integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA== - dependencies: - css-tree "^1.1.2" - -csstype@^3.0.10, csstype@^3.0.2, csstype@^3.1.2: - version "3.1.2" - resolved "https://registry.npmmirror.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" - integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== - -current-script-polyfill@1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/current-script-polyfill/-/current-script-polyfill-1.0.0.tgz#f31cf7e4f3e218b0726e738ca92a02d3488ef615" - integrity sha512-qv8s+G47V6Hq+g2kRE5th+ASzzrL7b6l+tap1DHKK25ZQJv3yIFhH96XaQ7NGL+zRW3t/RDbweJf/dJDe5Z5KA== - -d@1, d@^1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" - integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== - dependencies: - es5-ext "^0.10.50" - type "^1.0.1" - -data-uri-to-buffer@^4.0.0: - version "4.0.1" - resolved "https://registry.npmmirror.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz#d8feb2b2881e6a4f58c2e08acfd0e2834e26222e" - integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A== - -date-fns@^2.30.0: - version "2.30.0" - resolved "https://registry.npmmirror.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0" - integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw== - dependencies: - "@babel/runtime" "^7.21.0" - -dayjs@^1.11.1, dayjs@^1.11.7, dayjs@^1.11.9, dayjs@^1.9.1: - version "1.11.9" - resolved "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.9.tgz#9ca491933fadd0a60a2c19f6c237c03517d71d1a" - integrity sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA== - -debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: - version "4.3.4" - resolved "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - -debug@^2.6.8: - version "2.6.9" - resolved "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -debug@^3.2.6, debug@^3.2.7: - version "3.2.7" - resolved "https://registry.npmmirror.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" - integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== - dependencies: - ms "^2.1.1" - -decode-uri-component@^0.2.0: - version "0.2.2" - resolved "https://registry.npmmirror.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" - integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== - -decompress-response@^6.0.0: - version "6.0.0" - resolved "https://registry.npmmirror.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" - integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== - dependencies: - mimic-response "^3.1.0" - -deep-is@^0.1.3: - version "0.1.4" - resolved "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" - integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== - -deepmerge@^4.2.2: - version "4.3.1" - resolved "https://registry.npmmirror.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" - integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== - -default-browser-id@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/default-browser-id/-/default-browser-id-3.0.0.tgz#bee7bbbef1f4e75d31f98f4d3f1556a14cea790c" - integrity sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA== - dependencies: - bplist-parser "^0.2.0" - untildify "^4.0.0" - -default-browser@^4.0.0: - version "4.0.0" - resolved "https://registry.npmmirror.com/default-browser/-/default-browser-4.0.0.tgz#53c9894f8810bf86696de117a6ce9085a3cbc7da" - integrity sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA== - dependencies: - bundle-name "^3.0.0" - default-browser-id "^3.0.0" - execa "^7.1.1" - titleize "^3.0.0" - -defer-to-connect@^2.0.0: - version "2.0.1" - resolved "https://registry.npmmirror.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" - integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg== - -define-data-property@^1.0.1: - version "1.1.0" - resolved "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.0.tgz#0db13540704e1d8d479a0656cf781267531b9451" - integrity sha512-UzGwzcjyv3OtAvolTj1GoyNYzfFR+iqbGjcnBEENZVCpM4/Ng1yhGNvS3lR/xDS74Tb2wGG9WzNSNIOS9UVb2g== - dependencies: - get-intrinsic "^1.2.1" - gopd "^1.0.1" - has-property-descriptors "^1.0.0" - -define-lazy-prop@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" - integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== - -define-lazy-prop@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz#dbb19adfb746d7fc6d734a06b72f4a00d021255f" - integrity sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg== - -define-properties@^1.1.3, define-properties@^1.1.4, define-properties@^1.2.0: - version "1.2.0" - resolved "https://registry.npmmirror.com/define-properties/-/define-properties-1.2.0.tgz#52988570670c9eacedd8064f4a990f2405849bd5" - integrity sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA== - dependencies: - has-property-descriptors "^1.0.0" - object-keys "^1.1.1" - -define-properties@^1.2.1: - version "1.2.1" - resolved "https://registry.npmmirror.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" - integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== - dependencies: - define-data-property "^1.0.1" - has-property-descriptors "^1.0.0" - object-keys "^1.1.1" - -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== - -des.js@^1.0.0: - version "1.1.0" - resolved "https://registry.npmmirror.com/des.js/-/des.js-1.1.0.tgz#1d37f5766f3bbff4ee9638e871a8768c173b81da" - integrity sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg== - dependencies: - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - -detect-indent@^7.0.1: - version "7.0.1" - resolved "https://registry.npmmirror.com/detect-indent/-/detect-indent-7.0.1.tgz#cbb060a12842b9c4d333f1cac4aa4da1bb66bc25" - integrity sha512-Mc7QhQ8s+cLrnUfU/Ji94vG/r8M26m8f++vyres4ZoojaRDpZ1eSIh/EpzLNwlWuvzSZ3UbDFspjFvTDXe6e/g== - -detect-libc@^1.0.3: - version "1.0.3" - resolved "https://registry.npmmirror.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" - integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg== - -detect-newline@^4.0.0: - version "4.0.0" - resolved "https://registry.npmmirror.com/detect-newline/-/detect-newline-4.0.0.tgz#450ac3f864d5f61112b53a524123b012c59581bc" - integrity sha512-1aXUEPdfGdzVPFpzGJJNgq9o81bGg1s09uxTWsqBlo9PI332uyJRQq13+LK/UN4JfxJbFdCXonUFQ9R/p7yCtw== - -detect-node@^2.0.4: - version "2.1.0" - resolved "https://registry.npmmirror.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" - integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== - -didyoumean@^1.2.2: - version "1.2.2" - resolved "https://registry.npmmirror.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" - integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw== - -diffie-hellman@^5.0.0: - version "5.0.3" - resolved "https://registry.npmmirror.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" - integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg== - dependencies: - bn.js "^4.1.0" - miller-rabin "^4.0.0" - randombytes "^2.0.0" - -dir-compare@^2.4.0: - version "2.4.0" - resolved "https://registry.npmmirror.com/dir-compare/-/dir-compare-2.4.0.tgz#785c41dc5f645b34343a4eafc50b79bac7f11631" - integrity sha512-l9hmu8x/rjVC9Z2zmGzkhOEowZvW7pmYws5CWHutg8u1JgvsKWMx7Q/UODeu4djLZ4FgW5besw5yvMQnBHzuCA== - dependencies: - buffer-equal "1.0.0" - colors "1.0.3" - commander "2.9.0" - minimatch "3.0.4" - -dir-glob@^3.0.1: - version "3.0.1" - resolved "https://registry.npmmirror.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" - integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== - dependencies: - path-type "^4.0.0" - -discontinuous-range@1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/discontinuous-range/-/discontinuous-range-1.0.0.tgz#e38331f0844bba49b9a9cb71c771585aab1bc65a" - integrity sha512-c68LpLbO+7kP/b1Hr1qs8/BJ09F5khZGTxqxZuhzxpmwJKOgRFHJWIb9/KmqnqHhLdO55aOxFH/EGBvUQbL/RQ== - -dlv@^1.1.3: - version "1.1.3" - resolved "https://registry.npmmirror.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79" - integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA== - -dmg-builder@23.6.0: - version "23.6.0" - resolved "https://registry.npmmirror.com/dmg-builder/-/dmg-builder-23.6.0.tgz#d39d3871bce996f16c07d2cafe922d6ecbb2a948" - integrity sha512-jFZvY1JohyHarIAlTbfQOk+HnceGjjAdFjVn3n8xlDWKsYNqbO4muca6qXEZTfGXeQMG7TYim6CeS5XKSfSsGA== - dependencies: - app-builder-lib "23.6.0" - builder-util "23.6.0" - builder-util-runtime "9.1.1" - fs-extra "^10.0.0" - iconv-lite "^0.6.2" - js-yaml "^4.1.0" - optionalDependencies: - dmg-license "^1.0.11" - -dmg-license@^1.0.11: - version "1.0.11" - resolved "https://registry.npmmirror.com/dmg-license/-/dmg-license-1.0.11.tgz#7b3bc3745d1b52be7506b4ee80cb61df6e4cd79a" - integrity sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q== - dependencies: - "@types/plist" "^3.0.1" - "@types/verror" "^1.10.3" - ajv "^6.10.0" - crc "^3.8.0" - iconv-corefoundation "^1.1.7" - plist "^3.0.4" - smart-buffer "^4.0.2" - verror "^1.10.0" - -doctrine@^2.1.0: - version "2.1.0" - resolved "https://registry.npmmirror.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" - integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== - dependencies: - esutils "^2.0.2" - -doctrine@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" - integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== - dependencies: - esutils "^2.0.2" - -dom-align@^1.7.0: - version "1.12.4" - resolved "https://registry.npmmirror.com/dom-align/-/dom-align-1.12.4.tgz#3503992eb2a7cfcb2ed3b2a6d21e0b9c00d54511" - integrity sha512-R8LUSEay/68zE5c8/3BDxiTEvgb4xZTF0RKmAHfiEVN3klfIpXfi2/QCoiWPccVQ0J/ZGdz9OjzL4uJEP/MRAw== - -dom-converter@^0.2.0: - version "0.2.0" - resolved "https://registry.npmmirror.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" - integrity sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA== - dependencies: - utila "~0.4" - -dom-serializer@^1.0.1: - version "1.4.1" - resolved "https://registry.npmmirror.com/dom-serializer/-/dom-serializer-1.4.1.tgz#de5d41b1aea290215dc45a6dae8adcf1d32e2d30" - integrity sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag== - dependencies: - domelementtype "^2.0.1" - domhandler "^4.2.0" - entities "^2.0.0" - -dom-walk@^0.1.0: - version "0.1.2" - resolved "https://registry.npmmirror.com/dom-walk/-/dom-walk-0.1.2.tgz#0c548bef048f4d1f2a97249002236060daa3fd84" - integrity sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w== - -domain-browser@^1.1.1: - version "1.2.0" - resolved "https://registry.npmmirror.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" - integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== - -domelementtype@^2.0.1, domelementtype@^2.2.0: - version "2.3.0" - resolved "https://registry.npmmirror.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" - integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== - -domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1: - version "4.3.1" - resolved "https://registry.npmmirror.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c" - integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ== - dependencies: - domelementtype "^2.2.0" - -domutils@^2.5.2, domutils@^2.8.0: - version "2.8.0" - resolved "https://registry.npmmirror.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" - integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== - dependencies: - dom-serializer "^1.0.1" - domelementtype "^2.2.0" - domhandler "^4.2.0" - -dot-case@^3.0.4: - version "3.0.4" - resolved "https://registry.npmmirror.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" - integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w== - dependencies: - no-case "^3.0.4" - tslib "^2.0.3" - -dotenv-expand@^5.1.0: - version "5.1.0" - resolved "https://registry.npmmirror.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" - integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== - -dotenv@^9.0.2: - version "9.0.2" - resolved "https://registry.npmmirror.com/dotenv/-/dotenv-9.0.2.tgz#dacc20160935a37dea6364aa1bef819fb9b6ab05" - integrity sha512-I9OvvrHp4pIARv4+x9iuewrWycX6CcZtoAu1XrzPxc5UygMJXJZYmBsynku8IkrJwgypE5DGNjDPmPRhDCptUg== - -duplexify@^4.1.2: - version "4.1.2" - resolved "https://registry.npmmirror.com/duplexify/-/duplexify-4.1.2.tgz#18b4f8d28289132fa0b9573c898d9f903f81c7b0" - integrity sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw== - dependencies: - end-of-stream "^1.4.1" - inherits "^2.0.3" - readable-stream "^3.1.1" - stream-shift "^1.0.0" - -dva-core@^2.0.4: - version "2.0.4" - resolved "https://registry.npmmirror.com/dva-core/-/dva-core-2.0.4.tgz#086665d1d5f684e089c5bfac9ba388d91cc9050a" - integrity sha512-Zh39llFyItu9HKXKfCZVf9UFtDTcypdAjGBew1S+wK8BGVzFpm1GPTdd6uIMeg7O6STtCvt2Qv+RwUut1GFynA== - dependencies: - "@babel/runtime" "^7.0.0" - flatten "^1.0.2" - global "^4.3.2" - invariant "^2.2.1" - is-plain-object "^2.0.3" - redux-saga "^0.16.0" - warning "^3.0.0" - -dva-immer@^1.0.0: - version "1.0.1" - resolved "https://registry.npmmirror.com/dva-immer/-/dva-immer-1.0.1.tgz#209b9fbf995147bb6c1dcdd9e9beea883a9b159c" - integrity sha512-Oe+yFTtu2UMNcMoBLLTa/ms1RjUry38Yf0ClN8LiHbF+gT2QAdLYLk3miu1dDtm3Sxl9nk+DH1edKX0Hy49uQg== - dependencies: - "@babel/runtime" "^7.0.0" - immer "^8.0.4" - -dva-loading@^3.0.22: - version "3.0.24" - resolved "https://registry.npmmirror.com/dva-loading/-/dva-loading-3.0.24.tgz#c8974b3b29dc69ee0b4180e956ded5aa7e12adce" - integrity sha512-3j4bmuXOYH93xe+CC//z3Si8XMx6DLJveep+UbzKy0jhA7oQrCCZTdKxu0UPYXeAMYXpCO25pG4JOnVhzmC7ug== - dependencies: - "@babel/runtime" "^7.0.0" - -echarts-for-react@^3.0.2: - version "3.0.2" - resolved "https://registry.npmmirror.com/echarts-for-react/-/echarts-for-react-3.0.2.tgz#ac5859157048a1066d4553e34b328abb24f2b7c1" - integrity sha512-DRwIiTzx8JfwPOVgGttDytBqdp5VzCSyMRIxubgU/g2n9y3VLUmF2FK7Icmg/sNVkv4+rktmrLN9w22U2yy3fA== - dependencies: - fast-deep-equal "^3.1.3" - size-sensor "^1.0.1" - -echarts@^5.4.2: - version "5.4.3" - resolved "https://registry.npmmirror.com/echarts/-/echarts-5.4.3.tgz#f5522ef24419164903eedcfd2b506c6fc91fb20c" - integrity sha512-mYKxLxhzy6zyTi/FaEbJMOZU1ULGEQHaeIeuMR5L+JnJTpz+YR03mnnpBhbR4+UYJAgiXgpyTVLffPAjOTLkZA== - dependencies: - tslib "2.3.0" - zrender "5.4.4" - -ejs@^3.1.7: - version "3.1.9" - resolved "https://registry.npmmirror.com/ejs/-/ejs-3.1.9.tgz#03c9e8777fe12686a9effcef22303ca3d8eeb361" - integrity sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ== - dependencies: - jake "^10.8.5" - -electron-builder@^23.6.0: - version "23.6.0" - resolved "https://registry.npmmirror.com/electron-builder/-/electron-builder-23.6.0.tgz#c79050cbdce90ed96c5feb67c34e9e0a21b5331b" - integrity sha512-y8D4zO+HXGCNxFBV/JlyhFnoQ0Y0K7/sFH+XwIbj47pqaW8S6PGYQbjoObolKBR1ddQFPt4rwp4CnwMJrW3HAw== - dependencies: - "@types/yargs" "^17.0.1" - app-builder-lib "23.6.0" - builder-util "23.6.0" - builder-util-runtime "9.1.1" - chalk "^4.1.1" - dmg-builder "23.6.0" - fs-extra "^10.0.0" - is-ci "^3.0.0" - lazy-val "^1.0.5" - read-config-file "6.2.0" - simple-update-notifier "^1.0.7" - yargs "^17.5.1" - -electron-debug@^3.2.0: - version "3.2.0" - resolved "https://registry.npmmirror.com/electron-debug/-/electron-debug-3.2.0.tgz#46a15b555c3b11872218c65ea01d058aa0814920" - integrity sha512-7xZh+LfUvJ52M9rn6N+tPuDw6oRAjxUj9SoxAZfJ0hVCXhZCsdkrSt7TgXOiWiEOBgEV8qwUIO/ScxllsPS7ow== - dependencies: - electron-is-dev "^1.1.0" - electron-localshortcut "^3.1.0" - -electron-is-accelerator@^0.1.0: - version "0.1.2" - resolved "https://registry.npmmirror.com/electron-is-accelerator/-/electron-is-accelerator-0.1.2.tgz#509e510c26a56b55e17f863a4b04e111846ab27b" - integrity sha512-fLGSAjXZtdn1sbtZxx52+krefmtNuVwnJCV2gNiVt735/ARUboMl8jnNC9fZEqQdlAv2ZrETfmBUsoQci5evJA== - -electron-is-dev@^1.1.0: - version "1.2.0" - resolved "https://registry.npmmirror.com/electron-is-dev/-/electron-is-dev-1.2.0.tgz#2e5cea0a1b3ccf1c86f577cee77363ef55deb05e" - integrity sha512-R1oD5gMBPS7PVU8gJwH6CtT0e6VSoD0+SzSnYpNm+dBkcijgA+K7VAMHDfnRq/lkKPZArpzplTW6jfiMYosdzw== - -electron-localshortcut@^3.1.0: - version "3.2.1" - resolved "https://registry.npmmirror.com/electron-localshortcut/-/electron-localshortcut-3.2.1.tgz#cfc83a3eff5e28faf98ddcc87f80a2ce4f623cd3" - integrity sha512-DWvhKv36GsdXKnaFFhEiK8kZZA+24/yFLgtTwJJHc7AFgDjNRIBJZ/jq62Y/dWv9E4ypYwrVWN2bVrCYw1uv7Q== - dependencies: - debug "^4.0.1" - electron-is-accelerator "^0.1.0" - keyboardevent-from-electron-accelerator "^2.0.0" - keyboardevents-areequal "^0.2.1" - -electron-osx-sign@^0.6.0: - version "0.6.0" - resolved "https://registry.npmmirror.com/electron-osx-sign/-/electron-osx-sign-0.6.0.tgz#9b69c191d471d9458ef5b1e4fdd52baa059f1bb8" - integrity sha512-+hiIEb2Xxk6eDKJ2FFlpofCnemCbjbT5jz+BKGpVBrRNT3kWTGs4DfNX6IzGwgi33hUcXF+kFs9JW+r6Wc1LRg== - dependencies: - bluebird "^3.5.0" - compare-version "^0.1.2" - debug "^2.6.8" - isbinaryfile "^3.0.2" - minimist "^1.2.0" - plist "^3.0.1" - -electron-publish@23.6.0: - version "23.6.0" - resolved "https://registry.npmmirror.com/electron-publish/-/electron-publish-23.6.0.tgz#ac9b469e0b07752eb89357dd660e5fb10b3d1ce9" - integrity sha512-jPj3y+eIZQJF/+t5SLvsI5eS4mazCbNYqatv5JihbqOstIM13k0d1Z3vAWntvtt13Itl61SO6seicWdioOU5dg== - dependencies: - "@types/fs-extra" "^9.0.11" - builder-util "23.6.0" - builder-util-runtime "9.1.1" - chalk "^4.1.1" - fs-extra "^10.0.0" - lazy-val "^1.0.5" - mime "^2.5.2" - -electron-to-chromium@^1.4.431: - version "1.4.464" - resolved "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.4.464.tgz#2f94bad78dff34e527aacbfc5d0b1a33cf046507" - integrity sha512-guZ84yoou4+ILNdj0XEbmGs6DEWj6zpVOWYpY09GU66yEb0DSYvP/biBPzHn0GuW/3RC/pnaYNUWlQE1fJYtgA== - -electron@^22.3.0: - version "22.3.27" - resolved "https://registry.npmmirror.com/electron/-/electron-22.3.27.tgz#b77451a53f0c502e7559cceac28ac58eb289eef8" - integrity sha512-7Rht21vHqj4ZFRnKuZdFqZFsvMBCmDqmjetiMqPtF+TmTBiGne1mnstVXOA/SRGhN2Qy5gY5bznJKpiqogjM8A== - dependencies: - "@electron/get" "^2.0.0" - "@types/node" "^16.11.26" - extract-zip "^2.0.1" - -elliptic@^6.5.3: - version "6.5.4" - resolved "https://registry.npmmirror.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" - integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== - dependencies: - bn.js "^4.11.9" - brorand "^1.1.0" - hash.js "^1.0.0" - hmac-drbg "^1.0.1" - inherits "^2.0.4" - minimalistic-assert "^1.0.1" - minimalistic-crypto-utils "^1.0.1" - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -emojis-list@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" - integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== - -encoding@^0.1.11: - version "0.1.13" - resolved "https://registry.npmmirror.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" - integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== - dependencies: - iconv-lite "^0.6.2" - -end-of-stream@^1.1.0, end-of-stream@^1.4.1: - version "1.4.4" - resolved "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" - integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== - dependencies: - once "^1.4.0" - -enhanced-resolve@5.9.3: - version "5.9.3" - resolved "https://registry.npmmirror.com/enhanced-resolve/-/enhanced-resolve-5.9.3.tgz#44a342c012cbc473254af5cc6ae20ebd0aae5d88" - integrity sha512-Bq9VSor+kjvW3f9/MiiR4eE3XYgOl7/rS8lnSxbRbF3kS0B2r+Y9w5krBWxZgDxASVZbdYrn5wT4j/Wb0J9qow== - dependencies: - graceful-fs "^4.2.4" - tapable "^2.2.0" - -enhanced-resolve@^0.9.1: - version "0.9.1" - resolved "https://registry.npmmirror.com/enhanced-resolve/-/enhanced-resolve-0.9.1.tgz#4d6e689b3725f86090927ccc86cd9f1635b89e2e" - integrity sha512-kxpoMgrdtkXZ5h0SeraBS1iRntpTpQ3R8ussdb38+UAFnMGX5DDyJXePm+OCHOcoXvHDw7mc2erbJBpDnl7TPw== - dependencies: - graceful-fs "^4.1.2" - memory-fs "^0.2.0" - tapable "^0.1.8" - -enhanced-resolve@^5.15.0: - version "5.15.0" - resolved "https://registry.npmmirror.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz#1af946c7d93603eb88e9896cee4904dc012e9c35" - integrity sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg== - dependencies: - graceful-fs "^4.2.4" - tapable "^2.2.0" - -entities@^2.0.0: - version "2.2.0" - resolved "https://registry.npmmirror.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" - integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== - -entities@^4.4.0: - version "4.5.0" - resolved "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" - integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== - -env-paths@^2.2.0: - version "2.2.1" - resolved "https://registry.npmmirror.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" - integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== - -errno@^0.1.1: - version "0.1.8" - resolved "https://registry.npmmirror.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f" - integrity sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A== - dependencies: - prr "~1.0.1" - -error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.npmmirror.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== - dependencies: - is-arrayish "^0.2.1" - -error-stack-parser@^2.0.6: - version "2.1.4" - resolved "https://registry.npmmirror.com/error-stack-parser/-/error-stack-parser-2.1.4.tgz#229cb01cdbfa84440bfa91876285b94680188286" - integrity sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ== - dependencies: - stackframe "^1.3.4" - -es-abstract@^1.19.0, es-abstract@^1.20.4: - version "1.22.1" - resolved "https://registry.npmmirror.com/es-abstract/-/es-abstract-1.22.1.tgz#8b4e5fc5cefd7f1660f0f8e1a52900dfbc9d9ccc" - integrity sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw== - dependencies: - array-buffer-byte-length "^1.0.0" - arraybuffer.prototype.slice "^1.0.1" - available-typed-arrays "^1.0.5" - call-bind "^1.0.2" - es-set-tostringtag "^2.0.1" - es-to-primitive "^1.2.1" - function.prototype.name "^1.1.5" - get-intrinsic "^1.2.1" - get-symbol-description "^1.0.0" - globalthis "^1.0.3" - gopd "^1.0.1" - has "^1.0.3" - has-property-descriptors "^1.0.0" - has-proto "^1.0.1" - has-symbols "^1.0.3" - internal-slot "^1.0.5" - is-array-buffer "^3.0.2" - is-callable "^1.2.7" - is-negative-zero "^2.0.2" - is-regex "^1.1.4" - is-shared-array-buffer "^1.0.2" - is-string "^1.0.7" - is-typed-array "^1.1.10" - is-weakref "^1.0.2" - object-inspect "^1.12.3" - object-keys "^1.1.1" - object.assign "^4.1.4" - regexp.prototype.flags "^1.5.0" - safe-array-concat "^1.0.0" - safe-regex-test "^1.0.0" - string.prototype.trim "^1.2.7" - string.prototype.trimend "^1.0.6" - string.prototype.trimstart "^1.0.6" - typed-array-buffer "^1.0.0" - typed-array-byte-length "^1.0.0" - typed-array-byte-offset "^1.0.0" - typed-array-length "^1.0.4" - unbox-primitive "^1.0.2" - which-typed-array "^1.1.10" - -es-abstract@^1.22.1: - version "1.22.2" - resolved "https://registry.npmmirror.com/es-abstract/-/es-abstract-1.22.2.tgz#90f7282d91d0ad577f505e423e52d4c1d93c1b8a" - integrity sha512-YoxfFcDmhjOgWPWsV13+2RNjq1F6UQnfs+8TftwNqtzlmFzEXvlUwdrNrYeaizfjQzRMxkZ6ElWMOJIFKdVqwA== - dependencies: - array-buffer-byte-length "^1.0.0" - arraybuffer.prototype.slice "^1.0.2" - available-typed-arrays "^1.0.5" - call-bind "^1.0.2" - es-set-tostringtag "^2.0.1" - es-to-primitive "^1.2.1" - function.prototype.name "^1.1.6" - get-intrinsic "^1.2.1" - get-symbol-description "^1.0.0" - globalthis "^1.0.3" - gopd "^1.0.1" - has "^1.0.3" - has-property-descriptors "^1.0.0" - has-proto "^1.0.1" - has-symbols "^1.0.3" - internal-slot "^1.0.5" - is-array-buffer "^3.0.2" - is-callable "^1.2.7" - is-negative-zero "^2.0.2" - is-regex "^1.1.4" - is-shared-array-buffer "^1.0.2" - is-string "^1.0.7" - is-typed-array "^1.1.12" - is-weakref "^1.0.2" - object-inspect "^1.12.3" - object-keys "^1.1.1" - object.assign "^4.1.4" - regexp.prototype.flags "^1.5.1" - safe-array-concat "^1.0.1" - safe-regex-test "^1.0.0" - string.prototype.trim "^1.2.8" - string.prototype.trimend "^1.0.7" - string.prototype.trimstart "^1.0.7" - typed-array-buffer "^1.0.0" - typed-array-byte-length "^1.0.0" - typed-array-byte-offset "^1.0.0" - typed-array-length "^1.0.4" - unbox-primitive "^1.0.2" - which-typed-array "^1.1.11" - -es-get-iterator@^1.1.2: - version "1.1.3" - resolved "https://registry.npmmirror.com/es-get-iterator/-/es-get-iterator-1.1.3.tgz#3ef87523c5d464d41084b2c3c9c214f1199763d6" - integrity sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.1.3" - has-symbols "^1.0.3" - is-arguments "^1.1.1" - is-map "^2.0.2" - is-set "^2.0.2" - is-string "^1.0.7" - isarray "^2.0.5" - stop-iteration-iterator "^1.0.0" - -es-iterator-helpers@^1.0.12: - version "1.0.15" - resolved "https://registry.npmmirror.com/es-iterator-helpers/-/es-iterator-helpers-1.0.15.tgz#bd81d275ac766431d19305923707c3efd9f1ae40" - integrity sha512-GhoY8uYqd6iwUl2kgjTm4CZAf6oo5mHK7BPqx3rKgx893YSsy0LGHV6gfqqQvZt/8xM8xeOnfXBCfqclMKkJ5g== - dependencies: - asynciterator.prototype "^1.0.0" - call-bind "^1.0.2" - define-properties "^1.2.1" - es-abstract "^1.22.1" - es-set-tostringtag "^2.0.1" - function-bind "^1.1.1" - get-intrinsic "^1.2.1" - globalthis "^1.0.3" - has-property-descriptors "^1.0.0" - has-proto "^1.0.1" - has-symbols "^1.0.3" - internal-slot "^1.0.5" - iterator.prototype "^1.1.2" - safe-array-concat "^1.0.1" - -es-set-tostringtag@^2.0.1: - version "2.0.1" - resolved "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz#338d502f6f674301d710b80c8592de8a15f09cd8" - integrity sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg== - dependencies: - get-intrinsic "^1.1.3" - has "^1.0.3" - has-tostringtag "^1.0.0" - -es-shim-unscopables@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz#702e632193201e3edf8713635d083d378e510241" - integrity sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w== - dependencies: - has "^1.0.3" - -es-to-primitive@^1.2.1: - version "1.2.1" - resolved "https://registry.npmmirror.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" - integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== - dependencies: - is-callable "^1.1.4" - is-date-object "^1.0.1" - is-symbol "^1.0.2" - -es5-ext@^0.10.35, es5-ext@^0.10.50, es5-ext@~0.10.14: - version "0.10.62" - resolved "https://registry.npmmirror.com/es5-ext/-/es5-ext-0.10.62.tgz#5e6adc19a6da524bf3d1e02bbc8960e5eb49a9a5" - integrity sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA== - dependencies: - es6-iterator "^2.0.3" - es6-symbol "^3.1.3" - next-tick "^1.1.0" - -es5-imcompatible-versions@^0.1.78: - version "0.1.86" - resolved "https://registry.npmmirror.com/es5-imcompatible-versions/-/es5-imcompatible-versions-0.1.86.tgz#e9583ad3a7a93c1b13835fb804a3bbd05e30a662" - integrity sha512-Lbrsn5bCL4iVMBdundiFVNIKlnnoBiIMrjtLRe1Snt92s60WHotw83S2ijp5ioqe6pDil3iBPY634VDwBcb1rg== - -es6-error@^4.1.1: - version "4.1.1" - resolved "https://registry.npmmirror.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" - integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== - -es6-iterator@^2.0.3: - version "2.0.3" - resolved "https://registry.npmmirror.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" - integrity sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g== - dependencies: - d "1" - es5-ext "^0.10.35" - es6-symbol "^3.1.1" - -es6-symbol@^3.1.1, es6-symbol@^3.1.3: - version "3.1.3" - resolved "https://registry.npmmirror.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" - integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== - dependencies: - d "^1.0.1" - ext "^1.1.2" - -esbuild@0.17.19, esbuild@^0.17.5, esbuild@~0.17.6: - version "0.17.19" - resolved "https://registry.npmmirror.com/esbuild/-/esbuild-0.17.19.tgz#087a727e98299f0462a3d0bcdd9cd7ff100bd955" - integrity sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw== - optionalDependencies: - "@esbuild/android-arm" "0.17.19" - "@esbuild/android-arm64" "0.17.19" - "@esbuild/android-x64" "0.17.19" - "@esbuild/darwin-arm64" "0.17.19" - "@esbuild/darwin-x64" "0.17.19" - "@esbuild/freebsd-arm64" "0.17.19" - "@esbuild/freebsd-x64" "0.17.19" - "@esbuild/linux-arm" "0.17.19" - "@esbuild/linux-arm64" "0.17.19" - "@esbuild/linux-ia32" "0.17.19" - "@esbuild/linux-loong64" "0.17.19" - "@esbuild/linux-mips64el" "0.17.19" - "@esbuild/linux-ppc64" "0.17.19" - "@esbuild/linux-riscv64" "0.17.19" - "@esbuild/linux-s390x" "0.17.19" - "@esbuild/linux-x64" "0.17.19" - "@esbuild/netbsd-x64" "0.17.19" - "@esbuild/openbsd-x64" "0.17.19" - "@esbuild/sunos-x64" "0.17.19" - "@esbuild/win32-arm64" "0.17.19" - "@esbuild/win32-ia32" "0.17.19" - "@esbuild/win32-x64" "0.17.19" - -escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.npmmirror.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" - integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== - -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== - -escape-string-regexp@^4.0.0: - version "4.0.0" - resolved "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - -eslint-config-airbnb-base@^15.0.0: - version "15.0.0" - resolved "https://registry.npmmirror.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz#6b09add90ac79c2f8d723a2580e07f3925afd236" - integrity sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig== - dependencies: - confusing-browser-globals "^1.0.10" - object.assign "^4.1.2" - object.entries "^1.1.5" - semver "^6.3.0" - -eslint-config-prettier@^9.0.0: - version "9.0.0" - resolved "https://registry.npmmirror.com/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz#eb25485946dd0c66cd216a46232dc05451518d1f" - integrity sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw== - -eslint-import-resolver-node@^0.3.7: - version "0.3.9" - resolved "https://registry.npmmirror.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz#d4eaac52b8a2e7c3cd1903eb00f7e053356118ac" - integrity sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g== - dependencies: - debug "^3.2.7" - is-core-module "^2.13.0" - resolve "^1.22.4" - -eslint-import-resolver-webpack@^0.13.7: - version "0.13.7" - resolved "https://registry.npmmirror.com/eslint-import-resolver-webpack/-/eslint-import-resolver-webpack-0.13.7.tgz#49cd0108767b1f8ff81123c7e1ae362305aad47b" - integrity sha512-2a+meyMeABBRO4K53Oj1ygkmt5lhQS79Lmx2f684Qnv6gjvD4RLOM5jfPGTXwQ0A2K03WSoKt3HRQu/uBgxF7w== - dependencies: - array.prototype.find "^2.2.1" - debug "^3.2.7" - enhanced-resolve "^0.9.1" - find-root "^1.1.0" - has "^1.0.3" - interpret "^1.4.0" - is-core-module "^2.13.0" - is-regex "^1.1.4" - lodash "^4.17.21" - resolve "^2.0.0-next.4" - semver "^5.7.2" - -eslint-module-utils@^2.8.0: - version "2.8.0" - resolved "https://registry.npmmirror.com/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz#e439fee65fc33f6bba630ff621efc38ec0375c49" - integrity sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw== - dependencies: - debug "^3.2.7" - -eslint-plugin-babel@^5.3.1: - version "5.3.1" - resolved "https://registry.npmmirror.com/eslint-plugin-babel/-/eslint-plugin-babel-5.3.1.tgz#75a2413ffbf17e7be57458301c60291f2cfbf560" - integrity sha512-VsQEr6NH3dj664+EyxJwO4FCYm/00JhYb3Sk3ft8o+fpKuIfQ9TaW6uVUfvwMXHcf/lsnRIoyFPsLMyiWCSL/g== - dependencies: - eslint-rule-composer "^0.3.0" - -eslint-plugin-import@^2.28.1: - version "2.28.1" - resolved "https://registry.npmmirror.com/eslint-plugin-import/-/eslint-plugin-import-2.28.1.tgz#63b8b5b3c409bfc75ebaf8fb206b07ab435482c4" - integrity sha512-9I9hFlITvOV55alzoKBI+K9q74kv0iKMeY6av5+umsNwayt59fz692daGyjR+oStBQgx6nwR9rXldDev3Clw+A== - dependencies: - array-includes "^3.1.6" - array.prototype.findlastindex "^1.2.2" - array.prototype.flat "^1.3.1" - array.prototype.flatmap "^1.3.1" - debug "^3.2.7" - doctrine "^2.1.0" - eslint-import-resolver-node "^0.3.7" - eslint-module-utils "^2.8.0" - has "^1.0.3" - is-core-module "^2.13.0" - is-glob "^4.0.3" - minimatch "^3.1.2" - object.fromentries "^2.0.6" - object.groupby "^1.0.0" - object.values "^1.1.6" - semver "^6.3.1" - tsconfig-paths "^3.14.2" - -eslint-plugin-jest@27.2.3: - version "27.2.3" - resolved "https://registry.npmmirror.com/eslint-plugin-jest/-/eslint-plugin-jest-27.2.3.tgz#6f8a4bb2ca82c0c5d481d1b3be256ab001f5a3ec" - integrity sha512-sRLlSCpICzWuje66Gl9zvdF6mwD5X86I4u55hJyFBsxYOsBCmT5+kSUjf+fkFWVMMgpzNEupjW8WzUqi83hJAQ== - dependencies: - "@typescript-eslint/utils" "^5.10.0" - -eslint-plugin-prettier@^5.0.0: - version "5.0.0" - resolved "https://registry.npmmirror.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.0.tgz#6887780ed95f7708340ec79acfdf60c35b9be57a" - integrity sha512-AgaZCVuYDXHUGxj/ZGu1u8H8CYgDY3iG6w5kUFw4AzMVXzB7VvbKgYR4nATIN+OvUrghMbiDLeimVjVY5ilq3w== - dependencies: - prettier-linter-helpers "^1.0.0" - synckit "^0.8.5" - -eslint-plugin-react-hooks@4.6.0, eslint-plugin-react-hooks@^4.6.0: - version "4.6.0" - resolved "https://registry.npmmirror.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz#4c3e697ad95b77e93f8646aaa1630c1ba607edd3" - integrity sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g== - -eslint-plugin-react@7.33.2, eslint-plugin-react@^7.33.2: - version "7.33.2" - resolved "https://registry.npmmirror.com/eslint-plugin-react/-/eslint-plugin-react-7.33.2.tgz#69ee09443ffc583927eafe86ffebb470ee737608" - integrity sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw== - dependencies: - array-includes "^3.1.6" - array.prototype.flatmap "^1.3.1" - array.prototype.tosorted "^1.1.1" - doctrine "^2.1.0" - es-iterator-helpers "^1.0.12" - estraverse "^5.3.0" - jsx-ast-utils "^2.4.1 || ^3.0.0" - minimatch "^3.1.2" - object.entries "^1.1.6" - object.fromentries "^2.0.6" - object.hasown "^1.1.2" - object.values "^1.1.6" - prop-types "^15.8.1" - resolve "^2.0.0-next.4" - semver "^6.3.1" - string.prototype.matchall "^4.0.8" - -eslint-rule-composer@^0.3.0: - version "0.3.0" - resolved "https://registry.npmmirror.com/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz#79320c927b0c5c0d3d3d2b76c8b4a488f25bbaf9" - integrity sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg== - -eslint-scope@5.1.1, eslint-scope@^5.1.1: - version "5.1.1" - resolved "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" - integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== - dependencies: - esrecurse "^4.3.0" - estraverse "^4.1.1" - -eslint-scope@^7.2.2: - version "7.2.2" - resolved "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" - integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== - dependencies: - esrecurse "^4.3.0" - estraverse "^5.2.0" - -eslint-visitor-keys@^2.1.0: - version "2.1.0" - resolved "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" - integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== - -eslint-visitor-keys@^3.3.0: - version "3.4.1" - resolved "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz#c22c48f48942d08ca824cc526211ae400478a994" - integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA== - -eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: - version "3.4.3" - resolved "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" - integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== - -eslint@^8.49.0: - version "8.49.0" - resolved "https://registry.npmmirror.com/eslint/-/eslint-8.49.0.tgz#09d80a89bdb4edee2efcf6964623af1054bf6d42" - integrity sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ== - dependencies: - "@eslint-community/eslint-utils" "^4.2.0" - "@eslint-community/regexpp" "^4.6.1" - "@eslint/eslintrc" "^2.1.2" - "@eslint/js" "8.49.0" - "@humanwhocodes/config-array" "^0.11.11" - "@humanwhocodes/module-importer" "^1.0.1" - "@nodelib/fs.walk" "^1.2.8" - ajv "^6.12.4" - chalk "^4.0.0" - cross-spawn "^7.0.2" - debug "^4.3.2" - doctrine "^3.0.0" - escape-string-regexp "^4.0.0" - eslint-scope "^7.2.2" - eslint-visitor-keys "^3.4.3" - espree "^9.6.1" - esquery "^1.4.2" - esutils "^2.0.2" - fast-deep-equal "^3.1.3" - file-entry-cache "^6.0.1" - find-up "^5.0.0" - glob-parent "^6.0.2" - globals "^13.19.0" - graphemer "^1.4.0" - ignore "^5.2.0" - imurmurhash "^0.1.4" - is-glob "^4.0.0" - is-path-inside "^3.0.3" - js-yaml "^4.1.0" - json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.4.1" - lodash.merge "^4.6.2" - minimatch "^3.1.2" - natural-compare "^1.4.0" - optionator "^0.9.3" - strip-ansi "^6.0.1" - text-table "^0.2.0" - -espree@^9.6.0, espree@^9.6.1: - version "9.6.1" - resolved "https://registry.npmmirror.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" - integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== - dependencies: - acorn "^8.9.0" - acorn-jsx "^5.3.2" - eslint-visitor-keys "^3.4.1" - -esprima@^4.0.0: - version "4.0.1" - resolved "https://registry.npmmirror.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - -esquery@^1.4.2: - version "1.5.0" - resolved "https://registry.npmmirror.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" - integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== - dependencies: - estraverse "^5.1.0" - -esrecurse@^4.3.0: - version "4.3.0" - resolved "https://registry.npmmirror.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" - integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== - dependencies: - estraverse "^5.2.0" - -estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.npmmirror.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - -estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: - version "5.3.0" - resolved "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" - integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - -event-emitter@~0.3.5: - version "0.3.5" - resolved "https://registry.npmmirror.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" - integrity sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA== - dependencies: - d "1" - es5-ext "~0.10.14" - -event-source-polyfill@^1.0.31: - version "1.0.31" - resolved "https://registry.npmmirror.com/event-source-polyfill/-/event-source-polyfill-1.0.31.tgz#45fb0a6fc1375b2ba597361ba4287ffec5bf2e0c" - integrity sha512-4IJSItgS/41IxN5UVAVuAyczwZF7ZIEsM1XAoUzIHA6A+xzusEZUutdXz2Nr+MQPLxfTiCvqE79/C8HT8fKFvA== - -events@^3.0.0: - version "3.3.0" - resolved "https://registry.npmmirror.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" - integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== - -evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: - version "1.0.3" - resolved "https://registry.npmmirror.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" - integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA== - dependencies: - md5.js "^1.3.4" - safe-buffer "^5.1.1" - -execa@^5.0.0, execa@^5.1.1: - version "5.1.1" - resolved "https://registry.npmmirror.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" - integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== - dependencies: - cross-spawn "^7.0.3" - get-stream "^6.0.0" - human-signals "^2.1.0" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.1" - onetime "^5.1.2" - signal-exit "^3.0.3" - strip-final-newline "^2.0.0" - -execa@^7.1.1: - version "7.1.1" - resolved "https://registry.npmmirror.com/execa/-/execa-7.1.1.tgz#3eb3c83d239488e7b409d48e8813b76bb55c9c43" - integrity sha512-wH0eMf/UXckdUYnO21+HDztteVv05rq2GXksxT4fCGeHkBhw1DROXh40wcjMcRqDOWE7iPJ4n3M7e2+YFP+76Q== - dependencies: - cross-spawn "^7.0.3" - get-stream "^6.0.1" - human-signals "^4.3.0" - is-stream "^3.0.0" - merge-stream "^2.0.0" - npm-run-path "^5.1.0" - onetime "^6.0.0" - signal-exit "^3.0.7" - strip-final-newline "^3.0.0" - -ext@^1.1.2: - version "1.7.0" - resolved "https://registry.npmmirror.com/ext/-/ext-1.7.0.tgz#0ea4383c0103d60e70be99e9a7f11027a33c4f5f" - integrity sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw== - dependencies: - type "^2.7.2" - -extract-zip@^2.0.1: - version "2.0.1" - resolved "https://registry.npmmirror.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" - integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== - dependencies: - debug "^4.1.1" - get-stream "^5.1.0" - yauzl "^2.10.0" - optionalDependencies: - "@types/yauzl" "^2.9.1" - -extsprintf@^1.2.0: - version "1.4.1" - resolved "https://registry.npmmirror.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07" - integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA== - -fast-deep-equal@3.1.3, fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: - version "3.1.3" - resolved "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - -fast-diff@^1.1.2: - version "1.3.0" - resolved "https://registry.npmmirror.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" - integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== - -fast-glob@3.2.12: - version "3.2.12" - resolved "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80" - integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.4" - -fast-glob@^3.2.12: - version "3.3.1" - resolved "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.1.tgz#784b4e897340f3dbbef17413b3f11acf03c874c4" - integrity sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.4" - -fast-glob@^3.2.9, fast-glob@^3.3.0: - version "3.3.0" - resolved "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.0.tgz#7c40cb491e1e2ed5664749e87bfb516dbe8727c0" - integrity sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.4" - -fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: - version "2.1.0" - resolved "https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fast-levenshtein@^2.0.6: - version "2.0.6" - resolved "https://registry.npmmirror.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== - -fast-redact@^3.0.0: - version "3.2.0" - resolved "https://registry.npmmirror.com/fast-redact/-/fast-redact-3.2.0.tgz#b1e2d39bc731376d28bde844454fa23e26919987" - integrity sha512-zaTadChr+NekyzallAMXATXLOR8MNx3zqpZ0MUF2aGf4EathnG0f32VLODNlY8IuGY3HoRO2L6/6fSzNsLaHIw== - -fastq@^1.6.0: - version "1.15.0" - resolved "https://registry.npmmirror.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a" - integrity sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw== - dependencies: - reusify "^1.0.4" - -fb-watchman@^2.0.0: - version "2.0.2" - resolved "https://registry.npmmirror.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" - integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== - dependencies: - bser "2.1.1" - -fd-slicer@~1.1.0: - version "1.1.0" - resolved "https://registry.npmmirror.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" - integrity sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g== - dependencies: - pend "~1.2.0" - -fetch-blob@^3.1.2, fetch-blob@^3.1.4: - version "3.2.0" - resolved "https://registry.npmmirror.com/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9" - integrity sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ== - dependencies: - node-domexception "^1.0.0" - web-streams-polyfill "^3.0.3" - -file-entry-cache@^6.0.1: - version "6.0.1" - resolved "https://registry.npmmirror.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" - integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== - dependencies: - flat-cache "^3.0.4" - -filelist@^1.0.4: - version "1.0.4" - resolved "https://registry.npmmirror.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" - integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== - dependencies: - minimatch "^5.0.1" - -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.npmmirror.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== - dependencies: - to-regex-range "^5.0.1" - -filter-obj@^1.1.0: - version "1.1.0" - resolved "https://registry.npmmirror.com/filter-obj/-/filter-obj-1.1.0.tgz#9b311112bc6c6127a16e016c6c5d7f19e0805c5b" - integrity sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ== - -find-root@^1.1.0: - version "1.1.0" - resolved "https://registry.npmmirror.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" - integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== - -find-up@^4.1.0: - version "4.1.0" - resolved "https://registry.npmmirror.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -find-up@^5.0.0: - version "5.0.0" - resolved "https://registry.npmmirror.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" - -flat-cache@^3.0.4: - version "3.1.0" - resolved "https://registry.npmmirror.com/flat-cache/-/flat-cache-3.1.0.tgz#0e54ab4a1a60fe87e2946b6b00657f1c99e1af3f" - integrity sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew== - dependencies: - flatted "^3.2.7" - keyv "^4.5.3" - rimraf "^3.0.2" - -flatted@^3.2.7: - version "3.2.9" - resolved "https://registry.npmmirror.com/flatted/-/flatted-3.2.9.tgz#7eb4c67ca1ba34232ca9d2d93e9886e611ad7daf" - integrity sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ== - -flatten@^1.0.2: - version "1.0.3" - resolved "https://registry.npmmirror.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b" - integrity sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg== - -follow-redirects@^1.14.9: - version "1.15.2" - resolved "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" - integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== - -for-each@^0.3.3: - version "0.3.3" - resolved "https://registry.npmmirror.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" - integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== - dependencies: - is-callable "^1.1.3" - -fork-ts-checker-webpack-plugin@8.0.0: - version "8.0.0" - resolved "https://registry.npmmirror.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-8.0.0.tgz#dae45dfe7298aa5d553e2580096ced79b6179504" - integrity sha512-mX3qW3idpueT2klaQXBzrIM/pHw+T0B/V9KHEvNrqijTq9NFnMZU6oreVxDYcf33P8a5cW+67PjodNHthGnNVg== - dependencies: - "@babel/code-frame" "^7.16.7" - chalk "^4.1.2" - chokidar "^3.5.3" - cosmiconfig "^7.0.1" - deepmerge "^4.2.2" - fs-extra "^10.0.0" - memfs "^3.4.1" - minimatch "^3.0.4" - node-abort-controller "^3.0.1" - schema-utils "^3.1.1" - semver "^7.3.5" - tapable "^2.2.1" - -form-data@^4.0.0: - version "4.0.0" - resolved "https://registry.npmmirror.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" - integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - mime-types "^2.1.12" - -formdata-polyfill@^4.0.10: - version "4.0.10" - resolved "https://registry.npmmirror.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423" - integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g== - dependencies: - fetch-blob "^3.1.2" - -fraction.js@^4.2.0: - version "4.2.0" - resolved "https://registry.npmmirror.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950" - integrity sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA== - -fs-extra@^10.0.0, fs-extra@^10.1.0: - version "10.1.0" - resolved "https://registry.npmmirror.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" - integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - -fs-extra@^8.1.0: - version "8.1.0" - resolved "https://registry.npmmirror.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" - integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^4.0.0" - universalify "^0.1.0" - -fs-extra@^9.0.0, fs-extra@^9.0.1: - version "9.1.0" - resolved "https://registry.npmmirror.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" - integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== - dependencies: - at-least-node "^1.0.0" - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - -fs-minipass@^2.0.0: - version "2.1.0" - resolved "https://registry.npmmirror.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" - integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== - dependencies: - minipass "^3.0.0" - -fs-monkey@^1.0.4: - version "1.0.4" - resolved "https://registry.npmmirror.com/fs-monkey/-/fs-monkey-1.0.4.tgz#ee8c1b53d3fe8bb7e5d2c5c5dfc0168afdd2f747" - integrity sha512-INM/fWAxMICjttnD0DX1rBvinKskj5G1w+oy/pnm9u/tSlnBrzFonJMcalKJ30P8RRsPzKcCG7Q8l0jx5Fh9YQ== - -fs-readdir-recursive@^1.1.0: - version "1.1.0" - resolved "https://registry.npmmirror.com/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz#e32fc030a2ccee44a6b5371308da54be0b397d27" - integrity sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA== - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== - -fsevents@^2.3.2, fsevents@~2.3.2: - version "2.3.2" - resolved "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" - integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== - -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== - -function.prototype.name@^1.1.5: - version "1.1.5" - resolved "https://registry.npmmirror.com/function.prototype.name/-/function.prototype.name-1.1.5.tgz#cce0505fe1ffb80503e6f9e46cc64e46a12a9621" - integrity sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.0" - functions-have-names "^1.2.2" - -function.prototype.name@^1.1.6: - version "1.1.6" - resolved "https://registry.npmmirror.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" - integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - functions-have-names "^1.2.3" - -functions-have-names@^1.2.2, functions-have-names@^1.2.3: - version "1.2.3" - resolved "https://registry.npmmirror.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" - integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== - -gensync@^1.0.0-beta.2: - version "1.0.0-beta.2" - resolved "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" - integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== - -get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - -get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0, get-intrinsic@^1.2.1: - version "1.2.1" - resolved "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82" - integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw== - dependencies: - function-bind "^1.1.1" - has "^1.0.3" - has-proto "^1.0.1" - has-symbols "^1.0.3" - -get-package-type@^0.1.0: - version "0.1.0" - resolved "https://registry.npmmirror.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" - integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== - -get-stdin@=8.0.0: - version "8.0.0" - resolved "https://registry.npmmirror.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53" - integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg== - -get-stdin@^9.0.0: - version "9.0.0" - resolved "https://registry.npmmirror.com/get-stdin/-/get-stdin-9.0.0.tgz#3983ff82e03d56f1b2ea0d3e60325f39d703a575" - integrity sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA== - -get-stream@^5.1.0: - version "5.2.0" - resolved "https://registry.npmmirror.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" - integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== - dependencies: - pump "^3.0.0" - -get-stream@^6.0.0, get-stream@^6.0.1: - version "6.0.1" - resolved "https://registry.npmmirror.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" - integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== - -get-symbol-description@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" - integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.1.1" - -get-tsconfig@^4.4.0: - version "4.6.2" - resolved "https://registry.npmmirror.com/get-tsconfig/-/get-tsconfig-4.6.2.tgz#831879a5e6c2aa24fe79b60340e2233a1e0f472e" - integrity sha512-E5XrT4CbbXcXWy+1jChlZmrmCwd5KGx502kDCXJJ7y898TtWW9FwoG5HfOLVRKmlmDGkWN2HM9Ho+/Y8F0sJDg== - dependencies: - resolve-pkg-maps "^1.0.0" - -git-hooks-list@^3.0.0: - version "3.1.0" - resolved "https://registry.npmmirror.com/git-hooks-list/-/git-hooks-list-3.1.0.tgz#386dc531dcc17474cf094743ff30987a3d3e70fc" - integrity sha512-LF8VeHeR7v+wAbXqfgRlTSX/1BJR9Q1vEMR8JAz1cEg6GX07+zyj3sAdDvYjj/xnlIfVuGgj4qBei1K3hKH+PA== - -glob-parent@^5.1.2, glob-parent@~5.1.2: - version "5.1.2" - resolved "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob-parent@^6.0.2: - version "6.0.2" - resolved "https://registry.npmmirror.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" - integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== - dependencies: - is-glob "^4.0.3" - -glob@7.1.6: - version "7.1.6" - resolved "https://registry.npmmirror.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.2.0: - version "7.2.3" - resolved "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.1.1" - once "^1.3.0" - path-is-absolute "^1.0.0" - -global-agent@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/global-agent/-/global-agent-3.0.0.tgz#ae7cd31bd3583b93c5a16437a1afe27cc33a1ab6" - integrity sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q== - dependencies: - boolean "^3.0.1" - es6-error "^4.1.1" - matcher "^3.0.0" - roarr "^2.15.3" - semver "^7.3.2" - serialize-error "^7.0.1" - -global@^4.3.2: - version "4.4.0" - resolved "https://registry.npmmirror.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406" - integrity sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w== - dependencies: - min-document "^2.19.0" - process "^0.11.10" - -globals@^11.1.0: - version "11.12.0" - resolved "https://registry.npmmirror.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" - integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== - -globals@^13.19.0: - version "13.21.0" - resolved "https://registry.npmmirror.com/globals/-/globals-13.21.0.tgz#163aae12f34ef502f5153cfbdd3600f36c63c571" - integrity sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg== - dependencies: - type-fest "^0.20.2" - -globalthis@^1.0.1, globalthis@^1.0.3: - version "1.0.3" - resolved "https://registry.npmmirror.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" - integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA== - dependencies: - define-properties "^1.1.3" - -globby@^11.1.0: - version "11.1.0" - resolved "https://registry.npmmirror.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" - integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== - dependencies: - array-union "^2.1.0" - dir-glob "^3.0.1" - fast-glob "^3.2.9" - ignore "^5.2.0" - merge2 "^1.4.1" - slash "^3.0.0" - -globby@^13.1.2: - version "13.2.2" - resolved "https://registry.npmmirror.com/globby/-/globby-13.2.2.tgz#63b90b1bf68619c2135475cbd4e71e66aa090592" - integrity sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w== - dependencies: - dir-glob "^3.0.1" - fast-glob "^3.3.0" - ignore "^5.2.4" - merge2 "^1.4.1" - slash "^4.0.0" - -gopd@^1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" - integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== - dependencies: - get-intrinsic "^1.1.3" - -got@^11.8.5: - version "11.8.6" - resolved "https://registry.npmmirror.com/got/-/got-11.8.6.tgz#276e827ead8772eddbcfc97170590b841823233a" - integrity sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g== - dependencies: - "@sindresorhus/is" "^4.0.0" - "@szmarczak/http-timer" "^4.0.5" - "@types/cacheable-request" "^6.0.1" - "@types/responselike" "^1.0.0" - cacheable-lookup "^5.0.3" - cacheable-request "^7.0.2" - decompress-response "^6.0.0" - http2-wrapper "^1.0.0-beta.5.2" - lowercase-keys "^2.0.0" - p-cancelable "^2.0.0" - responselike "^2.0.0" - -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.9: - version "4.2.11" - resolved "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" - integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== - -"graceful-readlink@>= 1.0.0": - version "1.0.1" - resolved "https://registry.npmmirror.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" - integrity sha512-8tLu60LgxF6XpdbK8OW3FA+IfTNBn1ZHGHKF4KQbEeSkajYw5PlYJcKluntgegDPTg8UkHjpet1T82vk6TQ68w== - -graphemer@^1.4.0: - version "1.4.0" - resolved "https://registry.npmmirror.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" - integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== - -handle-thing@^2.0.0: - version "2.0.1" - resolved "https://registry.npmmirror.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" - integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== - -harmony-reflect@^1.4.6: - version "1.6.2" - resolved "https://registry.npmmirror.com/harmony-reflect/-/harmony-reflect-1.6.2.tgz#31ecbd32e648a34d030d86adb67d4d47547fe710" - integrity sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g== - -has-bigints@^1.0.1, has-bigints@^1.0.2: - version "1.0.2" - resolved "https://registry.npmmirror.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" - integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -has-property-descriptors@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861" - integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== - dependencies: - get-intrinsic "^1.1.1" - -has-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" - integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== - -has-symbols@^1.0.2, has-symbols@^1.0.3: - version "1.0.3" - resolved "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" - integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== - -has-tostringtag@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" - integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== - dependencies: - has-symbols "^1.0.2" - -has@^1.0.3: - version "1.0.3" - resolved "https://registry.npmmirror.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== - dependencies: - function-bind "^1.1.1" - -hash-base@^3.0.0: - version "3.1.0" - resolved "https://registry.npmmirror.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33" - integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA== - dependencies: - inherits "^2.0.4" - readable-stream "^3.6.0" - safe-buffer "^5.2.0" - -hash.js@^1.0.0, hash.js@^1.0.3: - version "1.1.7" - resolved "https://registry.npmmirror.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" - integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== - dependencies: - inherits "^2.0.3" - minimalistic-assert "^1.0.1" - -he@^1.2.0: - version "1.2.0" - resolved "https://registry.npmmirror.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" - integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== - -highlight.js@^11.9.0: - version "11.9.0" - resolved "https://registry.npmmirror.com/highlight.js/-/highlight.js-11.9.0.tgz#04ab9ee43b52a41a047432c8103e2158a1b8b5b0" - integrity sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw== - -history@5.3.0, history@^5.2.0: - version "5.3.0" - resolved "https://registry.npmmirror.com/history/-/history-5.3.0.tgz#1548abaa245ba47992f063a0783db91ef201c73b" - integrity sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ== - dependencies: - "@babel/runtime" "^7.7.6" - -hmac-drbg@^1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" - integrity sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg== - dependencies: - hash.js "^1.0.3" - minimalistic-assert "^1.0.0" - minimalistic-crypto-utils "^1.0.1" - -hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2: - version "3.3.2" - resolved "https://registry.npmmirror.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" - integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== - dependencies: - react-is "^16.7.0" - -hosted-git-info@^4.1.0: - version "4.1.0" - resolved "https://registry.npmmirror.com/hosted-git-info/-/hosted-git-info-4.1.0.tgz#827b82867e9ff1c8d0c4d9d53880397d2c86d224" - integrity sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA== - dependencies: - lru-cache "^6.0.0" - -hpack.js@^2.1.6: - version "2.1.6" - resolved "https://registry.npmmirror.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" - integrity sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ== - dependencies: - inherits "^2.0.1" - obuf "^1.0.0" - readable-stream "^2.0.1" - wbuf "^1.1.0" - -htm@^3.1.0: - version "3.1.1" - resolved "https://registry.npmmirror.com/htm/-/htm-3.1.1.tgz#49266582be0dc66ed2235d5ea892307cc0c24b78" - integrity sha512-983Vyg8NwUE7JkZ6NmOqpCZ+sh1bKv2iYTlUkzlWmA5JD2acKoxd4KVxbMmxX/85mtfdnDmTFoNKcg5DGAvxNQ== - -html-entities@^2.1.0: - version "2.4.0" - resolved "https://registry.npmmirror.com/html-entities/-/html-entities-2.4.0.tgz#edd0cee70402584c8c76cc2c0556db09d1f45061" - integrity sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ== - -html-minifier-terser@^6.0.2: - version "6.1.0" - resolved "https://registry.npmmirror.com/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#bfc818934cc07918f6b3669f5774ecdfd48f32ab" - integrity sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw== - dependencies: - camel-case "^4.1.2" - clean-css "^5.2.2" - commander "^8.3.0" - he "^1.2.0" - param-case "^3.0.4" - relateurl "^0.2.7" - terser "^5.10.0" - -html-webpack-plugin@5.5.0: - version "5.5.0" - resolved "https://registry.npmmirror.com/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz#c3911936f57681c1f9f4d8b68c158cd9dfe52f50" - integrity sha512-sy88PC2cRTVxvETRgUHFrL4No3UxvcH8G1NepGhqaTT+GXN2kTamqasot0inS5hXeg1cMbFDt27zzo9p35lZVw== - dependencies: - "@types/html-minifier-terser" "^6.0.0" - html-minifier-terser "^6.0.2" - lodash "^4.17.21" - pretty-error "^4.0.0" - tapable "^2.0.0" - -htmlparser2@^6.1.0: - version "6.1.0" - resolved "https://registry.npmmirror.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7" - integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A== - dependencies: - domelementtype "^2.0.1" - domhandler "^4.0.0" - domutils "^2.5.2" - entities "^2.0.0" - -http-cache-semantics@^4.0.0: - version "4.1.1" - resolved "https://registry.npmmirror.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" - integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== - -http-deceiver@^1.2.7: - version "1.2.7" - resolved "https://registry.npmmirror.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" - integrity sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw== - -http-proxy-agent@^5.0.0: - version "5.0.0" - resolved "https://registry.npmmirror.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" - integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w== - dependencies: - "@tootallnate/once" "2" - agent-base "6" - debug "4" - -http2-wrapper@^1.0.0-beta.5.2: - version "1.0.3" - resolved "https://registry.npmmirror.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d" - integrity sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg== - dependencies: - quick-lru "^5.1.1" - resolve-alpn "^1.0.0" - -https-browserify@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" - integrity sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg== - -https-proxy-agent@^5.0.0: - version "5.0.1" - resolved "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" - integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== - dependencies: - agent-base "6" - debug "4" - -human-signals@^2.1.0: - version "2.1.0" - resolved "https://registry.npmmirror.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" - integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== - -human-signals@^4.3.0: - version "4.3.1" - resolved "https://registry.npmmirror.com/human-signals/-/human-signals-4.3.1.tgz#ab7f811e851fca97ffbd2c1fe9a958964de321b2" - integrity sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ== - -iconv-corefoundation@^1.1.7: - version "1.1.7" - resolved "https://registry.npmmirror.com/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz#31065e6ab2c9272154c8b0821151e2c88f1b002a" - integrity sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ== - dependencies: - cli-truncate "^2.1.0" - node-addon-api "^1.6.3" - -iconv-lite@^0.6.2, iconv-lite@^0.6.3: - version "0.6.3" - resolved "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" - integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== - dependencies: - safer-buffer ">= 2.1.2 < 3.0.0" - -icss-utils@^5.0.0, icss-utils@^5.1.0: - version "5.1.0" - resolved "https://registry.npmmirror.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" - integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== - -identity-obj-proxy@3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz#94d2bda96084453ef36fbc5aaec37e0f79f1fc14" - integrity sha512-00n6YnVHKrinT9t0d9+5yZC6UBNJANpYEQvL2LlX6Ab9lnmxzIRcEmTPuyGScvl1+jKuCICX1Z0Ab1pPKKdikA== - dependencies: - harmony-reflect "^1.4.6" - -ieee754@^1.1.13, ieee754@^1.1.4: - version "1.2.1" - resolved "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" - integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== - -ignore@^5.2.0, ignore@^5.2.4: - version "5.2.4" - resolved "https://registry.npmmirror.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" - integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== - -image-size@~0.5.0: - version "0.5.5" - resolved "https://registry.npmmirror.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c" - integrity sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ== - -immer@^8.0.4: - version "8.0.4" - resolved "https://registry.npmmirror.com/immer/-/immer-8.0.4.tgz#3a21605a4e2dded852fb2afd208ad50969737b7a" - integrity sha512-jMfL18P+/6P6epANRvRk6q8t+3gGhqsJ9EuJ25AXE+9bNTYtssvzeYbEd0mXRYWCmmXSIbnlpz6vd6iJlmGGGQ== - -import-fresh@^3.2.1: - version "3.3.0" - resolved "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" - integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== - dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" - -import-html-entry@^1.14.5: - version "1.14.6" - resolved "https://registry.npmmirror.com/import-html-entry/-/import-html-entry-1.14.6.tgz#6bf54930e074d8264cbd040dcefb92062a3093d7" - integrity sha512-5MQkbwIr8n/bXOoE05M5/Nm0lnHO46vnb3D6svSvtVwpDqwhd/X14zjLcU31QWZ6gL8rUXNzj6vKHx4yOUL6gQ== - dependencies: - "@babel/runtime" "^7.7.2" - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.npmmirror.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: - version "2.0.4" - resolved "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -inherits@2.0.1: - version "2.0.1" - resolved "https://registry.npmmirror.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" - integrity sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA== - -inherits@2.0.3: - version "2.0.3" - resolved "https://registry.npmmirror.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== - -internal-slot@^1.0.3, internal-slot@^1.0.4, internal-slot@^1.0.5: - version "1.0.5" - resolved "https://registry.npmmirror.com/internal-slot/-/internal-slot-1.0.5.tgz#f2a2ee21f668f8627a4667f309dc0f4fb6674986" - integrity sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ== - dependencies: - get-intrinsic "^1.2.0" - has "^1.0.3" - side-channel "^1.0.4" - -interpret@^1.4.0: - version "1.4.0" - resolved "https://registry.npmmirror.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" - integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== - -intersection-observer@^0.12.0: - version "0.12.2" - resolved "https://registry.npmmirror.com/intersection-observer/-/intersection-observer-0.12.2.tgz#4a45349cc0cd91916682b1f44c28d7ec737dc375" - integrity sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg== - -intl-format-cache@^4.2.21: - version "4.3.1" - resolved "https://registry.npmmirror.com/intl-format-cache/-/intl-format-cache-4.3.1.tgz#484d31a9872161e6c02139349b259a6229ade377" - integrity sha512-OEUYNA7D06agqPOYhbTkl0T8HA3QKSuwWh1HiClEnpd9vw7N+3XsQt5iZ0GUEchp5CW1fQk/tary+NsbF3yQ1Q== - -intl-messageformat-parser@^3.6.4: - version "3.6.4" - resolved "https://registry.npmmirror.com/intl-messageformat-parser/-/intl-messageformat-parser-3.6.4.tgz#5199d106d816c3dda26ee0694362a9cf823978fb" - integrity sha512-RgPGwue0mJtoX2Ax8EmMzJzttxjnva7gx0Q7mKJ4oALrTZvtmCeAw5Msz2PcjW4dtCh/h7vN/8GJCxZO1uv+OA== - dependencies: - "@formatjs/intl-unified-numberformat" "^3.2.0" - -intl-messageformat@^7.8.4: - version "7.8.4" - resolved "https://registry.npmmirror.com/intl-messageformat/-/intl-messageformat-7.8.4.tgz#c29146a06b9cd26662978a4d95fff2b133e3642f" - integrity sha512-yS0cLESCKCYjseCOGXuV4pxJm/buTfyCJ1nzQjryHmSehlptbZbn9fnlk1I9peLopZGGbjj46yHHiTAEZ1qOTA== - dependencies: - intl-format-cache "^4.2.21" - intl-messageformat-parser "^3.6.4" - -intl@1.2.5: - version "1.2.5" - resolved "https://registry.npmmirror.com/intl/-/intl-1.2.5.tgz#82244a2190c4e419f8371f5aa34daa3420e2abde" - integrity sha512-rK0KcPHeBFBcqsErKSpvZnrOmWOj+EmDkyJ57e90YWaQNqbcivcqmKDlHEeNprDWOsKzPsh1BfSpPQdDvclHVw== - -invariant@^2.2.1, invariant@^2.2.4: - version "2.2.4" - resolved "https://registry.npmmirror.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" - integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== - dependencies: - loose-envify "^1.0.0" - -is-arguments@^1.1.1: - version "1.1.1" - resolved "https://registry.npmmirror.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" - integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - -is-array-buffer@^3.0.1, is-array-buffer@^3.0.2: - version "3.0.2" - resolved "https://registry.npmmirror.com/is-array-buffer/-/is-array-buffer-3.0.2.tgz#f2653ced8412081638ecb0ebbd0c41c6e0aecbbe" - integrity sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.2.0" - is-typed-array "^1.1.10" - -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== - -is-arrow-function@^2.0.3: - version "2.0.3" - resolved "https://registry.npmmirror.com/is-arrow-function/-/is-arrow-function-2.0.3.tgz#29be2c2d8d9450852b8bbafb635ba7b8d8e87ec2" - integrity sha512-iDStzcT1FJMzx+TjCOK//uDugSe/Mif/8a+T0htydQ3qkJGvSweTZpVYz4hpJH0baloSPiAFQdA8WslAgJphvQ== - dependencies: - is-callable "^1.0.4" - -is-async-function@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/is-async-function/-/is-async-function-2.0.0.tgz#8e4418efd3e5d3a6ebb0164c05ef5afb69aa9646" - integrity sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA== - dependencies: - has-tostringtag "^1.0.0" - -is-bigint@^1.0.1, is-bigint@^1.0.4: - version "1.0.4" - resolved "https://registry.npmmirror.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" - integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== - dependencies: - has-bigints "^1.0.1" - -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - -is-boolean-object@^1.1.0, is-boolean-object@^1.1.2: - version "1.1.2" - resolved "https://registry.npmmirror.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" - integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - -is-callable@^1.0.4, is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.4, is-callable@^1.2.7: - version "1.2.7" - resolved "https://registry.npmmirror.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" - integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== - -is-ci@^3.0.0: - version "3.0.1" - resolved "https://registry.npmmirror.com/is-ci/-/is-ci-3.0.1.tgz#db6ecbed1bd659c43dac0f45661e7674103d1867" - integrity sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ== - dependencies: - ci-info "^3.2.0" - -is-core-module@^2.11.0, is-core-module@^2.9.0: - version "2.12.1" - resolved "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.12.1.tgz#0c0b6885b6f80011c71541ce15c8d66cf5a4f9fd" - integrity sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg== - dependencies: - has "^1.0.3" - -is-core-module@^2.13.0: - version "2.13.0" - resolved "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.13.0.tgz#bb52aa6e2cbd49a30c2ba68c42bf3435ba6072db" - integrity sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ== - dependencies: - has "^1.0.3" - -is-date-object@^1.0.1, is-date-object@^1.0.5: - version "1.0.5" - resolved "https://registry.npmmirror.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" - integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== - dependencies: - has-tostringtag "^1.0.0" - -is-docker@^2.0.0, is-docker@^2.1.1: - version "2.2.1" - resolved "https://registry.npmmirror.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" - integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== - -is-docker@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/is-docker/-/is-docker-3.0.0.tgz#90093aa3106277d8a77a5910dbae71747e15a200" - integrity sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ== - -is-electron@^2.2.2: - version "2.2.2" - resolved "https://registry.npmmirror.com/is-electron/-/is-electron-2.2.2.tgz#3778902a2044d76de98036f5dc58089ac4d80bb9" - integrity sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg== - -is-equal@^1.6.4: - version "1.6.4" - resolved "https://registry.npmmirror.com/is-equal/-/is-equal-1.6.4.tgz#9a51b9ff565637ca2452356e293e9c98a1490ea1" - integrity sha512-NiPOTBb5ahmIOYkJ7mVTvvB1bydnTzixvfO+59AjJKBpyjPBIULL3EHGxySyZijlVpewveJyhiLQThcivkkAtw== - dependencies: - es-get-iterator "^1.1.2" - functions-have-names "^1.2.2" - has "^1.0.3" - has-bigints "^1.0.1" - has-symbols "^1.0.2" - is-arrow-function "^2.0.3" - is-bigint "^1.0.4" - is-boolean-object "^1.1.2" - is-callable "^1.2.4" - is-date-object "^1.0.5" - is-generator-function "^1.0.10" - is-number-object "^1.0.6" - is-regex "^1.1.4" - is-string "^1.0.7" - is-symbol "^1.0.4" - isarray "^2.0.5" - object-inspect "^1.12.0" - object.entries "^1.1.5" - object.getprototypeof "^1.0.3" - which-boxed-primitive "^1.0.2" - which-collection "^1.0.1" - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== - -is-finalizationregistry@^1.0.2: - version "1.0.2" - resolved "https://registry.npmmirror.com/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz#c8749b65f17c133313e661b1289b95ad3dbd62e6" - integrity sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw== - dependencies: - call-bind "^1.0.2" - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-generator-function@^1.0.10: - version "1.0.10" - resolved "https://registry.npmmirror.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" - integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== - dependencies: - has-tostringtag "^1.0.0" - -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: - version "4.0.3" - resolved "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - -is-inside-container@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/is-inside-container/-/is-inside-container-1.0.0.tgz#e81fba699662eb31dbdaf26766a61d4814717ea4" - integrity sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA== - dependencies: - is-docker "^3.0.0" - -is-map@^2.0.1, is-map@^2.0.2: - version "2.0.2" - resolved "https://registry.npmmirror.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127" - integrity sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg== - -is-negative-zero@^2.0.2: - version "2.0.2" - resolved "https://registry.npmmirror.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" - integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== - -is-number-object@^1.0.4, is-number-object@^1.0.6: - version "1.0.7" - resolved "https://registry.npmmirror.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" - integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== - dependencies: - has-tostringtag "^1.0.0" - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-path-inside@^3.0.3: - version "3.0.3" - resolved "https://registry.npmmirror.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" - integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== - -is-plain-obj@^4.1.0: - version "4.1.0" - resolved "https://registry.npmmirror.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz#d65025edec3657ce032fd7db63c97883eaed71f0" - integrity sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg== - -is-plain-object@^2.0.3: - version "2.0.4" - resolved "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - -is-regex@^1.1.4: - version "1.1.4" - resolved "https://registry.npmmirror.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" - integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - -is-set@^2.0.1, is-set@^2.0.2: - version "2.0.2" - resolved "https://registry.npmmirror.com/is-set/-/is-set-2.0.2.tgz#90755fa4c2562dc1c5d4024760d6119b94ca18ec" - integrity sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g== - -is-shared-array-buffer@^1.0.2: - version "1.0.2" - resolved "https://registry.npmmirror.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79" - integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA== - dependencies: - call-bind "^1.0.2" - -is-stream@^1.0.1: - version "1.1.0" - resolved "https://registry.npmmirror.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" - integrity sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ== - -is-stream@^2.0.0: - version "2.0.1" - resolved "https://registry.npmmirror.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" - integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== - -is-stream@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/is-stream/-/is-stream-3.0.0.tgz#e6bfd7aa6bef69f4f472ce9bb681e3e57b4319ac" - integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA== - -is-string@^1.0.5, is-string@^1.0.7: - version "1.0.7" - resolved "https://registry.npmmirror.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" - integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== - dependencies: - has-tostringtag "^1.0.0" - -is-symbol@^1.0.2, is-symbol@^1.0.3, is-symbol@^1.0.4: - version "1.0.4" - resolved "https://registry.npmmirror.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" - integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== - dependencies: - has-symbols "^1.0.2" - -is-typed-array@^1.1.10, is-typed-array@^1.1.12, is-typed-array@^1.1.9: - version "1.1.12" - resolved "https://registry.npmmirror.com/is-typed-array/-/is-typed-array-1.1.12.tgz#d0bab5686ef4a76f7a73097b95470ab199c57d4a" - integrity sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg== - dependencies: - which-typed-array "^1.1.11" - -is-weakmap@^2.0.1: - version "2.0.1" - resolved "https://registry.npmmirror.com/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2" - integrity sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA== - -is-weakref@^1.0.2: - version "1.0.2" - resolved "https://registry.npmmirror.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" - integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== - dependencies: - call-bind "^1.0.2" - -is-weakset@^2.0.1: - version "2.0.2" - resolved "https://registry.npmmirror.com/is-weakset/-/is-weakset-2.0.2.tgz#4569d67a747a1ce5a994dfd4ef6dcea76e7c0a1d" - integrity sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.1.1" - -is-what@^3.14.1: - version "3.14.1" - resolved "https://registry.npmmirror.com/is-what/-/is-what-3.14.1.tgz#e1222f46ddda85dead0fd1c9df131760e77755c1" - integrity sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA== - -is-what@^4.1.8: - version "4.1.15" - resolved "https://registry.npmmirror.com/is-what/-/is-what-4.1.15.tgz#de43a81090417a425942d67b1ae86e7fae2eee0e" - integrity sha512-uKua1wfy3Yt+YqsD6mTUEa2zSi3G1oPlqTflgaPJ7z63vUGN5pxFpnQfeSLMFnJDEsdvOtkp1rUWkYjB4YfhgA== - -is-wsl@^2.2.0: - version "2.2.0" - resolved "https://registry.npmmirror.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" - integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== - dependencies: - is-docker "^2.0.0" - -isarray@0.0.1: - version "0.0.1" - resolved "https://registry.npmmirror.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" - integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ== - -isarray@^1.0.0, isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== - -isarray@^2.0.5: - version "2.0.5" - resolved "https://registry.npmmirror.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" - integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== - -isbinaryfile@^3.0.2: - version "3.0.3" - resolved "https://registry.npmmirror.com/isbinaryfile/-/isbinaryfile-3.0.3.tgz#5d6def3edebf6e8ca8cae9c30183a804b5f8be80" - integrity sha512-8cJBL5tTd2OS0dM4jz07wQd5g0dCCqIhUxPIGtZfa5L6hWlvV5MHTITy/DBAsF+Oe2LS1X3krBUhNwaGUWpWxw== - dependencies: - buffer-alloc "^1.2.0" - -isbinaryfile@^4.0.10: - version "4.0.10" - resolved "https://registry.npmmirror.com/isbinaryfile/-/isbinaryfile-4.0.10.tgz#0c5b5e30c2557a2f06febd37b7322946aaee42b3" - integrity sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - -isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.npmmirror.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== - -isomorphic-fetch@^2.2.1: - version "2.2.1" - resolved "https://registry.npmmirror.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" - integrity sha512-9c4TNAKYXM5PRyVcwUZrF3W09nQ+sO7+jydgs4ZGW9dhsLG2VOlISJABombdQqQRXCwuYG3sYV/puGf5rp0qmA== - dependencies: - node-fetch "^1.0.1" - whatwg-fetch ">=0.10.0" - -isomorphic-unfetch@4.0.2: - version "4.0.2" - resolved "https://registry.npmmirror.com/isomorphic-unfetch/-/isomorphic-unfetch-4.0.2.tgz#5fc04eeb1053b7b702278e2cf7a3f246cb3a9214" - integrity sha512-1Yd+CF/7al18/N2BDbsLBcp6RO3tucSW+jcLq24dqdX5MNbCNTw1z4BsGsp4zNmjr/Izm2cs/cEqZPp4kvWSCA== - dependencies: - node-fetch "^3.2.0" - unfetch "^5.0.0" - -istanbul-lib-coverage@^3.2.0: - version "3.2.0" - resolved "https://registry.npmmirror.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" - integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== - -istanbul-lib-instrument@^5.0.4: - version "5.2.1" - resolved "https://registry.npmmirror.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" - integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== - dependencies: - "@babel/core" "^7.12.3" - "@babel/parser" "^7.14.7" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-coverage "^3.2.0" - semver "^6.3.0" - -iterator.prototype@^1.1.2: - version "1.1.2" - resolved "https://registry.npmmirror.com/iterator.prototype/-/iterator.prototype-1.1.2.tgz#5e29c8924f01916cb9335f1ff80619dcff22b0c0" - integrity sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w== - dependencies: - define-properties "^1.2.1" - get-intrinsic "^1.2.1" - has-symbols "^1.0.3" - reflect.getprototypeof "^1.0.4" - set-function-name "^2.0.1" - -jake@^10.8.5: - version "10.8.7" - resolved "https://registry.npmmirror.com/jake/-/jake-10.8.7.tgz#63a32821177940c33f356e0ba44ff9d34e1c7d8f" - integrity sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w== - dependencies: - async "^3.2.3" - chalk "^4.0.2" - filelist "^1.0.4" - minimatch "^3.1.2" - -jest-haste-map@^29.6.1: - version "29.6.1" - resolved "https://registry.npmmirror.com/jest-haste-map/-/jest-haste-map-29.6.1.tgz#62655c7a1c1b349a3206441330fb2dbdb4b63803" - integrity sha512-0m7f9PZXxOCk1gRACiVgX85knUKPKLPg4oRCjLoqIm9brTHXaorMA0JpmtmVkQiT8nmXyIVoZd/nnH1cfC33ig== - dependencies: - "@jest/types" "^29.6.1" - "@types/graceful-fs" "^4.1.3" - "@types/node" "*" - anymatch "^3.0.3" - fb-watchman "^2.0.0" - graceful-fs "^4.2.9" - jest-regex-util "^29.4.3" - jest-util "^29.6.1" - jest-worker "^29.6.1" - micromatch "^4.0.4" - walker "^1.0.8" - optionalDependencies: - fsevents "^2.3.2" - -jest-regex-util@^29.4.3: - version "29.4.3" - resolved "https://registry.npmmirror.com/jest-regex-util/-/jest-regex-util-29.4.3.tgz#a42616141e0cae052cfa32c169945d00c0aa0bb8" - integrity sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg== - -jest-util@^29.4.3, jest-util@^29.6.1: - version "29.6.1" - resolved "https://registry.npmmirror.com/jest-util/-/jest-util-29.6.1.tgz#c9e29a87a6edbf1e39e6dee2b4689b8a146679cb" - integrity sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg== - dependencies: - "@jest/types" "^29.6.1" - "@types/node" "*" - chalk "^4.0.0" - ci-info "^3.2.0" - graceful-fs "^4.2.9" - picomatch "^2.2.3" - -jest-worker@29.4.3: - version "29.4.3" - resolved "https://registry.npmmirror.com/jest-worker/-/jest-worker-29.4.3.tgz#9a4023e1ea1d306034237c7133d7da4240e8934e" - integrity sha512-GLHN/GTAAMEy5BFdvpUfzr9Dr80zQqBrh0fz1mtRMe05hqP45+HfQltu7oTBfduD0UeZs09d+maFtFYAXFWvAA== - dependencies: - "@types/node" "*" - jest-util "^29.4.3" - merge-stream "^2.0.0" - supports-color "^8.0.0" - -jest-worker@^29.6.1: - version "29.6.1" - resolved "https://registry.npmmirror.com/jest-worker/-/jest-worker-29.6.1.tgz#64b015f0e985ef3a8ad049b61fe92b3db74a5319" - integrity sha512-U+Wrbca7S8ZAxAe9L6nb6g8kPdia5hj32Puu5iOqBCMTMWFHXuK6dOV2IFrpedbTV8fjMFLdWNttQTBL6u2MRA== - dependencies: - "@types/node" "*" - jest-util "^29.6.1" - merge-stream "^2.0.0" - supports-color "^8.0.0" - -jiti@^1.18.2: - version "1.19.3" - resolved "https://registry.npmmirror.com/jiti/-/jiti-1.19.3.tgz#ef554f76465b3c2b222dc077834a71f0d4a37569" - integrity sha512-5eEbBDQT/jF1xg6l36P+mWGGoH9Spuy0PCdSr2dtWRDGC6ph/w9ZCL4lmESW8f8F7MwT3XKescfP0wnZWAKL9w== - -js-cookie@^2.x.x: - version "2.2.1" - resolved "https://registry.npmmirror.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8" - integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ== - -"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -js-yaml@^3.13.1: - version "3.14.1" - resolved "https://registry.npmmirror.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== - dependencies: - argparse "^2.0.1" - -jsesc@^2.5.1: - version "2.5.2" - resolved "https://registry.npmmirror.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" - integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== - -jsesc@~0.5.0: - version "0.5.0" - resolved "https://registry.npmmirror.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" - integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== - -json-buffer@3.0.1: - version "3.0.1" - resolved "https://registry.npmmirror.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" - integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== - -json-parse-even-better-errors@^2.3.0: - version "2.3.1" - resolved "https://registry.npmmirror.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" - integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-stable-stringify-without-jsonify@^1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" - integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== - -json-stringify-safe@^5.0.1: - version "5.0.1" - resolved "https://registry.npmmirror.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" - integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== - -json2mq@^0.2.0: - version "0.2.0" - resolved "https://registry.npmmirror.com/json2mq/-/json2mq-0.2.0.tgz#b637bd3ba9eabe122c83e9720483aeb10d2c904a" - integrity sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA== - dependencies: - string-convert "^0.2.0" - -json5@^1.0.2: - version "1.0.2" - resolved "https://registry.npmmirror.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" - integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== - dependencies: - minimist "^1.2.0" - -json5@^2.1.2, json5@^2.2.0, json5@^2.2.2, json5@^2.2.3: - version "2.2.3" - resolved "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" - integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== - -jsonfile@^4.0.0: - version "4.0.0" - resolved "https://registry.npmmirror.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" - integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== - optionalDependencies: - graceful-fs "^4.1.6" - -jsonfile@^6.0.1: - version "6.1.0" - resolved "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" - integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== - dependencies: - universalify "^2.0.0" - optionalDependencies: - graceful-fs "^4.1.6" - -"jsx-ast-utils@^2.4.1 || ^3.0.0": - version "3.3.4" - resolved "https://registry.npmmirror.com/jsx-ast-utils/-/jsx-ast-utils-3.3.4.tgz#b896535fed5b867650acce5a9bd4135ffc7b3bf9" - integrity sha512-fX2TVdCViod6HwKEtSWGHs57oFhVfCMwieb9PuRDgjDPh5XeqJiHFFFJCHxU5cnTc3Bu/GRL+kPiFmw8XWOfKw== - dependencies: - array-includes "^3.1.6" - array.prototype.flat "^1.3.1" - object.assign "^4.1.4" - object.values "^1.1.6" - -keyboardevent-from-electron-accelerator@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/keyboardevent-from-electron-accelerator/-/keyboardevent-from-electron-accelerator-2.0.0.tgz#ace21b1aa4e47148815d160057f9edb66567c50c" - integrity sha512-iQcmNA0M4ETMNi0kG/q0h/43wZk7rMeKYrXP7sqKIJbHkTU8Koowgzv+ieR/vWJbOwxx5nDC3UnudZ0aLSu4VA== - -keyboardevents-areequal@^0.2.1: - version "0.2.2" - resolved "https://registry.npmmirror.com/keyboardevents-areequal/-/keyboardevents-areequal-0.2.2.tgz#88191ec738ce9f7591c25e9056de928b40277194" - integrity sha512-Nv+Kr33T0mEjxR500q+I6IWisOQ0lK1GGOncV0kWE6n4KFmpcu7RUX5/2B0EUtX51Cb0HjZ9VJsSY3u4cBa0kw== - -keyv@^4.0.0: - version "4.5.4" - resolved "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" - integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== - dependencies: - json-buffer "3.0.1" - -keyv@^4.5.3: - version "4.5.3" - resolved "https://registry.npmmirror.com/keyv/-/keyv-4.5.3.tgz#00873d2b046df737963157bd04f294ca818c9c25" - integrity sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug== - dependencies: - json-buffer "3.0.1" - -kolorist@^1.6.0: - version "1.8.0" - resolved "https://registry.npmmirror.com/kolorist/-/kolorist-1.8.0.tgz#edddbbbc7894bc13302cdf740af6374d4a04743c" - integrity sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ== - -lazy-val@^1.0.4, lazy-val@^1.0.5: - version "1.0.5" - resolved "https://registry.npmmirror.com/lazy-val/-/lazy-val-1.0.5.tgz#6cf3b9f5bc31cee7ee3e369c0832b7583dcd923d" - integrity sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q== - -less-plugin-resolve@1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/less-plugin-resolve/-/less-plugin-resolve-1.0.0.tgz#c5cd9a4f75f24eccd69201df3a04b76eb5d806dc" - integrity sha512-offjRh1TfGsTgK0cqpl+RXFB0TFL6rPWy0yhCLhqhSEdWGVQp28K7wZ/ceUrRmWfZ5CSckYMe/KI+ViwaPLljQ== - dependencies: - enhanced-resolve "^5.15.0" - -less@4.1.3: - version "4.1.3" - resolved "https://registry.npmmirror.com/less/-/less-4.1.3.tgz#175be9ddcbf9b250173e0a00b4d6920a5b770246" - integrity sha512-w16Xk/Ta9Hhyei0Gpz9m7VS8F28nieJaL/VyShID7cYvP6IL5oHeL6p4TXSDJqZE/lNv0oJ2pGVjJsRkfwm5FA== - dependencies: - copy-anything "^2.0.1" - parse-node-version "^1.0.1" - tslib "^2.3.0" - optionalDependencies: - errno "^0.1.1" - graceful-fs "^4.1.2" - image-size "~0.5.0" - make-dir "^2.1.0" - mime "^1.4.1" - needle "^3.1.0" - source-map "~0.6.0" - -levn@^0.4.1: - version "0.4.1" - resolved "https://registry.npmmirror.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" - integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== - dependencies: - prelude-ls "^1.2.1" - type-check "~0.4.0" - -lightningcss-darwin-arm64@1.19.0: - version "1.19.0" - resolved "https://registry.npmmirror.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.19.0.tgz#56ab071e932f845dbb7667f44f5b78441175a343" - integrity sha512-wIJmFtYX0rXHsXHSr4+sC5clwblEMji7HHQ4Ub1/CznVRxtCFha6JIt5JZaNf8vQrfdZnBxLLC6R8pC818jXqg== - -lightningcss-darwin-x64@1.19.0: - version "1.19.0" - resolved "https://registry.npmmirror.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.19.0.tgz#c867308b88859ba61a2c46c82b1ca52ff73a1bd0" - integrity sha512-Lif1wD6P4poaw9c/4Uh2z+gmrWhw/HtXFoeZ3bEsv6Ia4tt8rOJBdkfVaUJ6VXmpKHALve+iTyP2+50xY1wKPw== - -lightningcss-linux-arm-gnueabihf@1.19.0: - version "1.19.0" - resolved "https://registry.npmmirror.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.19.0.tgz#0f921dc45f2e5c3aea70fab98844ac0e5f2f81be" - integrity sha512-P15VXY5682mTXaiDtbnLYQflc8BYb774j2R84FgDLJTN6Qp0ZjWEFyN1SPqyfTj2B2TFjRHRUvQSSZ7qN4Weig== - -lightningcss-linux-arm64-gnu@1.19.0: - version "1.19.0" - resolved "https://registry.npmmirror.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.19.0.tgz#027f9df9c7f4ffa127c37a71726245a5794d7ba2" - integrity sha512-zwXRjWqpev8wqO0sv0M1aM1PpjHz6RVIsBcxKszIG83Befuh4yNysjgHVplF9RTU7eozGe3Ts7r6we1+Qkqsww== - -lightningcss-linux-arm64-musl@1.19.0: - version "1.19.0" - resolved "https://registry.npmmirror.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.19.0.tgz#85ea987da868524eac6db94f8e1eaa23d0b688a3" - integrity sha512-vSCKO7SDnZaFN9zEloKSZM5/kC5gbzUjoJQ43BvUpyTFUX7ACs/mDfl2Eq6fdz2+uWhUh7vf92c4EaaP4udEtA== - -lightningcss-linux-x64-gnu@1.19.0: - version "1.19.0" - resolved "https://registry.npmmirror.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.19.0.tgz#02bec89579ab4153dccc0def755d1fd9e3ee7f3c" - integrity sha512-0AFQKvVzXf9byrXUq9z0anMGLdZJS+XSDqidyijI5njIwj6MdbvX2UZK/c4FfNmeRa2N/8ngTffoIuOUit5eIQ== - -lightningcss-linux-x64-musl@1.19.0: - version "1.19.0" - resolved "https://registry.npmmirror.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.19.0.tgz#e36a5df8193ae961d22974635e4c100a1823bb8c" - integrity sha512-SJoM8CLPt6ECCgSuWe+g0qo8dqQYVcPiW2s19dxkmSI5+Uu1GIRzyKA0b7QqmEXolA+oSJhQqCmJpzjY4CuZAg== - -lightningcss-win32-x64-msvc@1.19.0: - version "1.19.0" - resolved "https://registry.npmmirror.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.19.0.tgz#0854dbd153035eca1396e2227c708ad43655a61c" - integrity sha512-C+VuUTeSUOAaBZZOPT7Etn/agx/MatzJzGRkeV+zEABmPuntv1zihncsi+AyGmjkkzq3wVedEy7h0/4S84mUtg== - -lightningcss@1.19.0: - version "1.19.0" - resolved "https://registry.npmmirror.com/lightningcss/-/lightningcss-1.19.0.tgz#fbbad0975de66252e38d96b5bdd2a62f2dd0ffbf" - integrity sha512-yV5UR7og+Og7lQC+70DA7a8ta1uiOPnWPJfxa0wnxylev5qfo4P+4iMpzWAdYWOca4jdNQZii+bDL/l+4hUXIA== - dependencies: - detect-libc "^1.0.3" - optionalDependencies: - lightningcss-darwin-arm64 "1.19.0" - lightningcss-darwin-x64 "1.19.0" - lightningcss-linux-arm-gnueabihf "1.19.0" - lightningcss-linux-arm64-gnu "1.19.0" - lightningcss-linux-arm64-musl "1.19.0" - lightningcss-linux-x64-gnu "1.19.0" - lightningcss-linux-x64-musl "1.19.0" - lightningcss-win32-x64-msvc "1.19.0" - -lilconfig@^2.0.5, lilconfig@^2.1.0: - version "2.1.0" - resolved "https://registry.npmmirror.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52" - integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ== - -lines-and-columns@^1.1.6: - version "1.2.4" - resolved "https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" - integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== - -loader-utils@^2.0.2, loader-utils@^2.0.4: - version "2.0.4" - resolved "https://registry.npmmirror.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c" - integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw== - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^2.1.2" - -local-pkg@^0.4.2: - version "0.4.3" - resolved "https://registry.npmmirror.com/local-pkg/-/local-pkg-0.4.3.tgz#0ff361ab3ae7f1c19113d9bb97b98b905dbc4963" - integrity sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g== - -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.npmmirror.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== - dependencies: - p-locate "^4.1.0" - -locate-path@^6.0.0: - version "6.0.0" - resolved "https://registry.npmmirror.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" - integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== - dependencies: - p-locate "^5.0.0" - -lodash.debounce@^4.0.8: - version "4.0.8" - resolved "https://registry.npmmirror.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" - integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== - -lodash.merge@^4.6.2: - version "4.6.2" - resolved "https://registry.npmmirror.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" - integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== - -lodash.throttle@^4.1.1: - version "4.1.1" - resolved "https://registry.npmmirror.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" - integrity sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ== - -lodash.tonumber@^4.0.3: - version "4.0.3" - resolved "https://registry.npmmirror.com/lodash.tonumber/-/lodash.tonumber-4.0.3.tgz#0b96b31b35672793eb7f5a63ee791f1b9e9025d9" - integrity sha512-SY0SwuPOHRwKcCNTdsntPYb+Zddz5mDUIVFABzRMqmAiL41pMeyoQFGxYAw5zdc9NnH4pbJqiqqp5ckfxa+zSA== - -lodash@^4.0.1, lodash@^4.17.11, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21: - version "4.17.21" - resolved "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - -loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: - version "1.4.0" - resolved "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" - integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== - dependencies: - js-tokens "^3.0.0 || ^4.0.0" - -lower-case@^2.0.2: - version "2.0.2" - resolved "https://registry.npmmirror.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" - integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg== - dependencies: - tslib "^2.0.3" - -lowercase-keys@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" - integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== - -lru-cache@^5.1.1: - version "5.1.1" - resolved "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" - integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== - dependencies: - yallist "^3.0.2" - -lru-cache@^6.0.0: - version "6.0.0" - resolved "https://registry.npmmirror.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" - integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== - dependencies: - yallist "^4.0.0" - -make-dir@^2.1.0: - version "2.1.0" - resolved "https://registry.npmmirror.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" - integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== - dependencies: - pify "^4.0.1" - semver "^5.6.0" - -makeerror@1.0.12: - version "1.0.12" - resolved "https://registry.npmmirror.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" - integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== - dependencies: - tmpl "1.0.5" - -markdown-it-link-attributes@^4.0.1: - version "4.0.1" - resolved "https://registry.npmmirror.com/markdown-it-link-attributes/-/markdown-it-link-attributes-4.0.1.tgz#25751f2cf74fd91f0a35ba7b3247fa45f2056d88" - integrity sha512-pg5OK0jPLg62H4k7M9mRJLT61gUp9nvG0XveKYHMOOluASo9OEF13WlXrpAp2aj35LbedAy3QOCgQCw0tkLKAQ== - -matcher@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/matcher/-/matcher-3.0.0.tgz#bd9060f4c5b70aa8041ccc6f80368760994f30ca" - integrity sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng== - dependencies: - escape-string-regexp "^4.0.0" - -md5.js@^1.3.4: - version "1.3.5" - resolved "https://registry.npmmirror.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" - integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== - dependencies: - hash-base "^3.0.0" - inherits "^2.0.1" - safe-buffer "^5.1.2" - -mdn-data@2.0.14: - version "2.0.14" - resolved "https://registry.npmmirror.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" - integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== - -memfs@^3.4.1: - version "3.6.0" - resolved "https://registry.npmmirror.com/memfs/-/memfs-3.6.0.tgz#d7a2110f86f79dd950a8b6df6d57bc984aa185f6" - integrity sha512-EGowvkkgbMcIChjMTMkESFDbZeSh8xZ7kNSF0hAiAN4Jh6jgHCRS0Ga/+C8y6Au+oqpezRHCfPsmJ2+DwAgiwQ== - dependencies: - fs-monkey "^1.0.4" - -memory-fs@^0.2.0: - version "0.2.0" - resolved "https://registry.npmmirror.com/memory-fs/-/memory-fs-0.2.0.tgz#f2bb25368bc121e391c2520de92969caee0a0290" - integrity sha512-+y4mDxU4rvXXu5UDSGCGNiesFmwCHuefGMoPCO1WYucNYj7DsLqrFaa2fXVI0H+NNiPTwwzKwspn9yTZqUGqng== - -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== - -merge2@^1.3.0, merge2@^1.4.1: - version "1.4.1" - resolved "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" - integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== - -micromatch@^4.0.4, micromatch@^4.0.5: - version "4.0.5" - resolved "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== - dependencies: - braces "^3.0.2" - picomatch "^2.3.1" - -miller-rabin@^4.0.0: - version "4.0.1" - resolved "https://registry.npmmirror.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" - integrity sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA== - dependencies: - bn.js "^4.0.0" - brorand "^1.0.1" - -mime-db@1.52.0: - version "1.52.0" - resolved "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -mime-types@^2.1.12: - version "2.1.35" - resolved "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - -mime@^1.4.1: - version "1.6.0" - resolved "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" - integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== - -mime@^2.5.2: - version "2.6.0" - resolved "https://registry.npmmirror.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" - integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== - -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.npmmirror.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - -mimic-fn@^4.0.0: - version "4.0.0" - resolved "https://registry.npmmirror.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" - integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== - -mimic-response@^1.0.0: - version "1.0.1" - resolved "https://registry.npmmirror.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" - integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== - -mimic-response@^3.1.0: - version "3.1.0" - resolved "https://registry.npmmirror.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" - integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== - -min-document@^2.19.0: - version "2.19.0" - resolved "https://registry.npmmirror.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" - integrity sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ== - dependencies: - dom-walk "^0.1.0" - -minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" - integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== - -minimalistic-crypto-utils@^1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" - integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== - -minimatch@3.0.4: - version "3.0.4" - resolved "https://registry.npmmirror.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: - version "3.1.2" - resolved "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^5.0.1: - version "5.1.6" - resolved "https://registry.npmmirror.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" - integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== - dependencies: - brace-expansion "^2.0.1" - -minimist@^1.2.0, minimist@^1.2.6: - version "1.2.8" - resolved "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" - integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== - -minipass@^3.0.0: - version "3.3.6" - resolved "https://registry.npmmirror.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" - integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== - dependencies: - yallist "^4.0.0" - -minipass@^5.0.0: - version "5.0.0" - resolved "https://registry.npmmirror.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" - integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== - -minizlib@^2.1.1: - version "2.1.2" - resolved "https://registry.npmmirror.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" - integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== - dependencies: - minipass "^3.0.0" - yallist "^4.0.0" - -mkdirp@^1.0.3: - version "1.0.4" - resolved "https://registry.npmmirror.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" - integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== - -moment@^2.29.4: - version "2.29.4" - resolved "https://registry.npmmirror.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108" - integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w== - -monaco-editor-esm-webpack-plugin@^2.1.0: - version "2.1.0" - resolved "https://registry.npmmirror.com/monaco-editor-esm-webpack-plugin/-/monaco-editor-esm-webpack-plugin-2.1.0.tgz#2518217587d704fdc5b9f384257cea0fb20c654c" - integrity sha512-wphHja+0IfI26RR1dIphrnnFxmcSVyY/YfP/hWkytIwWmrv3KdqtcqEAJsXYzNoT6lV33p01eSob0RqOXyv7kg== - -monaco-editor-webpack-plugin@^7.0.1: - version "7.1.0" - resolved "https://registry.npmmirror.com/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-7.1.0.tgz#16f265c2b5dbb5fe08681b6b3b7d00d3c5b2ee97" - integrity sha512-ZjnGINHN963JQkFqjjcBtn1XBtUATDZBMgNQhDQwd78w2ukRhFXAPNgWuacaQiDZsUr4h1rWv5Mv6eriKuOSzA== - dependencies: - loader-utils "^2.0.2" - -monaco-editor@^0.44.0: - version "0.44.0" - resolved "https://registry.npmmirror.com/monaco-editor/-/monaco-editor-0.44.0.tgz#3c0fe3655923bbf7dd647057302070b5095b6c59" - integrity sha512-5SmjNStN6bSuSE5WPT2ZV+iYn1/yI9sd4Igtk23ChvqB7kDk9lZbB9F5frsuvpB+2njdIeGGFf2G4gbE6rCC9Q== - -moo@^0.5.0: - version "0.5.2" - resolved "https://registry.npmmirror.com/moo/-/moo-0.5.2.tgz#f9fe82473bc7c184b0d32e2215d3f6e67278733c" - integrity sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q== - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== - -ms@2.1.2: - version "2.1.2" - resolved "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -ms@^2.1.1: - version "2.1.3" - resolved "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -mz@^2.7.0: - version "2.7.0" - resolved "https://registry.npmmirror.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" - integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== - dependencies: - any-promise "^1.0.0" - object-assign "^4.0.1" - thenify-all "^1.0.0" - -nanoid@^3.3.6: - version "3.3.6" - resolved "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" - integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== - -natural-compare-lite@^1.4.0: - version "1.4.0" - resolved "https://registry.npmmirror.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" - integrity sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g== - -natural-compare@^1.4.0: - version "1.4.0" - resolved "https://registry.npmmirror.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" - integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== - -nearley@^2.20.1: - version "2.20.1" - resolved "https://registry.npmmirror.com/nearley/-/nearley-2.20.1.tgz#246cd33eff0d012faf197ff6774d7ac78acdd474" - integrity sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ== - dependencies: - commander "^2.19.0" - moo "^0.5.0" - railroad-diagrams "^1.0.0" - randexp "0.4.6" - -needle@^3.1.0: - version "3.2.0" - resolved "https://registry.npmmirror.com/needle/-/needle-3.2.0.tgz#07d240ebcabfd65c76c03afae7f6defe6469df44" - integrity sha512-oUvzXnyLiVyVGoianLijF9O/RecZUf7TkBfimjGrLM4eQhXyeJwM6GeAWccwfQ9aa4gMCZKqhAOuLaMIcQxajQ== - dependencies: - debug "^3.2.6" - iconv-lite "^0.6.3" - sax "^1.2.4" - -next-tick@^1.1.0: - version "1.1.0" - resolved "https://registry.npmmirror.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" - integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== - -no-case@^3.0.4: - version "3.0.4" - resolved "https://registry.npmmirror.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" - integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg== - dependencies: - lower-case "^2.0.2" - tslib "^2.0.3" - -node-abort-controller@^3.0.1: - version "3.1.1" - resolved "https://registry.npmmirror.com/node-abort-controller/-/node-abort-controller-3.1.1.tgz#a94377e964a9a37ac3976d848cb5c765833b8548" - integrity sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ== - -node-addon-api@^1.6.3: - version "1.7.2" - resolved "https://registry.npmmirror.com/node-addon-api/-/node-addon-api-1.7.2.tgz#3df30b95720b53c24e59948b49532b662444f54d" - integrity sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg== - -node-domexception@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" - integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== - -node-fetch@^1.0.1: - version "1.7.3" - resolved "https://registry.npmmirror.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" - integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ== - dependencies: - encoding "^0.1.11" - is-stream "^1.0.1" - -node-fetch@^3.2.0: - version "3.3.1" - resolved "https://registry.npmmirror.com/node-fetch/-/node-fetch-3.3.1.tgz#b3eea7b54b3a48020e46f4f88b9c5a7430d20b2e" - integrity sha512-cRVc/kyto/7E5shrWca1Wsea4y6tL9iYJE5FBCius3JQfb/4P4I295PfhgbJQBLTx6lATE4z+wK0rPM4VS2uow== - dependencies: - data-uri-to-buffer "^4.0.0" - fetch-blob "^3.1.4" - formdata-polyfill "^4.0.10" - -node-int64@^0.4.0: - version "0.4.0" - resolved "https://registry.npmmirror.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" - integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== - -node-libs-browser@2.2.1: - version "2.2.1" - resolved "https://registry.npmmirror.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425" - integrity sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q== - dependencies: - assert "^1.1.1" - browserify-zlib "^0.2.0" - buffer "^4.3.0" - console-browserify "^1.1.0" - constants-browserify "^1.0.0" - crypto-browserify "^3.11.0" - domain-browser "^1.1.1" - events "^3.0.0" - https-browserify "^1.0.0" - os-browserify "^0.3.0" - path-browserify "0.0.1" - process "^0.11.10" - punycode "^1.2.4" - querystring-es3 "^0.2.0" - readable-stream "^2.3.3" - stream-browserify "^2.0.1" - stream-http "^2.7.2" - string_decoder "^1.0.0" - timers-browserify "^2.0.4" - tty-browserify "0.0.0" - url "^0.11.0" - util "^0.11.0" - vm-browserify "^1.0.1" - -node-releases@^2.0.12: - version "2.0.13" - resolved "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d" - integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ== - -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -normalize-range@^0.1.2: - version "0.1.2" - resolved "https://registry.npmmirror.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" - integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== - -normalize-url@^6.0.1: - version "6.1.0" - resolved "https://registry.npmmirror.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" - integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== - -npm-run-path@^4.0.1: - version "4.0.1" - resolved "https://registry.npmmirror.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" - integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== - dependencies: - path-key "^3.0.0" - -npm-run-path@^5.1.0: - version "5.1.0" - resolved "https://registry.npmmirror.com/npm-run-path/-/npm-run-path-5.1.0.tgz#bc62f7f3f6952d9894bd08944ba011a6ee7b7e00" - integrity sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q== - dependencies: - path-key "^4.0.0" - -nth-check@^2.0.1: - version "2.1.1" - resolved "https://registry.npmmirror.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" - integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== - dependencies: - boolbase "^1.0.0" - -object-assign@4.x, object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.1: - version "4.1.1" - resolved "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== - -object-hash@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9" - integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw== - -object-inspect@^1.12.0, object-inspect@^1.12.3, object-inspect@^1.9.0: - version "1.12.3" - resolved "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" - integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== - -object-keys@^1.1.1: - version "1.1.1" - resolved "https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" - integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== - -object.assign@^4.1.0, object.assign@^4.1.2, object.assign@^4.1.4: - version "4.1.4" - resolved "https://registry.npmmirror.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f" - integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - has-symbols "^1.0.3" - object-keys "^1.1.1" - -object.entries@^1.1.5, object.entries@^1.1.6: - version "1.1.6" - resolved "https://registry.npmmirror.com/object.entries/-/object.entries-1.1.6.tgz#9737d0e5b8291edd340a3e3264bb8a3b00d5fa23" - integrity sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" - -object.fromentries@^2.0.6: - version "2.0.6" - resolved "https://registry.npmmirror.com/object.fromentries/-/object.fromentries-2.0.6.tgz#cdb04da08c539cffa912dcd368b886e0904bfa73" - integrity sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" - -object.getprototypeof@^1.0.3: - version "1.0.4" - resolved "https://registry.npmmirror.com/object.getprototypeof/-/object.getprototypeof-1.0.4.tgz#d662d3d13f9cda65f01d1ea2ba86f0097676f83b" - integrity sha512-xV/FkUNM9sHa56AB5deXrlIR+jBtDAHieyfm6XZUuehqlMX+YJPh8CAYtPrXGA/mFLFttasTc9ihhpkPrH7pLw== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" - reflect.getprototypeof "^1.0.2" - -object.groupby@^1.0.0: - version "1.0.1" - resolved "https://registry.npmmirror.com/object.groupby/-/object.groupby-1.0.1.tgz#d41d9f3c8d6c778d9cbac86b4ee9f5af103152ee" - integrity sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - get-intrinsic "^1.2.1" - -object.hasown@^1.1.2: - version "1.1.2" - resolved "https://registry.npmmirror.com/object.hasown/-/object.hasown-1.1.2.tgz#f919e21fad4eb38a57bc6345b3afd496515c3f92" - integrity sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw== - dependencies: - define-properties "^1.1.4" - es-abstract "^1.20.4" - -object.values@^1.1.6: - version "1.1.6" - resolved "https://registry.npmmirror.com/object.values/-/object.values-1.1.6.tgz#4abbaa71eba47d63589d402856f908243eea9b1d" - integrity sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" - -obuf@^1.0.0, obuf@^1.1.2: - version "1.1.2" - resolved "https://registry.npmmirror.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" - integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== - -omit.js@^2.0.2: - version "2.0.2" - resolved "https://registry.npmmirror.com/omit.js/-/omit.js-2.0.2.tgz#dd9b8436fab947a5f3ff214cb2538631e313ec2f" - integrity sha512-hJmu9D+bNB40YpL9jYebQl4lsTW6yEHRTroJzNLqQJYHm7c+NQnJGfZmIWh8S3q3KoaxV1aLhV6B3+0N0/kyJg== - -on-exit-leak-free@^0.2.0: - version "0.2.0" - resolved "https://registry.npmmirror.com/on-exit-leak-free/-/on-exit-leak-free-0.2.0.tgz#b39c9e3bf7690d890f4861558b0d7b90a442d209" - integrity sha512-dqaz3u44QbRXQooZLTUKU41ZrzYrcvLISVgbrzbyCMxpmSLJvZ3ZamIJIZ29P6OhZIkNIQKosdeM6t1LYbA9hg== - -once@^1.3.0, once@^1.3.1, once@^1.4.0: - version "1.4.0" - resolved "https://registry.npmmirror.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== - dependencies: - wrappy "1" - -onetime@^5.1.2: - version "5.1.2" - resolved "https://registry.npmmirror.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" - integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== - dependencies: - mimic-fn "^2.1.0" - -onetime@^6.0.0: - version "6.0.0" - resolved "https://registry.npmmirror.com/onetime/-/onetime-6.0.0.tgz#7c24c18ed1fd2e9bca4bd26806a33613c77d34b4" - integrity sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ== - dependencies: - mimic-fn "^4.0.0" - -open@^8.4.0: - version "8.4.2" - resolved "https://registry.npmmirror.com/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9" - integrity sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ== - dependencies: - define-lazy-prop "^2.0.0" - is-docker "^2.1.1" - is-wsl "^2.2.0" - -open@^9.1.0: - version "9.1.0" - resolved "https://registry.npmmirror.com/open/-/open-9.1.0.tgz#684934359c90ad25742f5a26151970ff8c6c80b6" - integrity sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg== - dependencies: - default-browser "^4.0.0" - define-lazy-prop "^3.0.0" - is-inside-container "^1.0.0" - is-wsl "^2.2.0" - -optionator@^0.9.3: - version "0.9.3" - resolved "https://registry.npmmirror.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" - integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg== - dependencies: - "@aashutoshrathi/word-wrap" "^1.2.3" - deep-is "^0.1.3" - fast-levenshtein "^2.0.6" - levn "^0.4.1" - prelude-ls "^1.2.1" - type-check "^0.4.0" - -os-browserify@^0.3.0: - version "0.3.0" - resolved "https://registry.npmmirror.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" - integrity sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A== - -p-cancelable@^2.0.0: - version "2.1.1" - resolved "https://registry.npmmirror.com/p-cancelable/-/p-cancelable-2.1.1.tgz#aab7fbd416582fa32a3db49859c122487c5ed2cf" - integrity sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg== - -p-limit@^2.2.0: - version "2.3.0" - resolved "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-limit@^3.0.2: - version "3.1.0" - resolved "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.npmmirror.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== - dependencies: - p-limit "^2.2.0" - -p-locate@^5.0.0: - version "5.0.0" - resolved "https://registry.npmmirror.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" - integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== - dependencies: - p-limit "^3.0.2" - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.npmmirror.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -pako@~1.0.5: - version "1.0.11" - resolved "https://registry.npmmirror.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" - integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== - -param-case@^3.0.4: - version "3.0.4" - resolved "https://registry.npmmirror.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5" - integrity sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A== - dependencies: - dot-case "^3.0.4" - tslib "^2.0.3" - -parent-module@^1.0.0: - version "1.0.1" - resolved "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" - integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== - dependencies: - callsites "^3.0.0" - -parse-asn1@^5.0.0, parse-asn1@^5.1.5: - version "5.1.6" - resolved "https://registry.npmmirror.com/parse-asn1/-/parse-asn1-5.1.6.tgz#385080a3ec13cb62a62d39409cb3e88844cdaed4" - integrity sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw== - dependencies: - asn1.js "^5.2.0" - browserify-aes "^1.0.0" - evp_bytestokey "^1.0.0" - pbkdf2 "^3.0.3" - safe-buffer "^5.1.1" - -parse-json@^5.0.0: - version "5.2.0" - resolved "https://registry.npmmirror.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" - integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== - dependencies: - "@babel/code-frame" "^7.0.0" - error-ex "^1.3.1" - json-parse-even-better-errors "^2.3.0" - lines-and-columns "^1.1.6" - -parse-node-version@^1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/parse-node-version/-/parse-node-version-1.0.1.tgz#e2b5dbede00e7fa9bc363607f53327e8b073189b" - integrity sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA== - -pascal-case@^3.1.2: - version "3.1.2" - resolved "https://registry.npmmirror.com/pascal-case/-/pascal-case-3.1.2.tgz#b48e0ef2b98e205e7c1dae747d0b1508237660eb" - integrity sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g== - dependencies: - no-case "^3.0.4" - tslib "^2.0.3" - -path-browserify@0.0.1: - version "0.0.1" - resolved "https://registry.npmmirror.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" - integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ== - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== - -path-key@^3.0.0, path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-key@^4.0.0: - version "4.0.0" - resolved "https://registry.npmmirror.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18" - integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ== - -path-parse@^1.0.7: - version "1.0.7" - resolved "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - -path-to-regexp@1.7.0: - version "1.7.0" - resolved "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d" - integrity sha512-nifX1uj4S9IrK/w3Xe7kKvNEepXivANs9ng60Iq7PU/BlouV3yL/VUhFqTuTq33ykwUqoNcTeGo5vdOBP4jS/Q== - dependencies: - isarray "0.0.1" - -path-to-regexp@2.4.0: - version "2.4.0" - resolved "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-2.4.0.tgz#35ce7f333d5616f1c1e1bfe266c3aba2e5b2e704" - integrity sha512-G6zHoVqC6GGTQkZwF4lkuEyMbVOjoBKAEybQUypI1WTkqinCOrq2x6U2+phkJ1XsEMTy4LjtwPI7HW+NVrRR2w== - -path-type@^4.0.0: - version "4.0.0" - resolved "https://registry.npmmirror.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" - integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== - -pbkdf2@^3.0.3: - version "3.1.2" - resolved "https://registry.npmmirror.com/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075" - integrity sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA== - dependencies: - create-hash "^1.1.2" - create-hmac "^1.1.4" - ripemd160 "^2.0.1" - safe-buffer "^5.0.1" - sha.js "^2.4.8" - -pend@~1.2.0: - version "1.2.0" - resolved "https://registry.npmmirror.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" - integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== - -picocolors@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" - integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== - -picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.0, picomatch@^2.3.1: - version "2.3.1" - resolved "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -pify@^2.3.0: - version "2.3.0" - resolved "https://registry.npmmirror.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" - integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== - -pify@^4.0.1: - version "4.0.1" - resolved "https://registry.npmmirror.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" - integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== - -pino-abstract-transport@v0.5.0: - version "0.5.0" - resolved "https://registry.npmmirror.com/pino-abstract-transport/-/pino-abstract-transport-0.5.0.tgz#4b54348d8f73713bfd14e3dc44228739aa13d9c0" - integrity sha512-+KAgmVeqXYbTtU2FScx1XS3kNyfZ5TrXY07V96QnUSFqo2gAqlvmaxH67Lj7SWazqsMabf+58ctdTcBgnOLUOQ== - dependencies: - duplexify "^4.1.2" - split2 "^4.0.0" - -pino-std-serializers@^4.0.0: - version "4.0.0" - resolved "https://registry.npmmirror.com/pino-std-serializers/-/pino-std-serializers-4.0.0.tgz#1791ccd2539c091ae49ce9993205e2cd5dbba1e2" - integrity sha512-cK0pekc1Kjy5w9V2/n+8MkZwusa6EyyxfeQCB799CQRhRt/CqYKiWs5adeu8Shve2ZNffvfC/7J64A2PJo1W/Q== - -pino@7.11.0: - version "7.11.0" - resolved "https://registry.npmmirror.com/pino/-/pino-7.11.0.tgz#0f0ea5c4683dc91388081d44bff10c83125066f6" - integrity sha512-dMACeu63HtRLmCG8VKdy4cShCPKaYDR4youZqoSWLxl5Gu99HUw8bw75thbPv9Nip+H+QYX8o3ZJbTdVZZ2TVg== - dependencies: - atomic-sleep "^1.0.0" - fast-redact "^3.0.0" - on-exit-leak-free "^0.2.0" - pino-abstract-transport v0.5.0 - pino-std-serializers "^4.0.0" - process-warning "^1.0.0" - quick-format-unescaped "^4.0.3" - real-require "^0.1.0" - safe-stable-stringify "^2.1.0" - sonic-boom "^2.2.1" - thread-stream "^0.15.1" - -pirates@^4.0.1, pirates@^4.0.4: - version "4.0.6" - resolved "https://registry.npmmirror.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" - integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== - -plist@^3.0.1, plist@^3.0.4: - version "3.1.0" - resolved "https://registry.npmmirror.com/plist/-/plist-3.1.0.tgz#797a516a93e62f5bde55e0b9cc9c967f860893c9" - integrity sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ== - dependencies: - "@xmldom/xmldom" "^0.8.8" - base64-js "^1.5.1" - xmlbuilder "^15.1.1" - -point-in-polygon@^1.1.0: - version "1.1.0" - resolved "https://registry.npmmirror.com/point-in-polygon/-/point-in-polygon-1.1.0.tgz#b0af2616c01bdee341cbf2894df643387ca03357" - integrity sha512-3ojrFwjnnw8Q9242TzgXuTD+eKiutbzyslcq1ydfu82Db2y+Ogbmyrkpv0Hgj31qwT3lbS9+QAAO/pIQM35XRw== - -postcss-attribute-case-insensitive@^5.0.0: - version "5.0.2" - resolved "https://registry.npmmirror.com/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-5.0.2.tgz#03d761b24afc04c09e757e92ff53716ae8ea2741" - integrity sha512-XIidXV8fDr0kKt28vqki84fRK8VW8eTuIa4PChv2MqKuT6C9UjmSKzen6KaWhWEoYvwxFCa7n/tC1SZ3tyq4SQ== - dependencies: - postcss-selector-parser "^6.0.10" - -postcss-clamp@^4.1.0: - version "4.1.0" - resolved "https://registry.npmmirror.com/postcss-clamp/-/postcss-clamp-4.1.0.tgz#7263e95abadd8c2ba1bd911b0b5a5c9c93e02363" - integrity sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-color-functional-notation@^4.2.2: - version "4.2.4" - resolved "https://registry.npmmirror.com/postcss-color-functional-notation/-/postcss-color-functional-notation-4.2.4.tgz#21a909e8d7454d3612d1659e471ce4696f28caec" - integrity sha512-2yrTAUZUab9s6CpxkxC4rVgFEVaR6/2Pipvi6qcgvnYiVqZcbDHEoBDhrXzyb7Efh2CCfHQNtcqWcIruDTIUeg== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-color-hex-alpha@^8.0.3: - version "8.0.4" - resolved "https://registry.npmmirror.com/postcss-color-hex-alpha/-/postcss-color-hex-alpha-8.0.4.tgz#c66e2980f2fbc1a63f5b079663340ce8b55f25a5" - integrity sha512-nLo2DCRC9eE4w2JmuKgVA3fGL3d01kGq752pVALF68qpGLmx2Qrk91QTKkdUqqp45T1K1XV8IhQpcu1hoAQflQ== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-color-rebeccapurple@^7.0.2: - version "7.1.1" - resolved "https://registry.npmmirror.com/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-7.1.1.tgz#63fdab91d878ebc4dd4b7c02619a0c3d6a56ced0" - integrity sha512-pGxkuVEInwLHgkNxUc4sdg4g3py7zUeCQ9sMfwyHAT+Ezk8a4OaaVZ8lIY5+oNqA/BXXgLyXv0+5wHP68R79hg== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-custom-media@^8.0.0: - version "8.0.2" - resolved "https://registry.npmmirror.com/postcss-custom-media/-/postcss-custom-media-8.0.2.tgz#c8f9637edf45fef761b014c024cee013f80529ea" - integrity sha512-7yi25vDAoHAkbhAzX9dHx2yc6ntS4jQvejrNcC+csQJAXjj15e7VcWfMgLqBNAbOvqi5uIa9huOVwdHbf+sKqg== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-custom-properties@^12.1.7: - version "12.1.11" - resolved "https://registry.npmmirror.com/postcss-custom-properties/-/postcss-custom-properties-12.1.11.tgz#d14bb9b3989ac4d40aaa0e110b43be67ac7845cf" - integrity sha512-0IDJYhgU8xDv1KY6+VgUwuQkVtmYzRwu+dMjnmdMafXYv86SWqfxkc7qdDvWS38vsjaEtv8e0vGOUQrAiMBLpQ== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-custom-selectors@^6.0.0: - version "6.0.3" - resolved "https://registry.npmmirror.com/postcss-custom-selectors/-/postcss-custom-selectors-6.0.3.tgz#1ab4684d65f30fed175520f82d223db0337239d9" - integrity sha512-fgVkmyiWDwmD3JbpCmB45SvvlCD6z9CG6Ie6Iere22W5aHea6oWa7EM2bpnv2Fj3I94L3VbtvX9KqwSi5aFzSg== - dependencies: - postcss-selector-parser "^6.0.4" - -postcss-dir-pseudo-class@^6.0.4: - version "6.0.5" - resolved "https://registry.npmmirror.com/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-6.0.5.tgz#2bf31de5de76added44e0a25ecf60ae9f7c7c26c" - integrity sha512-eqn4m70P031PF7ZQIvSgy9RSJ5uI2171O/OO/zcRNYpJbvaeKFUlar1aJ7rmgiQtbm0FSPsRewjpdS0Oew7MPA== - dependencies: - postcss-selector-parser "^6.0.10" - -postcss-double-position-gradients@^3.1.1: - version "3.1.2" - resolved "https://registry.npmmirror.com/postcss-double-position-gradients/-/postcss-double-position-gradients-3.1.2.tgz#b96318fdb477be95997e86edd29c6e3557a49b91" - integrity sha512-GX+FuE/uBR6eskOK+4vkXgT6pDkexLokPaz/AbJna9s5Kzp/yl488pKPjhy0obB475ovfT1Wv8ho7U/cHNaRgQ== - dependencies: - "@csstools/postcss-progressive-custom-properties" "^1.1.0" - postcss-value-parser "^4.2.0" - -postcss-env-function@^4.0.6: - version "4.0.6" - resolved "https://registry.npmmirror.com/postcss-env-function/-/postcss-env-function-4.0.6.tgz#7b2d24c812f540ed6eda4c81f6090416722a8e7a" - integrity sha512-kpA6FsLra+NqcFnL81TnsU+Z7orGtDTxcOhl6pwXeEq1yFPpRMkCDpHhrz8CFQDr/Wfm0jLiNQ1OsGGPjlqPwA== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-flexbugs-fixes@5.0.2: - version "5.0.2" - resolved "https://registry.npmmirror.com/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-5.0.2.tgz#2028e145313074fc9abe276cb7ca14e5401eb49d" - integrity sha512-18f9voByak7bTktR2QgDveglpn9DTbBWPUzSOe9g0N4WR/2eSt6Vrcbf0hmspvMI6YWGywz6B9f7jzpFNJJgnQ== - -postcss-focus-visible@^6.0.4: - version "6.0.4" - resolved "https://registry.npmmirror.com/postcss-focus-visible/-/postcss-focus-visible-6.0.4.tgz#50c9ea9afa0ee657fb75635fabad25e18d76bf9e" - integrity sha512-QcKuUU/dgNsstIK6HELFRT5Y3lbrMLEOwG+A4s5cA+fx3A3y/JTq3X9LaOj3OC3ALH0XqyrgQIgey/MIZ8Wczw== - dependencies: - postcss-selector-parser "^6.0.9" - -postcss-focus-within@^5.0.4: - version "5.0.4" - resolved "https://registry.npmmirror.com/postcss-focus-within/-/postcss-focus-within-5.0.4.tgz#5b1d2ec603195f3344b716c0b75f61e44e8d2e20" - integrity sha512-vvjDN++C0mu8jz4af5d52CB184ogg/sSxAFS+oUJQq2SuCe7T5U2iIsVJtsCp2d6R4j0jr5+q3rPkBVZkXD9fQ== - dependencies: - postcss-selector-parser "^6.0.9" - -postcss-font-variant@^5.0.0: - version "5.0.0" - resolved "https://registry.npmmirror.com/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz#efd59b4b7ea8bb06127f2d031bfbb7f24d32fa66" - integrity sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA== - -postcss-gap-properties@^3.0.3: - version "3.0.5" - resolved "https://registry.npmmirror.com/postcss-gap-properties/-/postcss-gap-properties-3.0.5.tgz#f7e3cddcf73ee19e94ccf7cb77773f9560aa2fff" - integrity sha512-IuE6gKSdoUNcvkGIqdtjtcMtZIFyXZhmFd5RUlg97iVEvp1BZKV5ngsAjCjrVy+14uhGBQl9tzmi1Qwq4kqVOg== - -postcss-image-set-function@^4.0.6: - version "4.0.7" - resolved "https://registry.npmmirror.com/postcss-image-set-function/-/postcss-image-set-function-4.0.7.tgz#08353bd756f1cbfb3b6e93182c7829879114481f" - integrity sha512-9T2r9rsvYzm5ndsBE8WgtrMlIT7VbtTfE7b3BQnudUqnBcBo7L758oc+o+pdj/dUV0l5wjwSdjeOH2DZtfv8qw== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-import@^15.1.0: - version "15.1.0" - resolved "https://registry.npmmirror.com/postcss-import/-/postcss-import-15.1.0.tgz#41c64ed8cc0e23735a9698b3249ffdbf704adc70" - integrity sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew== - dependencies: - postcss-value-parser "^4.0.0" - read-cache "^1.0.0" - resolve "^1.1.7" - -postcss-initial@^4.0.1: - version "4.0.1" - resolved "https://registry.npmmirror.com/postcss-initial/-/postcss-initial-4.0.1.tgz#529f735f72c5724a0fb30527df6fb7ac54d7de42" - integrity sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ== - -postcss-js@^4.0.1: - version "4.0.1" - resolved "https://registry.npmmirror.com/postcss-js/-/postcss-js-4.0.1.tgz#61598186f3703bab052f1c4f7d805f3991bee9d2" - integrity sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw== - dependencies: - camelcase-css "^2.0.1" - -postcss-lab-function@^4.2.0: - version "4.2.1" - resolved "https://registry.npmmirror.com/postcss-lab-function/-/postcss-lab-function-4.2.1.tgz#6fe4c015102ff7cd27d1bd5385582f67ebdbdc98" - integrity sha512-xuXll4isR03CrQsmxyz92LJB2xX9n+pZJ5jE9JgcnmsCammLyKdlzrBin+25dy6wIjfhJpKBAN80gsTlCgRk2w== - dependencies: - "@csstools/postcss-progressive-custom-properties" "^1.1.0" - postcss-value-parser "^4.2.0" - -postcss-load-config@^4.0.1: - version "4.0.1" - resolved "https://registry.npmmirror.com/postcss-load-config/-/postcss-load-config-4.0.1.tgz#152383f481c2758274404e4962743191d73875bd" - integrity sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA== - dependencies: - lilconfig "^2.0.5" - yaml "^2.1.1" - -postcss-logical@^5.0.4: - version "5.0.4" - resolved "https://registry.npmmirror.com/postcss-logical/-/postcss-logical-5.0.4.tgz#ec75b1ee54421acc04d5921576b7d8db6b0e6f73" - integrity sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g== - -postcss-media-minmax@^5.0.0: - version "5.0.0" - resolved "https://registry.npmmirror.com/postcss-media-minmax/-/postcss-media-minmax-5.0.0.tgz#7140bddec173e2d6d657edbd8554a55794e2a5b5" - integrity sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ== - -postcss-modules-extract-imports@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d" - integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw== - -postcss-modules-local-by-default@^4.0.0: - version "4.0.3" - resolved "https://registry.npmmirror.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz#b08eb4f083050708998ba2c6061b50c2870ca524" - integrity sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA== - dependencies: - icss-utils "^5.0.0" - postcss-selector-parser "^6.0.2" - postcss-value-parser "^4.1.0" - -postcss-modules-scope@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz#9ef3151456d3bbfa120ca44898dfca6f2fa01f06" - integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg== - dependencies: - postcss-selector-parser "^6.0.4" - -postcss-modules-values@^4.0.0: - version "4.0.0" - resolved "https://registry.npmmirror.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c" - integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ== - dependencies: - icss-utils "^5.0.0" - -postcss-nested@^6.0.1: - version "6.0.1" - resolved "https://registry.npmmirror.com/postcss-nested/-/postcss-nested-6.0.1.tgz#f83dc9846ca16d2f4fa864f16e9d9f7d0961662c" - integrity sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ== - dependencies: - postcss-selector-parser "^6.0.11" - -postcss-nesting@^10.1.4: - version "10.2.0" - resolved "https://registry.npmmirror.com/postcss-nesting/-/postcss-nesting-10.2.0.tgz#0b12ce0db8edfd2d8ae0aaf86427370b898890be" - integrity sha512-EwMkYchxiDiKUhlJGzWsD9b2zvq/r2SSubcRrgP+jujMXFzqvANLt16lJANC+5uZ6hjI7lpRmI6O8JIl+8l1KA== - dependencies: - "@csstools/selector-specificity" "^2.0.0" - postcss-selector-parser "^6.0.10" - -postcss-opacity-percentage@^1.1.2: - version "1.1.3" - resolved "https://registry.npmmirror.com/postcss-opacity-percentage/-/postcss-opacity-percentage-1.1.3.tgz#5b89b35551a556e20c5d23eb5260fbfcf5245da6" - integrity sha512-An6Ba4pHBiDtyVpSLymUUERMo2cU7s+Obz6BTrS+gxkbnSBNKSuD0AVUc+CpBMrpVPKKfoVz0WQCX+Tnst0i4A== - -postcss-overflow-shorthand@^3.0.3: - version "3.0.4" - resolved "https://registry.npmmirror.com/postcss-overflow-shorthand/-/postcss-overflow-shorthand-3.0.4.tgz#7ed6486fec44b76f0eab15aa4866cda5d55d893e" - integrity sha512-otYl/ylHK8Y9bcBnPLo3foYFLL6a6Ak+3EQBPOTR7luMYCOsiVTUk1iLvNf6tVPNGXcoL9Hoz37kpfriRIFb4A== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-page-break@^3.0.4: - version "3.0.4" - resolved "https://registry.npmmirror.com/postcss-page-break/-/postcss-page-break-3.0.4.tgz#7fbf741c233621622b68d435babfb70dd8c1ee5f" - integrity sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ== - -postcss-place@^7.0.4: - version "7.0.5" - resolved "https://registry.npmmirror.com/postcss-place/-/postcss-place-7.0.5.tgz#95dbf85fd9656a3a6e60e832b5809914236986c4" - integrity sha512-wR8igaZROA6Z4pv0d+bvVrvGY4GVHihBCBQieXFY3kuSuMyOmEnnfFzHl/tQuqHZkfkIVBEbDvYcFfHmpSet9g== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-prefix-selector@1.16.0: - version "1.16.0" - resolved "https://registry.npmmirror.com/postcss-prefix-selector/-/postcss-prefix-selector-1.16.0.tgz#ad5b56f9a73a2c090ca7161049632c9d89bcb404" - integrity sha512-rdVMIi7Q4B0XbXqNUEI+Z4E+pueiu/CS5E6vRCQommzdQ/sgsS4dK42U7GX8oJR+TJOtT+Qv3GkNo6iijUMp3Q== - -postcss-preset-env@7.5.0: - version "7.5.0" - resolved "https://registry.npmmirror.com/postcss-preset-env/-/postcss-preset-env-7.5.0.tgz#0c1f23933597d55dab4a90f61eda30b76e710658" - integrity sha512-0BJzWEfCdTtK2R3EiKKSdkE51/DI/BwnhlnicSW482Ym6/DGHud8K0wGLcdjip1epVX0HKo4c8zzTeV/SkiejQ== - dependencies: - "@csstools/postcss-color-function" "^1.1.0" - "@csstools/postcss-font-format-keywords" "^1.0.0" - "@csstools/postcss-hwb-function" "^1.0.0" - "@csstools/postcss-ic-unit" "^1.0.0" - "@csstools/postcss-is-pseudo-class" "^2.0.2" - "@csstools/postcss-normalize-display-values" "^1.0.0" - "@csstools/postcss-oklab-function" "^1.1.0" - "@csstools/postcss-progressive-custom-properties" "^1.3.0" - "@csstools/postcss-stepped-value-functions" "^1.0.0" - "@csstools/postcss-unset-value" "^1.0.0" - autoprefixer "^10.4.6" - browserslist "^4.20.3" - css-blank-pseudo "^3.0.3" - css-has-pseudo "^3.0.4" - css-prefers-color-scheme "^6.0.3" - cssdb "^6.6.1" - postcss-attribute-case-insensitive "^5.0.0" - postcss-clamp "^4.1.0" - postcss-color-functional-notation "^4.2.2" - postcss-color-hex-alpha "^8.0.3" - postcss-color-rebeccapurple "^7.0.2" - postcss-custom-media "^8.0.0" - postcss-custom-properties "^12.1.7" - postcss-custom-selectors "^6.0.0" - postcss-dir-pseudo-class "^6.0.4" - postcss-double-position-gradients "^3.1.1" - postcss-env-function "^4.0.6" - postcss-focus-visible "^6.0.4" - postcss-focus-within "^5.0.4" - postcss-font-variant "^5.0.0" - postcss-gap-properties "^3.0.3" - postcss-image-set-function "^4.0.6" - postcss-initial "^4.0.1" - postcss-lab-function "^4.2.0" - postcss-logical "^5.0.4" - postcss-media-minmax "^5.0.0" - postcss-nesting "^10.1.4" - postcss-opacity-percentage "^1.1.2" - postcss-overflow-shorthand "^3.0.3" - postcss-page-break "^3.0.4" - postcss-place "^7.0.4" - postcss-pseudo-class-any-link "^7.1.2" - postcss-replace-overflow-wrap "^4.0.0" - postcss-selector-not "^5.0.0" - postcss-value-parser "^4.2.0" - -postcss-pseudo-class-any-link@^7.1.2: - version "7.1.6" - resolved "https://registry.npmmirror.com/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-7.1.6.tgz#2693b221902da772c278def85a4d9a64b6e617ab" - integrity sha512-9sCtZkO6f/5ML9WcTLcIyV1yz9D1rf0tWc+ulKcvV30s0iZKS/ONyETvoWsr6vnrmW+X+KmuK3gV/w5EWnT37w== - dependencies: - postcss-selector-parser "^6.0.10" - -postcss-replace-overflow-wrap@^4.0.0: - version "4.0.0" - resolved "https://registry.npmmirror.com/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz#d2df6bed10b477bf9c52fab28c568b4b29ca4319" - integrity sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw== - -postcss-selector-not@^5.0.0: - version "5.0.0" - resolved "https://registry.npmmirror.com/postcss-selector-not/-/postcss-selector-not-5.0.0.tgz#ac5fc506f7565dd872f82f5314c0f81a05630dc7" - integrity sha512-/2K3A4TCP9orP4TNS7u3tGdRFVKqz/E6pX3aGnriPG0jU78of8wsUcqE4QAhWEU0d+WnMSF93Ah3F//vUtK+iQ== - dependencies: - balanced-match "^1.0.0" - -postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.9: - version "6.0.13" - resolved "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz#d05d8d76b1e8e173257ef9d60b706a8e5e99bf1b" - integrity sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ== - dependencies: - cssesc "^3.0.0" - util-deprecate "^1.0.2" - -postcss-syntax@0.36.2: - version "0.36.2" - resolved "https://registry.npmmirror.com/postcss-syntax/-/postcss-syntax-0.36.2.tgz#f08578c7d95834574e5593a82dfbfa8afae3b51c" - integrity sha512-nBRg/i7E3SOHWxF3PpF5WnJM/jQ1YpY9000OaVXlAQj6Zp/kIqJxEDWIZ67tAd7NLuk7zqN4yqe9nc0oNAOs1w== - -postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: - version "4.2.0" - resolved "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" - integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== - -postcss@^8.4.21, postcss@^8.4.23, postcss@^8.4.7: - version "8.4.26" - resolved "https://registry.npmmirror.com/postcss/-/postcss-8.4.26.tgz#1bc62ab19f8e1e5463d98cf74af39702a00a9e94" - integrity sha512-jrXHFF8iTloAenySjM/ob3gSj7pCu0Ji49hnjqzsgSRa50hkWCKD0HQ+gMNJkW38jBI68MpAAg7ZWwHwX8NMMw== - dependencies: - nanoid "^3.3.6" - picocolors "^1.0.0" - source-map-js "^1.0.2" - -prelude-ls@^1.2.1: - version "1.2.1" - resolved "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" - integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== - -prettier-linter-helpers@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" - integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== - dependencies: - fast-diff "^1.1.2" - -prettier-plugin-organize-imports@^2: - version "2.3.4" - resolved "https://registry.npmmirror.com/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-2.3.4.tgz#65473861ae5ab7960439fff270a2258558fbe9ba" - integrity sha512-R8o23sf5iVL/U71h9SFUdhdOEPsi3nm42FD/oDYIZ2PQa4TNWWuWecxln6jlIQzpZTDMUeO1NicJP6lLn2TtRw== - -prettier-plugin-organize-imports@^3.2.2: - version "3.2.3" - resolved "https://registry.npmmirror.com/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-3.2.3.tgz#6b0141ac71f7ee9a673ce83e95456319e3a7cf0d" - integrity sha512-KFvk8C/zGyvUaE3RvxN2MhCLwzV6OBbFSkwZ2OamCrs9ZY4i5L77jQ/w4UmUr+lqX8qbaqVq6bZZkApn+IgJSg== - -prettier-plugin-packagejson@2.4.3: - version "2.4.3" - resolved "https://registry.npmmirror.com/prettier-plugin-packagejson/-/prettier-plugin-packagejson-2.4.3.tgz#77f50538cc47c86d4fa510bc312a548e346fb724" - integrity sha512-kPeeviJiwy0BgOSk7No8NmzzXfW4R9FYWni6ziA5zc1kGVVrKnBzMZdu2TUhI+I7h8/5Htt3vARYOk7KKJTTNQ== - dependencies: - sort-package-json "2.4.1" - synckit "0.8.5" - -prettier-plugin-packagejson@^2: - version "2.4.5" - resolved "https://registry.npmmirror.com/prettier-plugin-packagejson/-/prettier-plugin-packagejson-2.4.5.tgz#20cc396e5654b5736657bd2dfb7ac859afc618cc" - integrity sha512-glG71jE1gO3y5+JNAhC8X+4yrlN28rub6Aj461SKbaPie9RgMiHKcInH2Moi2VGOfkTXaEHBhg4uVMBqa+kBUA== - dependencies: - sort-package-json "2.5.1" - synckit "0.8.5" - -prettier@^2: - version "2.8.8" - resolved "https://registry.npmmirror.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" - integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== - -pretty-error@^4.0.0: - version "4.0.0" - resolved "https://registry.npmmirror.com/pretty-error/-/pretty-error-4.0.0.tgz#90a703f46dd7234adb46d0f84823e9d1cb8f10d6" - integrity sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw== - dependencies: - lodash "^4.17.20" - renderkid "^3.0.0" - -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== - -process-warning@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/process-warning/-/process-warning-1.0.0.tgz#980a0b25dc38cd6034181be4b7726d89066b4616" - integrity sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q== - -process@^0.11.10: - version "0.11.10" - resolved "https://registry.npmmirror.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" - integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== - -progress@^2.0.3: - version "2.0.3" - resolved "https://registry.npmmirror.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" - integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== - -prop-types@^15.5.10, prop-types@^15.7.2, prop-types@^15.8.1: - version "15.8.1" - resolved "https://registry.npmmirror.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" - integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== - dependencies: - loose-envify "^1.4.0" - object-assign "^4.1.1" - react-is "^16.13.1" - -proxy-compare@2.4.0: - version "2.4.0" - resolved "https://registry.npmmirror.com/proxy-compare/-/proxy-compare-2.4.0.tgz#90f6abffe734ef86d8e37428c5026268606a9c1b" - integrity sha512-FD8KmQUQD6Mfpd0hywCOzcon/dbkFP8XBd9F1ycbKtvVsfv6TsFUKJ2eC0Iz2y+KzlkdT1Z8SY6ZSgm07zOyqg== - -prr@~1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" - integrity sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw== - -public-encrypt@^4.0.0: - version "4.0.3" - resolved "https://registry.npmmirror.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" - integrity sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q== - dependencies: - bn.js "^4.1.0" - browserify-rsa "^4.0.0" - create-hash "^1.1.0" - parse-asn1 "^5.0.0" - randombytes "^2.0.1" - safe-buffer "^5.1.2" - -pump@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" - integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -punycode@^1.2.4, punycode@^1.4.1: - version "1.4.1" - resolved "https://registry.npmmirror.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" - integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ== - -punycode@^2.1.0: - version "2.3.0" - resolved "https://registry.npmmirror.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" - integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== - -qiankun@^2.10.1: - version "2.10.11" - resolved "https://registry.npmmirror.com/qiankun/-/qiankun-2.10.11.tgz#8876e10b8ed49102a40d547d30b0325d7ddd0551" - integrity sha512-lKXZ3TFgKpeTC3kQQrZZUm5jb2d+MkHbPGW05rdVel72FwagZVlcXIrCNkn1lY7Ng8cnGJUsqYIX4exKf5PLuA== - dependencies: - "@babel/runtime" "^7.10.5" - import-html-entry "^1.14.5" - lodash "^4.17.11" - single-spa "^5.9.2" - -qrcode.react@^3.1.0: - version "3.1.0" - resolved "https://registry.npmmirror.com/qrcode.react/-/qrcode.react-3.1.0.tgz#5c91ddc0340f768316fbdb8fff2765134c2aecd8" - integrity sha512-oyF+Urr3oAMUG/OiOuONL3HXM+53wvuH3mtIWQrYmsXoAq0DkvZp2RYUWFSMFtbdOpuS++9v+WAkzNVkMlNW6Q== - -qs@^6.11.0, qs@^6.9.1: - version "6.11.2" - resolved "https://registry.npmmirror.com/qs/-/qs-6.11.2.tgz#64bea51f12c1f5da1bc01496f48ffcff7c69d7d9" - integrity sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA== - dependencies: - side-channel "^1.0.4" - -query-string@^6.13.6: - version "6.14.1" - resolved "https://registry.npmmirror.com/query-string/-/query-string-6.14.1.tgz#7ac2dca46da7f309449ba0f86b1fd28255b0c86a" - integrity sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw== - dependencies: - decode-uri-component "^0.2.0" - filter-obj "^1.1.0" - split-on-first "^1.0.0" - strict-uri-encode "^2.0.0" - -querystring-es3@^0.2.0: - version "0.2.1" - resolved "https://registry.npmmirror.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" - integrity sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA== - -queue-microtask@^1.2.2: - version "1.2.3" - resolved "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" - integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== - -quick-format-unescaped@^4.0.3: - version "4.0.4" - resolved "https://registry.npmmirror.com/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz#93ef6dd8d3453cbc7970dd614fad4c5954d6b5a7" - integrity sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg== - -quick-lru@^5.1.1: - version "5.1.1" - resolved "https://registry.npmmirror.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" - integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== - -railroad-diagrams@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz#eb7e6267548ddedfb899c1b90e57374559cddb7e" - integrity sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A== - -randexp@0.4.6: - version "0.4.6" - resolved "https://registry.npmmirror.com/randexp/-/randexp-0.4.6.tgz#e986ad5e5e31dae13ddd6f7b3019aa7c87f60ca3" - integrity sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ== - dependencies: - discontinuous-range "1.0.0" - ret "~0.1.10" - -randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: - version "2.1.0" - resolved "https://registry.npmmirror.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" - integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== - dependencies: - safe-buffer "^5.1.0" - -randomfill@^1.0.3: - version "1.0.4" - resolved "https://registry.npmmirror.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" - integrity sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw== - dependencies: - randombytes "^2.0.5" - safe-buffer "^5.1.0" - -rc-align@^4.0.0: - version "4.0.15" - resolved "https://registry.npmmirror.com/rc-align/-/rc-align-4.0.15.tgz#2bbd665cf85dfd0b0244c5a752b07565e9098577" - integrity sha512-wqJtVH60pka/nOX7/IspElA8gjPNQKIx/ZqJ6heATCkXpe1Zg4cPVrMD2vC96wjsFFL8WsmhPbx9tdMo1qqlIA== - dependencies: - "@babel/runtime" "^7.10.1" - classnames "2.x" - dom-align "^1.7.0" - rc-util "^5.26.0" - resize-observer-polyfill "^1.5.1" - -rc-cascader@~3.20.0: - version "3.20.0" - resolved "https://registry.npmmirror.com/rc-cascader/-/rc-cascader-3.20.0.tgz#b270f9d84ed83417ee7309ef5e56e415f1586076" - integrity sha512-lkT9EEwOcYdjZ/jvhLoXGzprK1sijT3/Tp4BLxQQcHDZkkOzzwYQC9HgmKoJz0K7CukMfgvO9KqHeBdgE+pELw== - dependencies: - "@babel/runtime" "^7.12.5" - array-tree-filter "^2.1.0" - classnames "^2.3.1" - rc-select "~14.10.0" - rc-tree "~5.8.1" - rc-util "^5.37.0" - -rc-checkbox@~3.1.0: - version "3.1.0" - resolved "https://registry.npmmirror.com/rc-checkbox/-/rc-checkbox-3.1.0.tgz#6be0d9d8de2cc96fb5e37f9036a1c3e360d0a42d" - integrity sha512-PAwpJFnBa3Ei+5pyqMMXdcKYKNBMS+TvSDiLdDnARnMJHC8ESxwPfm4Ao1gJiKtWLdmGfigascnCpwrHFgoOBQ== - dependencies: - "@babel/runtime" "^7.10.1" - classnames "^2.3.2" - rc-util "^5.25.2" - -rc-collapse@~3.7.2: - version "3.7.2" - resolved "https://registry.npmmirror.com/rc-collapse/-/rc-collapse-3.7.2.tgz#d11538ff9c705a5c988d9a4dfcc051a919692fe3" - integrity sha512-ZRw6ipDyOnfLFySxAiCMdbHtb5ePAsB9mT17PA6y1mRD/W6KHRaZeb5qK/X9xDV1CqgyxMpzw0VdS74PCcUk4A== - dependencies: - "@babel/runtime" "^7.10.1" - classnames "2.x" - rc-motion "^2.3.4" - rc-util "^5.27.0" - -rc-dialog@~9.3.4: - version "9.3.4" - resolved "https://registry.npmmirror.com/rc-dialog/-/rc-dialog-9.3.4.tgz#e0decb3d4a0dbe36524a67ed2f8fe2daa4b7b73c" - integrity sha512-975X3018GhR+EjZFbxA2Z57SX5rnu0G0/OxFgMMvZK4/hQWEm3MHaNvP4wXpxYDoJsp+xUvVW+GB9CMMCm81jA== - 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" - -rc-drawer@~6.5.2: - version "6.5.2" - resolved "https://registry.npmmirror.com/rc-drawer/-/rc-drawer-6.5.2.tgz#49c1f279261992f6d4653d32a03b14acd436d610" - integrity sha512-QckxAnQNdhh4vtmKN0ZwDf3iakO83W9eZcSKWYYTDv4qcD2fHhRAZJJ/OE6v2ZlQ2kSqCJX5gYssF4HJFvsEPQ== - dependencies: - "@babel/runtime" "^7.10.1" - "@rc-component/portal" "^1.1.1" - classnames "^2.2.6" - rc-motion "^2.6.1" - rc-util "^5.36.0" - -rc-dropdown@~4.1.0: - version "4.1.0" - resolved "https://registry.npmmirror.com/rc-dropdown/-/rc-dropdown-4.1.0.tgz#418a68939631520de80d0865d02b440eeeb4168e" - integrity sha512-VZjMunpBdlVzYpEdJSaV7WM7O0jf8uyDjirxXLZRNZ+tAC+NzD3PXPEtliFwGzVwBBdCmGuSqiS9DWcOLxQ9tw== - dependencies: - "@babel/runtime" "^7.18.3" - "@rc-component/trigger" "^1.7.0" - classnames "^2.2.6" - rc-util "^5.17.0" - -rc-field-form@~1.41.0: - version "1.41.0" - resolved "https://registry.npmmirror.com/rc-field-form/-/rc-field-form-1.41.0.tgz#660ed8691fdabbc1e5b1ee6b5e0e4f534b295cf0" - integrity sha512-k9AS0wmxfJfusWDP/YXWTpteDNaQ4isJx9UKxx4/e8Dub4spFeZ54/EuN2sYrMRID/+hUznPgVZeg+Gf7XSYCw== - dependencies: - "@babel/runtime" "^7.18.0" - async-validator "^4.1.0" - rc-util "^5.32.2" - -rc-image@~7.5.1: - version "7.5.1" - resolved "https://registry.npmmirror.com/rc-image/-/rc-image-7.5.1.tgz#39a93354e14fe3e5eaafd9c9464e8fe3c6c171a0" - integrity sha512-Z9loECh92SQp0nSipc0MBuf5+yVC05H/pzC+Nf8xw1BKDFUJzUeehYBjaWlxly8VGBZJcTHYri61Fz9ng1G3Ag== - dependencies: - "@babel/runtime" "^7.11.2" - "@rc-component/portal" "^1.0.2" - classnames "^2.2.6" - rc-dialog "~9.3.4" - rc-motion "^2.6.2" - rc-util "^5.34.1" - -rc-input-number@~8.4.0: - version "8.4.0" - resolved "https://registry.npmmirror.com/rc-input-number/-/rc-input-number-8.4.0.tgz#f0d0caa2ce3a4e37f062556f9cb4c08c8c23322d" - integrity sha512-B6rziPOLRmeP7kcS5qbdC5hXvvDHYKV4vUxmahevYx2E6crS2bRi0xLDjhJ0E1HtOWo8rTmaE2EBJAkTCZOLdA== - dependencies: - "@babel/runtime" "^7.10.1" - "@rc-component/mini-decimal" "^1.0.1" - classnames "^2.2.5" - rc-input "~1.3.5" - rc-util "^5.28.0" - -rc-input@~1.3.5, rc-input@~1.3.6: - version "1.3.6" - resolved "https://registry.npmmirror.com/rc-input/-/rc-input-1.3.6.tgz#038b74779b6c8b688ff60a41c3976d1db7a1d7d6" - integrity sha512-/HjTaKi8/Ts4zNbYaB5oWCquxFyFQO4Co1MnMgoCeGJlpe7k8Eir2HN0a0F9IHDmmo+GYiGgPpz7w/d/krzsJA== - dependencies: - "@babel/runtime" "^7.11.1" - classnames "^2.2.1" - rc-util "^5.18.1" - -rc-mentions@~2.9.1: - version "2.9.1" - resolved "https://registry.npmmirror.com/rc-mentions/-/rc-mentions-2.9.1.tgz#cfe55913fd5bc156ef9814f38c1a2ceefee032ce" - integrity sha512-cZuElWr/5Ws0PXx1uxobxfYh4mqUw2FitfabR62YnWgm+WAfDyXZXqZg5DxXW+M1cgVvntrQgDDd9LrihrXzew== - dependencies: - "@babel/runtime" "^7.22.5" - "@rc-component/trigger" "^1.5.0" - classnames "^2.2.6" - rc-input "~1.3.5" - rc-menu "~9.12.0" - rc-textarea "~1.5.0" - rc-util "^5.34.1" - -rc-menu@~9.12.0, rc-menu@~9.12.2: - version "9.12.4" - resolved "https://registry.npmmirror.com/rc-menu/-/rc-menu-9.12.4.tgz#4959b5eeb780be7ff52aac31952b35efca46b9a3" - integrity sha512-t2NcvPLV1mFJzw4F21ojOoRVofK2rWhpKPx69q2raUsiHPDP6DDevsBILEYdsIegqBeSXoWs2bf6CueBKg3BFg== - dependencies: - "@babel/runtime" "^7.10.1" - "@rc-component/trigger" "^1.17.0" - classnames "2.x" - rc-motion "^2.4.3" - rc-overflow "^1.3.1" - rc-util "^5.27.0" - -rc-motion@^2.0.0, rc-motion@^2.0.1, rc-motion@^2.3.0, rc-motion@^2.3.4, rc-motion@^2.4.3, rc-motion@^2.4.4, rc-motion@^2.6.1, rc-motion@^2.6.2: - version "2.7.3" - resolved "https://registry.npmmirror.com/rc-motion/-/rc-motion-2.7.3.tgz#126155bb3e687174fb3b92fddade2835c963b04d" - integrity sha512-2xUvo8yGHdOHeQbdI8BtBsCIrWKchEmFEIskf0nmHtJsou+meLd/JE+vnvSX2JxcBrJtXY2LuBpxAOxrbY/wMQ== - dependencies: - "@babel/runtime" "^7.11.1" - classnames "^2.2.1" - rc-util "^5.21.0" - -rc-motion@^2.9.0: - version "2.9.0" - resolved "https://registry.npmmirror.com/rc-motion/-/rc-motion-2.9.0.tgz#9e18a1b8d61e528a97369cf9a7601e9b29205710" - integrity sha512-XIU2+xLkdIr1/h6ohPZXyPBMvOmuyFZQ/T0xnawz+Rh+gh4FINcnZmMT5UTIj6hgI0VLDjTaPeRd+smJeSPqiQ== - dependencies: - "@babel/runtime" "^7.11.1" - classnames "^2.2.1" - rc-util "^5.21.0" - -rc-notification@~5.3.0: - version "5.3.0" - resolved "https://registry.npmmirror.com/rc-notification/-/rc-notification-5.3.0.tgz#e31c86fe2350598ade8cff383babd1befa7a94fe" - integrity sha512-WCf0uCOkZ3HGfF0p1H4Sgt7aWfipxORWTPp7o6prA3vxwtWhtug3GfpYls1pnBp4WA+j8vGIi5c2/hQRpGzPcQ== - dependencies: - "@babel/runtime" "^7.10.1" - classnames "2.x" - rc-motion "^2.9.0" - rc-util "^5.20.1" - -rc-overflow@^1.3.1: - version "1.3.1" - resolved "https://registry.npmmirror.com/rc-overflow/-/rc-overflow-1.3.1.tgz#03224cf90c66aa570eb0deeb4eff6cc96401e979" - integrity sha512-RY0nVBlfP9CkxrpgaLlGzkSoh9JhjJLu6Icqs9E7CW6Ewh9s0peF9OHIex4OhfoPsR92LR0fN6BlCY9Z4VoUtA== - dependencies: - "@babel/runtime" "^7.11.1" - classnames "^2.2.1" - rc-resize-observer "^1.0.0" - rc-util "^5.19.2" - -rc-pagination@~4.0.1: - version "4.0.1" - resolved "https://registry.npmmirror.com/rc-pagination/-/rc-pagination-4.0.1.tgz#bf1bfdace69c43efef82354d963031622652ba4e" - integrity sha512-udrYHGTVXBm5HxE+RYeu9P9o+M7aZSFMwGd2OvYupvSI/wt1jzn2arHb30/nwpJ7tV876BkvJQBvctMH4fDmLw== - dependencies: - "@babel/runtime" "^7.10.1" - classnames "^2.3.2" - rc-util "^5.38.0" - -rc-picker@~3.14.6: - version "3.14.6" - resolved "https://registry.npmmirror.com/rc-picker/-/rc-picker-3.14.6.tgz#60fc34f9883272e10f6c593fa6d82e7e7a70781b" - integrity sha512-AdKKW0AqMwZsKvIpwUWDUnpuGKZVrbxVTZTNjcO+pViGkjC1EBcjMgxVe8tomOEaIHJL5Gd13vS8Rr3zzxWmag== - dependencies: - "@babel/runtime" "^7.10.1" - "@rc-component/trigger" "^1.5.0" - classnames "^2.2.1" - rc-util "^5.30.0" - -rc-progress@~3.5.1: - version "3.5.1" - resolved "https://registry.npmmirror.com/rc-progress/-/rc-progress-3.5.1.tgz#a3cdfd2fe04eb5c3d43fa1c69e7dd70c73b102ae" - integrity sha512-V6Amx6SbLRwPin/oD+k1vbPrO8+9Qf8zW1T8A7o83HdNafEVvAxPV5YsgtKFP+Ud5HghLj33zKOcEHrcrUGkfw== - dependencies: - "@babel/runtime" "^7.10.1" - classnames "^2.2.6" - rc-util "^5.16.1" - -rc-rate@~2.12.0: - version "2.12.0" - resolved "https://registry.npmmirror.com/rc-rate/-/rc-rate-2.12.0.tgz#0182deffed3b009cdcc61660da8746c39ed91ed5" - integrity sha512-g092v5iZCdVzbjdn28FzvWebK2IutoVoiTeqoLTj9WM7SjA/gOJIw5/JFZMRyJYYVe1jLAU2UhAfstIpCNRozg== - dependencies: - "@babel/runtime" "^7.10.1" - classnames "^2.2.5" - rc-util "^5.0.1" - -rc-resize-observer@^0.2.3: - version "0.2.6" - resolved "https://registry.npmmirror.com/rc-resize-observer/-/rc-resize-observer-0.2.6.tgz#c1b642f6d1293e34c4e3715f47f69443a167b825" - integrity sha512-YX6nYnd6fk7zbuvT6oSDMKiZjyngjHoy+fz+vL3Tez38d/G5iGdaDJa2yE7345G6sc4Mm1IGRUIwclvltddhmA== - dependencies: - "@babel/runtime" "^7.10.1" - classnames "^2.2.1" - rc-util "^5.0.0" - resize-observer-polyfill "^1.5.1" - -rc-resize-observer@^1.0.0, rc-resize-observer@^1.1.0, rc-resize-observer@^1.3.1: - version "1.3.1" - resolved "https://registry.npmmirror.com/rc-resize-observer/-/rc-resize-observer-1.3.1.tgz#b61b9f27048001243617b81f95e53d7d7d7a6a3d" - integrity sha512-iFUdt3NNhflbY3mwySv5CA1TC06zdJ+pfo0oc27xpf4PIOvfZwZGtD9Kz41wGYqC4SLio93RVAirSSpYlV/uYg== - dependencies: - "@babel/runtime" "^7.20.7" - classnames "^2.2.1" - rc-util "^5.27.0" - resize-observer-polyfill "^1.5.1" - -rc-resize-observer@^1.4.0: - version "1.4.0" - resolved "https://registry.npmmirror.com/rc-resize-observer/-/rc-resize-observer-1.4.0.tgz#7bba61e6b3c604834980647cce6451914750d0cc" - integrity sha512-PnMVyRid9JLxFavTjeDXEXo65HCRqbmLBw9xX9gfC4BZiSzbLXKzW3jPz+J0P71pLbD5tBMTT+mkstV5gD0c9Q== - dependencies: - "@babel/runtime" "^7.20.7" - classnames "^2.2.1" - rc-util "^5.38.0" - resize-observer-polyfill "^1.5.1" - -rc-segmented@~2.2.2: - version "2.2.2" - resolved "https://registry.npmmirror.com/rc-segmented/-/rc-segmented-2.2.2.tgz#a34f12ce6c0975fc3042ae7656bcd18e1744798e" - integrity sha512-Mq52M96QdHMsNdE/042ibT5vkcGcD5jxKp7HgPC2SRofpia99P5fkfHy1pEaajLMF/kj0+2Lkq1UZRvqzo9mSA== - dependencies: - "@babel/runtime" "^7.11.1" - classnames "^2.2.1" - rc-motion "^2.4.4" - rc-util "^5.17.0" - -rc-select@~14.10.0: - version "14.10.0" - resolved "https://registry.npmmirror.com/rc-select/-/rc-select-14.10.0.tgz#5f60e61ed7c9a83c8591616b1174a1c4ab2de0cd" - integrity sha512-TsIJTYafTTapCA32LLNpx/AD6ntepR1TG8jEVx35NiAAWCPymhUfuca8kRcUNd3WIGVMDcMKn9kkphoxEz+6Ag== - dependencies: - "@babel/runtime" "^7.10.1" - "@rc-component/trigger" "^1.5.0" - classnames "2.x" - rc-motion "^2.0.1" - rc-overflow "^1.3.1" - rc-util "^5.16.1" - rc-virtual-list "^3.5.2" - -rc-slider@~10.5.0: - version "10.5.0" - resolved "https://registry.npmmirror.com/rc-slider/-/rc-slider-10.5.0.tgz#1bd4853d114cb3403b67c485125887adb6a2a117" - integrity sha512-xiYght50cvoODZYI43v3Ylsqiw14+D7ELsgzR40boDZaya1HFa1Etnv9MDkQE8X/UrXAffwv2AcNAhslgYuDTw== - dependencies: - "@babel/runtime" "^7.10.1" - classnames "^2.2.5" - rc-util "^5.27.0" - -rc-steps@~6.0.1: - version "6.0.1" - resolved "https://registry.npmmirror.com/rc-steps/-/rc-steps-6.0.1.tgz#c2136cd0087733f6d509209a84a5c80dc29a274d" - integrity sha512-lKHL+Sny0SeHkQKKDJlAjV5oZ8DwCdS2hFhAkIjuQt1/pB81M0cA0ErVFdHq9+jmPmFw1vJB2F5NBzFXLJxV+g== - dependencies: - "@babel/runtime" "^7.16.7" - classnames "^2.2.3" - rc-util "^5.16.1" - -rc-switch@~4.1.0: - version "4.1.0" - resolved "https://registry.npmmirror.com/rc-switch/-/rc-switch-4.1.0.tgz#f37d81b4e0c5afd1274fd85367b17306bf25e7d7" - integrity sha512-TI8ufP2Az9oEbvyCeVE4+90PDSljGyuwix3fV58p7HV2o4wBnVToEyomJRVyTaZeqNPAp+vqeo4Wnj5u0ZZQBg== - dependencies: - "@babel/runtime" "^7.21.0" - classnames "^2.2.1" - rc-util "^5.30.0" - -rc-table@~7.36.0: - version "7.36.0" - resolved "https://registry.npmmirror.com/rc-table/-/rc-table-7.36.0.tgz#95e50805392b6a723105c3eb77eefb1e14ba1ced" - integrity sha512-3xVcdCC5OLeOOhaCg+5Lps2oPreM/GWXmUXWTSX4p6vF7F76ABM4dfPpMJ9Dnf5yGRyh+8pe7FRyhRVnWw2H/w== - 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.37.0" - rc-virtual-list "^3.11.1" - -rc-tabs@~12.14.1: - version "12.14.1" - resolved "https://registry.npmmirror.com/rc-tabs/-/rc-tabs-12.14.1.tgz#1fe4c0bd54550c216f9612b76eff7fbe750f4d2b" - integrity sha512-1xlE7JQNYxD5RwBsM7jf2xSdUrkmTSDFLFEm2gqAgnsRlOGydEzXXNAVTOT6QcgM1G/gCm+AgG+FYPUGb4Hs4g== - dependencies: - "@babel/runtime" "^7.11.2" - classnames "2.x" - rc-dropdown "~4.1.0" - rc-menu "~9.12.0" - rc-motion "^2.6.2" - rc-resize-observer "^1.0.0" - rc-util "^5.34.1" - -rc-textarea@~1.5.0, rc-textarea@~1.5.3: - version "1.5.3" - resolved "https://registry.npmmirror.com/rc-textarea/-/rc-textarea-1.5.3.tgz#513e837d308584996c05f540f4f58645a3a8c89a" - integrity sha512-oH682ghHx++stFNYrosPRBfwsypywrTXpaD0/5Z8MPkUOnyOQUaY9ueL9tMu6BP1LfsuYQ1VLpg5OtshViLNgA== - dependencies: - "@babel/runtime" "^7.10.1" - classnames "^2.2.1" - rc-input "~1.3.5" - rc-resize-observer "^1.0.0" - rc-util "^5.27.0" - -rc-tooltip@~6.1.2: - version "6.1.2" - resolved "https://registry.npmmirror.com/rc-tooltip/-/rc-tooltip-6.1.2.tgz#33923ecfb2cf24347975093cbd0b048ab33c9567" - integrity sha512-89zwvybvCxGJu3+gGF8w5AXd4HHk6hIN7K0vZbkzjilVaEAIWPqc1fcyeUeP71n3VCcw7pTL9LyFupFbrx8gHw== - dependencies: - "@babel/runtime" "^7.11.2" - "@rc-component/trigger" "^1.18.0" - classnames "^2.3.1" - -rc-tree-select@~5.15.0: - version "5.15.0" - resolved "https://registry.npmmirror.com/rc-tree-select/-/rc-tree-select-5.15.0.tgz#8591f1dd28b043dde6fa1ca30c7acb198b160a42" - integrity sha512-YJHfdO6azFnR0/JuNBZLDptGE4/RGfVeHAafUIYcm2T3RBkL1O8aVqiHvwIyLzdK59ry0NLrByd+3TkfpRM+9Q== - dependencies: - "@babel/runtime" "^7.10.1" - classnames "2.x" - rc-select "~14.10.0" - rc-tree "~5.8.1" - rc-util "^5.16.1" - -rc-tree@~5.8.1, rc-tree@~5.8.2: - version "5.8.2" - resolved "https://registry.npmmirror.com/rc-tree/-/rc-tree-5.8.2.tgz#ed3a3f7c56597bbeab3303407a9e1739bbf15621" - integrity sha512-xH/fcgLHWTLmrSuNphU8XAqV7CdaOQgm4KywlLGNoTMhDAcNR3GVNP6cZzb0GrKmIZ9yae+QLot/cAgUdPRMzg== - 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" - -rc-upload@~4.3.5: - version "4.3.5" - resolved "https://registry.npmmirror.com/rc-upload/-/rc-upload-4.3.5.tgz#12fc69b2af74d08646a104828831bcaf44076eda" - integrity sha512-EHlKJbhkgFSQHliTj9v/2K5aEuFwfUQgZARzD7AmAPOneZEPiCNF3n6PEWIuqz9h7oq6FuXgdR67sC5BWFxJbA== - dependencies: - "@babel/runtime" "^7.18.3" - classnames "^2.2.5" - rc-util "^5.2.0" - -rc-util@^4.19.0: - version "4.21.1" - resolved "https://registry.npmmirror.com/rc-util/-/rc-util-4.21.1.tgz#88602d0c3185020aa1053d9a1e70eac161becb05" - integrity sha512-Z+vlkSQVc1l8O2UjR3WQ+XdWlhj5q9BMQNLk2iOBch75CqPfrJyGtcWMcnhRlNuDu0Ndtt4kLVO8JI8BrABobg== - dependencies: - add-dom-event-listener "^1.1.0" - prop-types "^15.5.10" - react-is "^16.12.0" - react-lifecycles-compat "^3.0.4" - shallowequal "^1.1.0" - -rc-util@^5.0.0, rc-util@^5.0.1, rc-util@^5.0.6, rc-util@^5.15.0, rc-util@^5.16.1, rc-util@^5.17.0, rc-util@^5.18.1, rc-util@^5.19.2, rc-util@^5.2.0, rc-util@^5.20.1, rc-util@^5.21.0, rc-util@^5.24.4, rc-util@^5.25.2, rc-util@^5.26.0, rc-util@^5.27.0, rc-util@^5.28.0, rc-util@^5.30.0, rc-util@^5.31.1, rc-util@^5.32.2, rc-util@^5.33.0, rc-util@^5.34.1, rc-util@^5.4.0, rc-util@^5.9.4: - version "5.34.1" - resolved "https://registry.npmmirror.com/rc-util/-/rc-util-5.34.1.tgz#0becf411d8f09bdb0f1b61322964f27efeeba642" - integrity sha512-SqiUT8Ssgh5C+hu4y887xwCrMNcxLm6ScOo8AFlWYYF3z9uNNiPpwwSjvicqOlWd79rNw1g44rnP7tz9MrO1ZQ== - dependencies: - "@babel/runtime" "^7.18.3" - react-is "^16.12.0" - -rc-util@^5.35.0, rc-util@^5.36.0, rc-util@^5.37.0, rc-util@^5.38.0, rc-util@^5.38.1: - version "5.38.1" - resolved "https://registry.npmmirror.com/rc-util/-/rc-util-5.38.1.tgz#4915503b89855f5c5cd9afd4c72a7a17568777bb" - integrity sha512-e4ZMs7q9XqwTuhIK7zBIVFltUtMSjphuPPQXHoHlzRzNdOwUxDejo0Zls5HYaJfRKNURcsS/ceKVULlhjBrxng== - dependencies: - "@babel/runtime" "^7.18.3" - react-is "^18.2.0" - -rc-virtual-list@^3.11.1: - version "3.11.3" - resolved "https://registry.npmmirror.com/rc-virtual-list/-/rc-virtual-list-3.11.3.tgz#77d4e12e20c1ba314b43c0e37e118296674c5401" - integrity sha512-tu5UtrMk/AXonHwHxUogdXAWynaXsrx1i6dsgg+lOo/KJSF8oBAcprh1z5J3xgnPJD5hXxTL58F8s8onokdt0Q== - dependencies: - "@babel/runtime" "^7.20.0" - classnames "^2.2.6" - rc-resize-observer "^1.0.0" - rc-util "^5.36.0" - -rc-virtual-list@^3.5.1, rc-virtual-list@^3.5.2: - version "3.5.3" - resolved "https://registry.npmmirror.com/rc-virtual-list/-/rc-virtual-list-3.5.3.tgz#84f82d3257f6c520106a6285558dfc764c41c076" - integrity sha512-rG6IuD4EYM8K6oZ8Shu2BC/CmcTdqng4yBWkc/5fjWhB20bl6QwR2Upyt7+MxvfscoVm8zOQY+tcpEO5cu4GaQ== - dependencies: - "@babel/runtime" "^7.20.0" - classnames "^2.2.6" - rc-resize-observer "^1.0.0" - rc-util "^5.15.0" - -react-dom@18.1.0: - version "18.1.0" - resolved "https://registry.npmmirror.com/react-dom/-/react-dom-18.1.0.tgz#7f6dd84b706408adde05e1df575b3a024d7e8a2f" - integrity sha512-fU1Txz7Budmvamp7bshe4Zi32d0ll7ect+ccxNu9FlObT605GOEB8BfO4tmRJ39R5Zj831VCpvQ05QPBW5yb+w== - dependencies: - loose-envify "^1.1.0" - scheduler "^0.22.0" - -react-error-overlay@6.0.9: - version "6.0.9" - resolved "https://registry.npmmirror.com/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a" - integrity sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew== - -react-fast-compare@^3.2.0: - version "3.2.2" - resolved "https://registry.npmmirror.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49" - integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ== - -react-helmet-async@1.3.0: - version "1.3.0" - resolved "https://registry.npmmirror.com/react-helmet-async/-/react-helmet-async-1.3.0.tgz#7bd5bf8c5c69ea9f02f6083f14ce33ef545c222e" - integrity sha512-9jZ57/dAn9t3q6hneQS0wukqC2ENOBgMNVEhb/ZG9ZSxUetzVIw4iAmEU38IaVg3QGYauQPhSeUTuIUtFglWpg== - dependencies: - "@babel/runtime" "^7.12.5" - invariant "^2.2.4" - prop-types "^15.7.2" - react-fast-compare "^3.2.0" - shallowequal "^1.1.0" - -react-intl@3.12.1: - version "3.12.1" - resolved "https://registry.npmmirror.com/react-intl/-/react-intl-3.12.1.tgz#e9a783ea20302e9da25e4eda59e5593a43d2ec80" - integrity sha512-cgumW29mwROIqyp8NXStYsoIm27+8FqnxykiLSawWjOxGIBeLuN/+p2srei5SRIumcJefOkOIHP+NDck05RgHg== - dependencies: - "@formatjs/intl-displaynames" "^1.2.0" - "@formatjs/intl-listformat" "^1.4.1" - "@formatjs/intl-relativetimeformat" "^4.5.9" - "@formatjs/intl-unified-numberformat" "^3.2.0" - "@formatjs/intl-utils" "^2.2.0" - "@types/hoist-non-react-statics" "^3.3.1" - "@types/invariant" "^2.2.31" - hoist-non-react-statics "^3.3.2" - intl-format-cache "^4.2.21" - intl-messageformat "^7.8.4" - intl-messageformat-parser "^3.6.4" - shallow-equal "^1.2.1" - -react-is@^16.12.0, react-is@^16.13.1, react-is@^16.7.0: - version "16.13.1" - resolved "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" - integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== - -react-is@^18.0.0, react-is@^18.2.0: - version "18.2.0" - resolved "https://registry.npmmirror.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" - integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== - -react-lifecycles-compat@^3.0.4: - version "3.0.4" - resolved "https://registry.npmmirror.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" - integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== - -react-merge-refs@^1.1.0: - version "1.1.0" - resolved "https://registry.npmmirror.com/react-merge-refs/-/react-merge-refs-1.1.0.tgz#73d88b892c6c68cbb7a66e0800faa374f4c38b06" - integrity sha512-alTKsjEL0dKH/ru1Iyn7vliS2QRcBp9zZPGoWxUOvRGWPUYgjo+V01is7p04It6KhgrzhJGnIj9GgX8W4bZoCQ== - -react-monaco-editor@^0.54.0: - version "0.54.0" - resolved "https://registry.npmmirror.com/react-monaco-editor/-/react-monaco-editor-0.54.0.tgz#ec9293249a991b08264be723c1ec0ca3a6d480d8" - integrity sha512-9JwO69851mfpuhYLHlKbae7omQWJ/2ICE2lbL0VHyNyZR8rCOH7440u+zAtDgiOMpLwmYdY1sEZCdRefywX6GQ== - dependencies: - prop-types "^15.8.1" - -react-redux@^8.0.5: - version "8.1.1" - resolved "https://registry.npmmirror.com/react-redux/-/react-redux-8.1.1.tgz#8e740f3fd864a4cd0de5ba9cdc8ad39cc9e7c81a" - integrity sha512-5W0QaKtEhj+3bC0Nj0NkqkhIv8gLADH/2kYFMTHxCVqQILiWzLv6MaLuV5wJU3BQEdHKzTfcvPN0WMS6SC1oyA== - dependencies: - "@babel/runtime" "^7.12.1" - "@types/hoist-non-react-statics" "^3.3.1" - "@types/use-sync-external-store" "^0.0.3" - hoist-non-react-statics "^3.3.2" - react-is "^18.0.0" - use-sync-external-store "^1.0.0" - -react-refresh@0.14.0, react-refresh@^0.14.0: - version "0.14.0" - resolved "https://registry.npmmirror.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e" - integrity sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ== - -react-router-dom@6.3.0: - version "6.3.0" - resolved "https://registry.npmmirror.com/react-router-dom/-/react-router-dom-6.3.0.tgz#a0216da813454e521905b5fa55e0e5176123f43d" - integrity sha512-uaJj7LKytRxZNQV8+RbzJWnJ8K2nPsOOEuX7aQstlMZKQT0164C+X2w6bnkqU3sjtLvpd5ojrezAyfZ1+0sStw== - dependencies: - history "^5.2.0" - react-router "6.3.0" - -react-router@6.3.0: - version "6.3.0" - resolved "https://registry.npmmirror.com/react-router/-/react-router-6.3.0.tgz#3970cc64b4cb4eae0c1ea5203a80334fdd175557" - integrity sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ== - dependencies: - history "^5.2.0" - -react-sortablejs@^6.1.4: - version "6.1.4" - resolved "https://registry.npmmirror.com/react-sortablejs/-/react-sortablejs-6.1.4.tgz#420ebfab602bbd935035dec24a04c8b3b836dbbf" - integrity sha512-fc7cBosfhnbh53Mbm6a45W+F735jwZ1UFIYSrIqcO/gRIFoDyZeMtgKlpV4DdyQfbCzdh5LoALLTDRxhMpTyXQ== - dependencies: - classnames "2.3.1" - tiny-invariant "1.2.0" - -react@18.1.0: - version "18.1.0" - resolved "https://registry.npmmirror.com/react/-/react-18.1.0.tgz#6f8620382decb17fdc5cc223a115e2adbf104890" - integrity sha512-4oL8ivCz5ZEPyclFQXaNksK3adutVS8l2xzZU0cqEFrE9Sb7fC0EFK5uEk74wIreL1DERyjvsU915j1pcT2uEQ== - dependencies: - loose-envify "^1.1.0" - -reactcss@^1.2.3: - version "1.2.3" - resolved "https://registry.npmmirror.com/reactcss/-/reactcss-1.2.3.tgz#c00013875e557b1cf0dfd9a368a1c3dab3b548dd" - integrity sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A== - dependencies: - lodash "^4.0.1" - -read-cache@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" - integrity sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA== - dependencies: - pify "^2.3.0" - -read-config-file@6.2.0: - version "6.2.0" - resolved "https://registry.npmmirror.com/read-config-file/-/read-config-file-6.2.0.tgz#71536072330bcd62ba814f91458b12add9fc7ade" - integrity sha512-gx7Pgr5I56JtYz+WuqEbQHj/xWo+5Vwua2jhb1VwM4Wid5PqYmZ4i00ZB0YEGIfkVBsCv9UrjgyqCiQfS/Oosg== - dependencies: - dotenv "^9.0.2" - dotenv-expand "^5.1.0" - js-yaml "^4.1.0" - json5 "^2.2.0" - lazy-val "^1.0.4" - -readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.3.3, readable-stream@^2.3.6: - version "2.3.8" - resolved "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" - integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.6.0: - version "3.6.2" - resolved "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" - integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -readdirp@~3.6.0: - version "3.6.0" - resolved "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" - integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== - dependencies: - picomatch "^2.2.1" - -real-require@^0.1.0: - version "0.1.0" - resolved "https://registry.npmmirror.com/real-require/-/real-require-0.1.0.tgz#736ac214caa20632847b7ca8c1056a0767df9381" - integrity sha512-r/H9MzAWtrv8aSVjPCMFpDMl5q66GqtmmRkRjpHTsp4zBAa+snZyiQNlMONiUmEJcsnaw0wCauJ2GWODr/aFkg== - -redux-saga@^0.16.0: - version "0.16.2" - resolved "https://registry.npmmirror.com/redux-saga/-/redux-saga-0.16.2.tgz#993662e86bc945d8509ac2b8daba3a8c615cc971" - integrity sha512-iIjKnRThI5sKPEASpUvySemjzwqwI13e3qP7oLub+FycCRDysLSAOwt958niZW6LhxfmS6Qm1BzbU70w/Koc4w== - -redux@^4.2.1: - version "4.2.1" - resolved "https://registry.npmmirror.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197" - integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w== - dependencies: - "@babel/runtime" "^7.9.2" - -reflect.getprototypeof@^1.0.2: - version "1.0.3" - resolved "https://registry.npmmirror.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.3.tgz#2738fd896fcc3477ffbd4190b40c2458026b6928" - integrity sha512-TTAOZpkJ2YLxl7mVHWrNo3iDMEkYlva/kgFcXndqMgbo/AZUmmavEkdXV+hXtE4P8xdyEKRzalaFqZVuwIk/Nw== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" - get-intrinsic "^1.1.1" - globalthis "^1.0.3" - which-builtin-type "^1.1.3" - -reflect.getprototypeof@^1.0.4: - version "1.0.4" - resolved "https://registry.npmmirror.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz#aaccbf41aca3821b87bb71d9dcbc7ad0ba50a3f3" - integrity sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - get-intrinsic "^1.2.1" - globalthis "^1.0.3" - which-builtin-type "^1.1.3" - -regenerate-unicode-properties@10.1.0, regenerate-unicode-properties@^10.1.0: - version "10.1.0" - resolved "https://registry.npmmirror.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz#7c3192cab6dd24e21cb4461e5ddd7dd24fa8374c" - integrity sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ== - dependencies: - regenerate "^1.4.2" - -regenerate-unicode-properties@10.1.1: - version "10.1.1" - resolved "https://registry.npmmirror.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz#6b0e05489d9076b04c436f318d9b067bba459480" - integrity sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q== - dependencies: - regenerate "^1.4.2" - -regenerate@^1.4.2: - version "1.4.2" - resolved "https://registry.npmmirror.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" - integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== - -regenerator-runtime@0.13.11, regenerator-runtime@^0.13.11: - version "0.13.11" - resolved "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" - integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== - -regenerator-runtime@^0.14.0: - version "0.14.0" - resolved "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45" - integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA== - -regenerator-transform@^0.15.1: - version "0.15.1" - resolved "https://registry.npmmirror.com/regenerator-transform/-/regenerator-transform-0.15.1.tgz#f6c4e99fc1b4591f780db2586328e4d9a9d8dc56" - integrity sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg== - dependencies: - "@babel/runtime" "^7.8.4" - -regexp.prototype.flags@^1.4.3, regexp.prototype.flags@^1.5.0: - version "1.5.0" - resolved "https://registry.npmmirror.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz#fe7ce25e7e4cca8db37b6634c8a2c7009199b9cb" - integrity sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - functions-have-names "^1.2.3" - -regexp.prototype.flags@^1.5.1: - version "1.5.1" - resolved "https://registry.npmmirror.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz#90ce989138db209f81492edd734183ce99f9677e" - integrity sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - set-function-name "^2.0.0" - -regexpu-core@^5.3.1: - version "5.3.2" - resolved "https://registry.npmmirror.com/regexpu-core/-/regexpu-core-5.3.2.tgz#11a2b06884f3527aec3e93dbbf4a3b958a95546b" - integrity sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ== - dependencies: - "@babel/regjsgen" "^0.8.0" - regenerate "^1.4.2" - regenerate-unicode-properties "^10.1.0" - regjsparser "^0.9.1" - unicode-match-property-ecmascript "^2.0.0" - unicode-match-property-value-ecmascript "^2.1.0" - -regjsparser@^0.9.1: - version "0.9.1" - resolved "https://registry.npmmirror.com/regjsparser/-/regjsparser-0.9.1.tgz#272d05aa10c7c1f67095b1ff0addae8442fc5709" - integrity sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ== - dependencies: - jsesc "~0.5.0" - -relateurl@^0.2.7: - version "0.2.7" - resolved "https://registry.npmmirror.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" - integrity sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog== - -remove-accents@0.4.2: - version "0.4.2" - resolved "https://registry.npmmirror.com/remove-accents/-/remove-accents-0.4.2.tgz#0a43d3aaae1e80db919e07ae254b285d9e1c7bb5" - integrity sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA== - -renderkid@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/renderkid/-/renderkid-3.0.0.tgz#5fd823e4d6951d37358ecc9a58b1f06836b6268a" - integrity sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg== - dependencies: - css-select "^4.1.3" - dom-converter "^0.2.0" - htmlparser2 "^6.1.0" - lodash "^4.17.21" - strip-ansi "^6.0.1" - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== - -resize-observer-polyfill@^1.5.1: - version "1.5.1" - resolved "https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464" - integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg== - -resolve-alpn@^1.0.0: - version "1.2.1" - resolved "https://registry.npmmirror.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" - integrity sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g== - -resolve-from@^4.0.0: - version "4.0.0" - resolved "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" - integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== - -resolve-from@^5.0.0: - version "5.0.0" - resolved "https://registry.npmmirror.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" - integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== - -resolve-pkg-maps@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f" - integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== - -resolve@^1.1.7, resolve@^1.22.2: - version "1.22.4" - resolved "https://registry.npmmirror.com/resolve/-/resolve-1.22.4.tgz#1dc40df46554cdaf8948a486a10f6ba1e2026c34" - integrity sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg== - dependencies: - is-core-module "^2.13.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -resolve@^1.14.2: - version "1.22.2" - resolved "https://registry.npmmirror.com/resolve/-/resolve-1.22.2.tgz#0ed0943d4e301867955766c9f3e1ae6d01c6845f" - integrity sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g== - dependencies: - is-core-module "^2.11.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -resolve@^1.22.4: - version "1.22.6" - resolved "https://registry.npmmirror.com/resolve/-/resolve-1.22.6.tgz#dd209739eca3aef739c626fea1b4f3c506195362" - integrity sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw== - dependencies: - is-core-module "^2.13.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -resolve@^2.0.0-next.4: - version "2.0.0-next.4" - resolved "https://registry.npmmirror.com/resolve/-/resolve-2.0.0-next.4.tgz#3d37a113d6429f496ec4752d2a2e58efb1fd4660" - integrity sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ== - dependencies: - is-core-module "^2.9.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -responselike@^2.0.0: - version "2.0.1" - resolved "https://registry.npmmirror.com/responselike/-/responselike-2.0.1.tgz#9a0bc8fdc252f3fb1cca68b016591059ba1422bc" - integrity sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw== - dependencies: - lowercase-keys "^2.0.0" - -ret@~0.1.10: - version "0.1.15" - resolved "https://registry.npmmirror.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" - integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== - -reusify@^1.0.4: - version "1.0.4" - resolved "https://registry.npmmirror.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" - integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== - -rimraf@^3.0.0, rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.npmmirror.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - -ripemd160@^2.0.0, ripemd160@^2.0.1: - version "2.0.2" - resolved "https://registry.npmmirror.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" - integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== - dependencies: - hash-base "^3.0.0" - inherits "^2.0.1" - -roarr@^2.15.3: - version "2.15.4" - resolved "https://registry.npmmirror.com/roarr/-/roarr-2.15.4.tgz#f5fe795b7b838ccfe35dc608e0282b9eba2e7afd" - integrity sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A== - dependencies: - boolean "^3.0.1" - detect-node "^2.0.4" - globalthis "^1.0.1" - json-stringify-safe "^5.0.1" - semver-compare "^1.0.0" - sprintf-js "^1.1.2" - -rollup-plugin-visualizer@5.9.0: - version "5.9.0" - resolved "https://registry.npmmirror.com/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.9.0.tgz#013ac54fb6a9d7c9019e7eb77eced673399e5a0b" - integrity sha512-bbDOv47+Bw4C/cgs0czZqfm8L82xOZssk4ayZjG40y9zbXclNk7YikrZTDao6p7+HDiGxrN0b65SgZiVm9k1Cg== - dependencies: - open "^8.4.0" - picomatch "^2.3.1" - source-map "^0.7.4" - yargs "^17.5.1" - -rollup@^3.20.2: - version "3.26.3" - resolved "https://registry.npmmirror.com/rollup/-/rollup-3.26.3.tgz#bbc8818cadd0aebca348dbb3d68d296d220967b8" - integrity sha512-7Tin0C8l86TkpcMtXvQu6saWH93nhG3dGQ1/+l5V2TDMceTxO7kDiK6GzbfLWNNxqJXm591PcEZUozZm51ogwQ== - optionalDependencies: - fsevents "~2.3.2" - -run-applescript@^5.0.0: - version "5.0.0" - resolved "https://registry.npmmirror.com/run-applescript/-/run-applescript-5.0.0.tgz#e11e1c932e055d5c6b40d98374e0268d9b11899c" - integrity sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg== - dependencies: - execa "^5.0.0" - -run-parallel@^1.1.9: - version "1.2.0" - resolved "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" - integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== - dependencies: - queue-microtask "^1.2.2" - -rxjs@^6.5.4: - version "6.6.7" - resolved "https://registry.npmmirror.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" - integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== - dependencies: - tslib "^1.9.0" - -rxjs@^7.8.1: - version "7.8.1" - resolved "https://registry.npmmirror.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" - integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== - dependencies: - tslib "^2.1.0" - -safe-array-concat@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/safe-array-concat/-/safe-array-concat-1.0.0.tgz#2064223cba3c08d2ee05148eedbc563cd6d84060" - integrity sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.2.0" - has-symbols "^1.0.3" - isarray "^2.0.5" - -safe-array-concat@^1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/safe-array-concat/-/safe-array-concat-1.0.1.tgz#91686a63ce3adbea14d61b14c99572a8ff84754c" - integrity sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.2.1" - has-symbols "^1.0.3" - isarray "^2.0.5" - -safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-regex-test@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz#793b874d524eb3640d1873aad03596db2d4f2295" - integrity sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.1.3" - is-regex "^1.1.4" - -safe-stable-stringify@^2.1.0: - version "2.4.3" - resolved "https://registry.npmmirror.com/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz#138c84b6f6edb3db5f8ef3ef7115b8f55ccbf886" - integrity sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g== - -"safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.1.0: - version "2.1.2" - resolved "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -sanitize-filename@^1.6.3: - version "1.6.3" - resolved "https://registry.npmmirror.com/sanitize-filename/-/sanitize-filename-1.6.3.tgz#755ebd752045931977e30b2025d340d7c9090378" - integrity sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg== - dependencies: - truncate-utf8-bytes "^1.0.0" - -sax@^1.2.4: - version "1.2.4" - resolved "https://registry.npmmirror.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" - integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== - -scheduler@^0.22.0: - version "0.22.0" - resolved "https://registry.npmmirror.com/scheduler/-/scheduler-0.22.0.tgz#83a5d63594edf074add9a7198b1bae76c3db01b8" - integrity sha512-6QAm1BgQI88NPYymgGQLCZgvep4FyePDWFpXVK+zNSUgHwlqpJy8VEh8Et0KxTACS4VWwMousBElAZOH9nkkoQ== - dependencies: - loose-envify "^1.1.0" - -schema-utils@^3.0.0, schema-utils@^3.1.1: - version "3.3.0" - resolved "https://registry.npmmirror.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe" - integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg== - dependencies: - "@types/json-schema" "^7.0.8" - ajv "^6.12.5" - ajv-keywords "^3.5.2" - -screenfull@^5.0.0: - version "5.2.0" - resolved "https://registry.npmmirror.com/screenfull/-/screenfull-5.2.0.tgz#6533d524d30621fc1283b9692146f3f13a93d1ba" - integrity sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA== - -scroll-into-view-if-needed@^3.1.0: - version "3.1.0" - resolved "https://registry.npmmirror.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz#fa9524518c799b45a2ef6bbffb92bcad0296d01f" - integrity sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ== - dependencies: - compute-scroll-into-view "^3.0.2" - -select-hose@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" - integrity sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg== - -semver-compare@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" - integrity sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow== - -semver@^5.6.0, semver@^5.7.2: - version "5.7.2" - resolved "https://registry.npmmirror.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" - integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== - -semver@^6.2.0, semver@^6.3.0, semver@^6.3.1: - version "6.3.1" - resolved "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" - integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== - -semver@^7.3.2, semver@^7.3.5, semver@^7.3.7, semver@^7.5.4: - version "7.5.4" - resolved "https://registry.npmmirror.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" - integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== - dependencies: - lru-cache "^6.0.0" - -semver@~7.0.0: - version "7.0.0" - resolved "https://registry.npmmirror.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" - integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== - -serialize-error@^7.0.1: - version "7.0.1" - resolved "https://registry.npmmirror.com/serialize-error/-/serialize-error-7.0.1.tgz#f1360b0447f61ffb483ec4157c737fab7d778e18" - integrity sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw== - dependencies: - type-fest "^0.13.1" - -set-function-name@^2.0.0, set-function-name@^2.0.1: - version "2.0.1" - resolved "https://registry.npmmirror.com/set-function-name/-/set-function-name-2.0.1.tgz#12ce38b7954310b9f61faa12701620a0c882793a" - integrity sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA== - dependencies: - define-data-property "^1.0.1" - functions-have-names "^1.2.3" - has-property-descriptors "^1.0.0" - -setimmediate@^1.0.4: - version "1.0.5" - resolved "https://registry.npmmirror.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" - integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== - -sha.js@^2.4.0, sha.js@^2.4.8: - version "2.4.11" - resolved "https://registry.npmmirror.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" - integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - -shallow-equal@^1.2.1: - version "1.2.1" - resolved "https://registry.npmmirror.com/shallow-equal/-/shallow-equal-1.2.1.tgz#4c16abfa56043aa20d050324efa68940b0da79da" - integrity sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA== - -shallowequal@^1.1.0: - version "1.1.0" - resolved "https://registry.npmmirror.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" - integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -shell-quote@^1.8.1: - version "1.8.1" - resolved "https://registry.npmmirror.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680" - integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA== - -side-channel@^1.0.4: - version "1.0.4" - resolved "https://registry.npmmirror.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" - integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== - dependencies: - call-bind "^1.0.0" - get-intrinsic "^1.0.2" - object-inspect "^1.9.0" - -signal-exit@^3.0.3, signal-exit@^3.0.7: - version "3.0.7" - resolved "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - -simple-update-notifier@^1.0.7: - version "1.1.0" - resolved "https://registry.npmmirror.com/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz#67694c121de354af592b347cdba798463ed49c82" - integrity sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg== - dependencies: - semver "~7.0.0" - -single-spa@^5.9.2: - version "5.9.5" - resolved "https://registry.npmmirror.com/single-spa/-/single-spa-5.9.5.tgz#f47b3c91b009ebc3b224dd1086ef2b2dac524373" - integrity sha512-9SQdmsyz4HSP+3gs6PJzhkaMEg+6zTlu9oxIghnwUX3eq+ajq4ft5egl0iyR55LAmO/UwvU8NgIWs/ZyQMa6dw== - -size-sensor@^1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/size-sensor/-/size-sensor-1.0.1.tgz#f84e46206d3e259faff1d548e4b3beca93219dbb" - integrity sha512-QTy7MnuugCFXIedXRpUSk9gUnyNiaxIdxGfUjr8xxXOqIB3QvBUYP9+b51oCg2C4dnhaeNk/h57TxjbvoJrJUA== - -slash@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" - integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== - -slash@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" - integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== - -slash@^4.0.0: - version "4.0.0" - resolved "https://registry.npmmirror.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7" - integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== - -slice-ansi@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787" - integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ== - dependencies: - ansi-styles "^4.0.0" - astral-regex "^2.0.0" - is-fullwidth-code-point "^3.0.0" - -smart-buffer@^4.0.2: - version "4.2.0" - resolved "https://registry.npmmirror.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" - integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== - -sonic-boom@^2.2.1: - version "2.8.0" - resolved "https://registry.npmmirror.com/sonic-boom/-/sonic-boom-2.8.0.tgz#c1def62a77425090e6ad7516aad8eb402e047611" - integrity sha512-kuonw1YOYYNOve5iHdSahXPOK49GqwA+LZhI6Wz/l0rP57iKyXXIHaRagOBHAPmGwJC6od2Z9zgvZ5loSgMlVg== - dependencies: - atomic-sleep "^1.0.0" - -sort-object-keys@^1.1.3: - version "1.1.3" - resolved "https://registry.npmmirror.com/sort-object-keys/-/sort-object-keys-1.1.3.tgz#bff833fe85cab147b34742e45863453c1e190b45" - integrity sha512-855pvK+VkU7PaKYPc+Jjnmt4EzejQHyhhF33q31qG8x7maDzkeFhAAThdCYay11CISO+qAMwjOBP+fPZe0IPyg== - -sort-package-json@2.4.1: - version "2.4.1" - resolved "https://registry.npmmirror.com/sort-package-json/-/sort-package-json-2.4.1.tgz#4ea68a0b9ef34c2bc519e86d0d07de56622a7600" - integrity sha512-Nd3rgLBJcZ4iw7tpuOhwBupG6SvUDU0Fy1cZGAMorA2JmDUb+29Dg5phJK9gapa2Ak9d15w/RuMl/viwX+nKwQ== - dependencies: - detect-indent "^7.0.1" - detect-newline "^4.0.0" - git-hooks-list "^3.0.0" - globby "^13.1.2" - is-plain-obj "^4.1.0" - sort-object-keys "^1.1.3" - -sort-package-json@2.5.1: - version "2.5.1" - resolved "https://registry.npmmirror.com/sort-package-json/-/sort-package-json-2.5.1.tgz#5c0f2ce8cc8851988e5039f76b8978439439039d" - integrity sha512-vx/KoZxm8YNMUqdlw7SGTfqR5pqZ/sUfgOuRtDILiOy/3AvzhAibyUe2cY3OpLs3oRSow9up4yLVtQaM24rbDQ== - dependencies: - detect-indent "^7.0.1" - detect-newline "^4.0.0" - get-stdin "^9.0.0" - git-hooks-list "^3.0.0" - globby "^13.1.2" - is-plain-obj "^4.1.0" - sort-object-keys "^1.1.3" - -source-map-js@^1.0.2: - version "1.0.2" - resolved "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" - integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== - -source-map-support@^0.5.19, source-map-support@^0.5.21, source-map-support@~0.5.20: - version "0.5.21" - resolved "https://registry.npmmirror.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" - integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0: - version "0.6.1" - resolved "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -source-map@^0.7.3, source-map@^0.7.4: - version "0.7.4" - resolved "https://registry.npmmirror.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" - integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== - -spawn-command@0.0.2: - version "0.0.2" - resolved "https://registry.npmmirror.com/spawn-command/-/spawn-command-0.0.2.tgz#9544e1a43ca045f8531aac1a48cb29bdae62338e" - integrity sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ== - -spdy-transport@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" - integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== - dependencies: - debug "^4.1.0" - detect-node "^2.0.4" - hpack.js "^2.1.6" - obuf "^1.1.2" - readable-stream "^3.0.6" - wbuf "^1.7.3" - -spdy@^4.0.2: - version "4.0.2" - resolved "https://registry.npmmirror.com/spdy/-/spdy-4.0.2.tgz#b74f466203a3eda452c02492b91fb9e84a27677b" - integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA== - dependencies: - debug "^4.1.0" - handle-thing "^2.0.0" - http-deceiver "^1.2.7" - select-hose "^2.0.0" - spdy-transport "^3.0.0" - -split-on-first@^1.0.0: - version "1.1.0" - resolved "https://registry.npmmirror.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f" - integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw== - -split2@^4.0.0: - version "4.2.0" - resolved "https://registry.npmmirror.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" - integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== - -sprintf-js@^1.1.2: - version "1.1.3" - resolved "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a" - integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== - -sql-formatter@^13.0.4: - version "13.1.0" - resolved "https://registry.npmmirror.com/sql-formatter/-/sql-formatter-13.1.0.tgz#272cccd9b249daee601e791cb12eb3c93edfd626" - integrity sha512-/nZQXuN7KzipFNM20ko+dHY4kOr9rymSfZLUDED8rhx3m8OK5y74jcyN+y1L51ZqHqiB0kp40VdpZP99uWvQdA== - dependencies: - argparse "^2.0.1" - get-stdin "=8.0.0" - nearley "^2.20.1" - -stable@^0.1.8: - version "0.1.8" - resolved "https://registry.npmmirror.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" - integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== - -stackframe@^1.3.4: - version "1.3.4" - resolved "https://registry.npmmirror.com/stackframe/-/stackframe-1.3.4.tgz#b881a004c8c149a5e8efef37d51b16e412943310" - integrity sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw== - -stat-mode@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/stat-mode/-/stat-mode-1.0.0.tgz#68b55cb61ea639ff57136f36b216a291800d1465" - integrity sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg== - -stop-iteration-iterator@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz#6a60be0b4ee757d1ed5254858ec66b10c49285e4" - integrity sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ== - dependencies: - internal-slot "^1.0.4" - -stream-browserify@^2.0.1: - version "2.0.2" - resolved "https://registry.npmmirror.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" - integrity sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg== - dependencies: - inherits "~2.0.1" - readable-stream "^2.0.2" - -stream-http@^2.7.2: - version "2.8.3" - resolved "https://registry.npmmirror.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" - integrity sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw== - dependencies: - builtin-status-codes "^3.0.0" - inherits "^2.0.1" - readable-stream "^2.3.6" - to-arraybuffer "^1.0.0" - xtend "^4.0.0" - -stream-shift@^1.0.0: - version "1.0.1" - resolved "https://registry.npmmirror.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" - integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== - -strict-uri-encode@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" - integrity sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ== - -string-convert@^0.2.0: - version "0.2.1" - resolved "https://registry.npmmirror.com/string-convert/-/string-convert-0.2.1.tgz#6982cc3049fbb4cd85f8b24568b9d9bf39eeff97" - integrity sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A== - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string.prototype.matchall@^4.0.8: - version "4.0.8" - resolved "https://registry.npmmirror.com/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz#3bf85722021816dcd1bf38bb714915887ca79fd3" - integrity sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" - get-intrinsic "^1.1.3" - has-symbols "^1.0.3" - internal-slot "^1.0.3" - regexp.prototype.flags "^1.4.3" - side-channel "^1.0.4" - -string.prototype.trim@^1.2.7: - version "1.2.7" - resolved "https://registry.npmmirror.com/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz#a68352740859f6893f14ce3ef1bb3037f7a90533" - integrity sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" - -string.prototype.trim@^1.2.8: - version "1.2.8" - resolved "https://registry.npmmirror.com/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz#f9ac6f8af4bd55ddfa8895e6aea92a96395393bd" - integrity sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - -string.prototype.trimend@^1.0.6: - version "1.0.6" - resolved "https://registry.npmmirror.com/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz#c4a27fa026d979d79c04f17397f250a462944533" - integrity sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" - -string.prototype.trimend@^1.0.7: - version "1.0.7" - resolved "https://registry.npmmirror.com/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz#1bb3afc5008661d73e2dc015cd4853732d6c471e" - integrity sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - -string.prototype.trimstart@^1.0.6: - version "1.0.6" - resolved "https://registry.npmmirror.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz#e90ab66aa8e4007d92ef591bbf3cd422c56bdcf4" - integrity sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" - -string.prototype.trimstart@^1.0.7: - version "1.0.7" - resolved "https://registry.npmmirror.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz#d4cdb44b83a4737ffbac2d406e405d43d0184298" - integrity sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - -string_decoder@^1.0.0, string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-bom@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" - integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== - -strip-final-newline@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" - integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== - -strip-final-newline@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd" - integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw== - -strip-json-comments@^3.1.1: - version "3.1.1" - resolved "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" - integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== - -styled-components@6.0.0-rc.0: - version "6.0.0-rc.0" - resolved "https://registry.npmmirror.com/styled-components/-/styled-components-6.0.0-rc.0.tgz#c92f8f3c1d16edf780d84f51eeac67bd2a42754b" - integrity sha512-3+Lnu1NC5JuieYi8dV/nhmlK7/yzqZW43u4P7WgIJfu5Dq5AiPU3t4efu0nWLmlMEmWrSXdrinxfbDnqnpP6hg== - dependencies: - "@babel/cli" "^7.21.0" - "@babel/core" "^7.21.0" - "@babel/helper-module-imports" "^7.18.6" - "@babel/plugin-external-helpers" "^7.18.6" - "@babel/plugin-proposal-class-properties" "^7.18.6" - "@babel/plugin-proposal-object-rest-spread" "^7.20.7" - "@babel/preset-env" "^7.20.2" - "@babel/preset-react" "^7.18.6" - "@babel/preset-typescript" "^7.21.0" - "@babel/traverse" "^7.21.2" - "@emotion/unitless" "^0.8.0" - css-to-react-native "^3.2.0" - shallowequal "^1.1.0" - stylis "^4.1.4" - tslib "^2.5.0" - -"styled-components@^3.4.10 || ^5.0.1": - version "5.3.11" - resolved "https://registry.npmmirror.com/styled-components/-/styled-components-5.3.11.tgz#9fda7bf1108e39bf3f3e612fcc18170dedcd57a8" - integrity sha512-uuzIIfnVkagcVHv9nE0VPlHPSCmXIUGKfJ42LNjxCCTDTL5sgnJ8Z7GZBq0EnLYGln77tPpEpExt2+qa+cZqSw== - dependencies: - "@babel/helper-module-imports" "^7.0.0" - "@babel/traverse" "^7.4.5" - "@emotion/is-prop-valid" "^1.1.0" - "@emotion/stylis" "^0.8.4" - "@emotion/unitless" "^0.7.4" - babel-plugin-styled-components ">= 1.12.0" - css-to-react-native "^3.0.0" - hoist-non-react-statics "^3.0.0" - shallowequal "^1.1.0" - supports-color "^5.5.0" - -styled-components@^6.0.1: - version "6.0.4" - resolved "https://registry.npmmirror.com/styled-components/-/styled-components-6.0.4.tgz#55bb3a1197daf8075ae8b345b57eb03f2570d51e" - integrity sha512-lRJt4vg8hKJhlVG+VKz8QEqPCXKyTryZZ59odyK0UC0HHV3u/mshWTfSay8NpkN0Xijw1iN9r0Leld3dcCcp/w== - dependencies: - "@babel/cli" "^7.21.0" - "@babel/core" "^7.21.0" - "@babel/helper-module-imports" "^7.18.6" - "@babel/plugin-external-helpers" "^7.18.6" - "@babel/plugin-proposal-class-properties" "^7.18.6" - "@babel/plugin-proposal-object-rest-spread" "^7.20.7" - "@babel/preset-env" "^7.20.2" - "@babel/preset-react" "^7.18.6" - "@babel/preset-typescript" "^7.21.0" - "@babel/traverse" "^7.21.2" - "@emotion/is-prop-valid" "^1.2.1" - "@emotion/unitless" "^0.8.0" - "@types/stylis" "^4.0.2" - css-to-react-native "^3.2.0" - csstype "^3.1.2" - postcss "^8.4.23" - shallowequal "^1.1.0" - stylis "^4.3.0" - tslib "^2.5.0" - -stylelint-config-recommended@^7.0.0: - version "7.0.0" - resolved "https://registry.npmmirror.com/stylelint-config-recommended/-/stylelint-config-recommended-7.0.0.tgz#7497372ae83ab7a6fffc18d7d7b424c6480ae15e" - integrity sha512-yGn84Bf/q41J4luis1AZ95gj0EQwRX8lWmGmBwkwBNSkpGSpl66XcPTulxGa/Z91aPoNGuIGBmFkcM1MejMo9Q== - -stylelint-config-standard@25.0.0: - version "25.0.0" - resolved "https://registry.npmmirror.com/stylelint-config-standard/-/stylelint-config-standard-25.0.0.tgz#2c916984e6655d40d6e8748b19baa8603b680bff" - integrity sha512-21HnP3VSpaT1wFjFvv9VjvOGDtAviv47uTp3uFmzcN+3Lt+RYRv6oAplLaV51Kf792JSxJ6svCJh/G18E9VnCA== - dependencies: - stylelint-config-recommended "^7.0.0" - -stylis@^4.0.13, stylis@^4.1.4, stylis@^4.3.0: - version "4.3.0" - resolved "https://registry.npmmirror.com/stylis/-/stylis-4.3.0.tgz#abe305a669fc3d8777e10eefcfc73ad861c5588c" - integrity sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ== - -sucrase@^3.32.0: - version "3.34.0" - resolved "https://registry.npmmirror.com/sucrase/-/sucrase-3.34.0.tgz#1e0e2d8fcf07f8b9c3569067d92fbd8690fb576f" - integrity sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw== - dependencies: - "@jridgewell/gen-mapping" "^0.3.2" - commander "^4.0.0" - glob "7.1.6" - lines-and-columns "^1.1.6" - mz "^2.7.0" - pirates "^4.0.1" - ts-interface-checker "^0.1.9" - -sumchecker@^3.0.1: - version "3.0.1" - resolved "https://registry.npmmirror.com/sumchecker/-/sumchecker-3.0.1.tgz#6377e996795abb0b6d348e9b3e1dfb24345a8e42" - integrity sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg== - dependencies: - debug "^4.1.0" - -superjson@^1.10.0: - version "1.13.1" - resolved "https://registry.npmmirror.com/superjson/-/superjson-1.13.1.tgz#a0b6ab5d22876f6207fcb9d08b0cb2acad8ee5cd" - integrity sha512-AVH2eknm9DEd3qvxM4Sq+LTCkSXE2ssfh1t11MHMXyYXFQyQ1HLgVvV+guLTsaQnJU3gnaVo34TohHPulY/wLg== - dependencies: - copy-anything "^3.0.2" - -supports-color@^5.3.0, supports-color@^5.5.0: - version "5.5.0" - resolved "https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - -supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -supports-color@^8.0.0, supports-color@^8.1.1: - version "8.1.1" - resolved "https://registry.npmmirror.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - -svg-parser@^2.0.4: - version "2.0.4" - resolved "https://registry.npmmirror.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5" - integrity sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ== - -svgo@^2.8.0: - version "2.8.0" - resolved "https://registry.npmmirror.com/svgo/-/svgo-2.8.0.tgz#4ff80cce6710dc2795f0c7c74101e6764cfccd24" - integrity sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg== - dependencies: - "@trysound/sax" "0.2.0" - commander "^7.2.0" - css-select "^4.1.3" - css-tree "^1.1.3" - csso "^4.2.0" - picocolors "^1.0.0" - stable "^0.1.8" - -swr@^2.0.0: - version "2.2.0" - resolved "https://registry.npmmirror.com/swr/-/swr-2.2.0.tgz#575c6ac1bec087847f4c86a39ccbc0043c834d6a" - integrity sha512-AjqHOv2lAhkuUdIiBu9xbuettzAzWXmCEcLONNKJRba87WAefz8Ca9d6ds/SzrPc235n1IxWYdhJ2zF3MNUaoQ== - dependencies: - use-sync-external-store "^1.2.0" - -synckit@0.8.5, synckit@^0.8.5: - version "0.8.5" - resolved "https://registry.npmmirror.com/synckit/-/synckit-0.8.5.tgz#b7f4358f9bb559437f9f167eb6bc46b3c9818fa3" - integrity sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q== - dependencies: - "@pkgr/utils" "^2.3.1" - tslib "^2.5.0" - -systemjs@^6.14.1: - version "6.14.2" - resolved "https://registry.npmmirror.com/systemjs/-/systemjs-6.14.2.tgz#e289f959f8c8b407403bd39c6abaa16f2c13f316" - integrity sha512-1TlOwvKWdXxAY9vba+huLu99zrQURDWA8pUTYsRIYDZYQbGyK+pyEP4h4dlySsqo7ozyJBmYD20F+iUHhAltEg== - -tailwindcss@^3: - version "3.3.3" - resolved "https://registry.npmmirror.com/tailwindcss/-/tailwindcss-3.3.3.tgz#90da807393a2859189e48e9e7000e6880a736daf" - integrity sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w== - dependencies: - "@alloc/quick-lru" "^5.2.0" - arg "^5.0.2" - chokidar "^3.5.3" - didyoumean "^1.2.2" - dlv "^1.1.3" - fast-glob "^3.2.12" - glob-parent "^6.0.2" - is-glob "^4.0.3" - jiti "^1.18.2" - lilconfig "^2.1.0" - micromatch "^4.0.5" - normalize-path "^3.0.0" - object-hash "^3.0.0" - picocolors "^1.0.0" - postcss "^8.4.23" - postcss-import "^15.1.0" - postcss-js "^4.0.1" - postcss-load-config "^4.0.1" - postcss-nested "^6.0.1" - postcss-selector-parser "^6.0.11" - resolve "^1.22.2" - sucrase "^3.32.0" - -tapable@^0.1.8: - version "0.1.10" - resolved "https://registry.npmmirror.com/tapable/-/tapable-0.1.10.tgz#29c35707c2b70e50d07482b5d202e8ed446dafd4" - integrity sha512-jX8Et4hHg57mug1/079yitEKWGB3LCwoxByLsNim89LABq8NqgiX+6iYVOsq0vX8uJHkU+DZ5fnq95f800bEsQ== - -tapable@^2.0.0, tapable@^2.2.0, tapable@^2.2.1: - version "2.2.1" - resolved "https://registry.npmmirror.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" - integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== - -tar@^6.1.11: - version "6.1.15" - resolved "https://registry.npmmirror.com/tar/-/tar-6.1.15.tgz#c9738b0b98845a3b344d334b8fa3041aaba53a69" - integrity sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A== - dependencies: - chownr "^2.0.0" - fs-minipass "^2.0.0" - minipass "^5.0.0" - minizlib "^2.1.1" - mkdirp "^1.0.3" - yallist "^4.0.0" - -temp-file@^3.4.0: - version "3.4.0" - resolved "https://registry.npmmirror.com/temp-file/-/temp-file-3.4.0.tgz#766ea28911c683996c248ef1a20eea04d51652c7" - integrity sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg== - dependencies: - async-exit-hook "^2.0.1" - fs-extra "^10.0.0" - -terser@^5.10.0: - version "5.19.1" - resolved "https://registry.npmmirror.com/terser/-/terser-5.19.1.tgz#dbd7231f224a9e2401d0f0959542ed74d76d340b" - integrity sha512-27hxBUVdV6GoNg1pKQ7Z5cbR6V9txPVyBA+FQw3BaZ1Wuzvztce5p156DaP0NVZNrMZZ+6iG9Syf7WgMNKDg2Q== - dependencies: - "@jridgewell/source-map" "^0.3.3" - acorn "^8.8.2" - commander "^2.20.0" - source-map-support "~0.5.20" - -test-exclude@^6.0.0: - version "6.0.0" - resolved "https://registry.npmmirror.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" - integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== - dependencies: - "@istanbuljs/schema" "^0.1.2" - glob "^7.1.4" - minimatch "^3.0.4" - -text-table@^0.2.0: - version "0.2.0" - resolved "https://registry.npmmirror.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" - integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== - -thenify-all@^1.0.0: - version "1.6.0" - resolved "https://registry.npmmirror.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" - integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== - dependencies: - thenify ">= 3.1.0 < 4" - -"thenify@>= 3.1.0 < 4": - version "3.3.1" - resolved "https://registry.npmmirror.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" - integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== - dependencies: - any-promise "^1.0.0" - -thread-stream@^0.15.1: - version "0.15.2" - resolved "https://registry.npmmirror.com/thread-stream/-/thread-stream-0.15.2.tgz#fb95ad87d2f1e28f07116eb23d85aba3bc0425f4" - integrity sha512-UkEhKIg2pD+fjkHQKyJO3yoIvAP3N6RlNFt2dUhcS1FGvCD1cQa1M/PGknCLFIyZdtJOWQjejp7bdNqmN7zwdA== - dependencies: - real-require "^0.1.0" - -throttle-debounce@^5.0.0: - version "5.0.0" - resolved "https://registry.npmmirror.com/throttle-debounce/-/throttle-debounce-5.0.0.tgz#a17a4039e82a2ed38a5e7268e4132d6960d41933" - integrity sha512-2iQTSgkkc1Zyk0MeVrt/3BvuOXYPl/R8Z0U2xxo9rjwNciaHDG3R+Lm6dh4EeUci49DanvBnuqI6jshoQQRGEg== - -timers-browserify@^2.0.4: - version "2.0.12" - resolved "https://registry.npmmirror.com/timers-browserify/-/timers-browserify-2.0.12.tgz#44a45c11fbf407f34f97bccd1577c652361b00ee" - integrity sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ== - dependencies: - setimmediate "^1.0.4" - -tiny-invariant@1.2.0: - version "1.2.0" - resolved "https://registry.npmmirror.com/tiny-invariant/-/tiny-invariant-1.2.0.tgz#a1141f86b672a9148c72e978a19a73b9b94a15a9" - integrity sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg== - -tinycolor2@^1.4.2: - version "1.6.0" - resolved "https://registry.npmmirror.com/tinycolor2/-/tinycolor2-1.6.0.tgz#f98007460169b0263b97072c5ae92484ce02d09e" - integrity sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw== - -titleize@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/titleize/-/titleize-3.0.0.tgz#71c12eb7fdd2558aa8a44b0be83b8a76694acd53" - integrity sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ== - -tmp-promise@^3.0.2: - version "3.0.3" - resolved "https://registry.npmmirror.com/tmp-promise/-/tmp-promise-3.0.3.tgz#60a1a1cc98c988674fcbfd23b6e3367bdeac4ce7" - integrity sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ== - dependencies: - tmp "^0.2.0" - -tmp@^0.2.0: - version "0.2.1" - resolved "https://registry.npmmirror.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" - integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== - dependencies: - rimraf "^3.0.0" - -tmpl@1.0.5: - version "1.0.5" - resolved "https://registry.npmmirror.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" - integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== - -to-arraybuffer@^1.0.0: - version "1.0.1" - resolved "https://registry.npmmirror.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" - integrity sha512-okFlQcoGTi4LQBG/PgSYblw9VOyptsz2KJZqc6qtgGdes8VktzUQkj4BI2blit072iS8VODNcMA+tvnS9dnuMA== - -to-fast-properties@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" - integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -toggle-selection@^1.0.6: - version "1.0.6" - resolved "https://registry.npmmirror.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32" - integrity sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ== - -tree-kill@^1.2.2: - version "1.2.2" - resolved "https://registry.npmmirror.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" - integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== - -truncate-utf8-bytes@^1.0.0: - version "1.0.2" - resolved "https://registry.npmmirror.com/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz#405923909592d56f78a5818434b0b78489ca5f2b" - integrity sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ== - dependencies: - utf8-byte-length "^1.0.1" - -ts-api-utils@^1.0.1: - version "1.0.3" - resolved "https://registry.npmmirror.com/ts-api-utils/-/ts-api-utils-1.0.3.tgz#f12c1c781d04427313dbac808f453f050e54a331" - integrity sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg== - -ts-interface-checker@^0.1.9: - version "0.1.13" - resolved "https://registry.npmmirror.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" - integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== - -tsconfig-paths@^3.14.2: - version "3.14.2" - resolved "https://registry.npmmirror.com/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz#6e32f1f79412decd261f92d633a9dc1cfa99f088" - integrity sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g== - dependencies: - "@types/json5" "^0.0.29" - json5 "^1.0.2" - minimist "^1.2.6" - strip-bom "^3.0.0" - -tslib@2.3.0: - version "2.3.0" - resolved "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e" - integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg== - -tslib@^1.8.1, tslib@^1.9.0: - version "1.14.1" - resolved "https://registry.npmmirror.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" - integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== - -tslib@^2, tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.4.1, tslib@^2.5.0, tslib@^2.6.0: - version "2.6.0" - resolved "https://registry.npmmirror.com/tslib/-/tslib-2.6.0.tgz#b295854684dbda164e181d259a22cd779dcd7bc3" - integrity sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA== - -tsutils@^3.21.0: - version "3.21.0" - resolved "https://registry.npmmirror.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" - integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== - dependencies: - tslib "^1.8.1" - -tsx@^3.12.2: - version "3.12.7" - resolved "https://registry.npmmirror.com/tsx/-/tsx-3.12.7.tgz#b3b8b0fc79afc8260d1e14f9e995616c859a91e9" - integrity sha512-C2Ip+jPmqKd1GWVQDvz/Eyc6QJbGfE7NrR3fx5BpEHMZsEHoIxHL1j+lKdGobr8ovEyqeNkPLSKp6SCSOt7gmw== - dependencies: - "@esbuild-kit/cjs-loader" "^2.4.2" - "@esbuild-kit/core-utils" "^3.0.0" - "@esbuild-kit/esm-loader" "^2.5.5" - optionalDependencies: - fsevents "~2.3.2" - -tty-browserify@0.0.0: - version "0.0.0" - resolved "https://registry.npmmirror.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" - integrity sha512-JVa5ijo+j/sOoHGjw0sxw734b1LhBkQ3bvUGNdxnVXDCX81Yx7TFgnZygxrIIWn23hbfTaMYLwRmAxFyDuFmIw== - -type-check@^0.4.0, type-check@~0.4.0: - version "0.4.0" - resolved "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" - integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== - dependencies: - prelude-ls "^1.2.1" - -type-fest@^0.13.1: - version "0.13.1" - resolved "https://registry.npmmirror.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934" - integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg== - -type-fest@^0.20.2: - version "0.20.2" - resolved "https://registry.npmmirror.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" - integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== - -type@^1.0.1: - version "1.2.0" - resolved "https://registry.npmmirror.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" - integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== - -type@^2.7.2: - version "2.7.2" - resolved "https://registry.npmmirror.com/type/-/type-2.7.2.tgz#2376a15a3a28b1efa0f5350dcf72d24df6ef98d0" - integrity sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw== - -typed-array-buffer@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz#18de3e7ed7974b0a729d3feecb94338d1472cd60" - integrity sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.2.1" - is-typed-array "^1.1.10" - -typed-array-byte-length@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz#d787a24a995711611fb2b87a4052799517b230d0" - integrity sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA== - dependencies: - call-bind "^1.0.2" - for-each "^0.3.3" - has-proto "^1.0.1" - is-typed-array "^1.1.10" - -typed-array-byte-offset@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz#cbbe89b51fdef9cd6aaf07ad4707340abbc4ea0b" - integrity sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg== - dependencies: - available-typed-arrays "^1.0.5" - call-bind "^1.0.2" - for-each "^0.3.3" - has-proto "^1.0.1" - is-typed-array "^1.1.10" - -typed-array-length@^1.0.4: - version "1.0.4" - resolved "https://registry.npmmirror.com/typed-array-length/-/typed-array-length-1.0.4.tgz#89d83785e5c4098bec72e08b319651f0eac9c1bb" - integrity sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng== - dependencies: - call-bind "^1.0.2" - for-each "^0.3.3" - is-typed-array "^1.1.9" - -typescript@^5.0.3: - version "5.1.6" - resolved "https://registry.npmmirror.com/typescript/-/typescript-5.1.6.tgz#02f8ac202b6dad2c0dd5e0913745b47a37998274" - integrity sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA== - -umi-request@^1.4.0: - version "1.4.0" - resolved "https://registry.npmmirror.com/umi-request/-/umi-request-1.4.0.tgz#ed0e54e47f043d2be06e691477f0890383f9dd8a" - integrity sha512-OknwtQZddZHi0Ggi+Vr/olJ7HNMx4AzlywyK0W3NZBT7B0stjeZ9lcztA85dBgdAj3KVk8uPJPZSnGaDjELhrA== - dependencies: - isomorphic-fetch "^2.2.1" - qs "^6.9.1" - -umi@^4.0.87: - version "4.0.87" - resolved "https://registry.npmmirror.com/umi/-/umi-4.0.87.tgz#107e8875de827e4b0cda45ff2f9ebf54af85ba71" - integrity sha512-DzdO+aPSYHBMmST5bLk3MhakpY//8M+o1shQqPam1yAkj3ugHTMpj7+l948U6tb3iFH+tAGj+vvRuR9uHfEUEg== - dependencies: - "@babel/runtime" "7.23.2" - "@umijs/bundler-utils" "4.0.87" - "@umijs/bundler-webpack" "4.0.87" - "@umijs/core" "4.0.87" - "@umijs/lint" "4.0.87" - "@umijs/preset-umi" "4.0.87" - "@umijs/renderer-react" "4.0.87" - "@umijs/server" "4.0.87" - "@umijs/test" "4.0.87" - "@umijs/utils" "4.0.87" - prettier-plugin-organize-imports "^3.2.2" - prettier-plugin-packagejson "2.4.3" - -unbox-primitive@^1.0.2: - version "1.0.2" - resolved "https://registry.npmmirror.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" - integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== - dependencies: - call-bind "^1.0.2" - has-bigints "^1.0.2" - has-symbols "^1.0.3" - which-boxed-primitive "^1.0.2" - -unfetch@^5.0.0: - version "5.0.0" - resolved "https://registry.npmmirror.com/unfetch/-/unfetch-5.0.0.tgz#8a5b6e5779ebe4dde0049f7d7a81d4a1af99d142" - integrity sha512-3xM2c89siXg0nHvlmYsQ2zkLASvVMBisZm5lF3gFDqfF2xonNStDJyMpvaOBe0a1Edxmqrf2E0HBdmy9QyZaeg== - -unicode-canonical-property-names-ecmascript@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc" - integrity sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ== - -unicode-match-property-ecmascript@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz#54fd16e0ecb167cf04cf1f756bdcc92eba7976c3" - integrity sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q== - dependencies: - unicode-canonical-property-names-ecmascript "^2.0.0" - unicode-property-aliases-ecmascript "^2.0.0" - -unicode-match-property-value-ecmascript@^2.1.0: - version "2.1.0" - resolved "https://registry.npmmirror.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz#cb5fffdcd16a05124f5a4b0bf7c3770208acbbe0" - integrity sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA== - -unicode-property-aliases-ecmascript@^2.0.0: - version "2.1.0" - resolved "https://registry.npmmirror.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd" - integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== - -universalify@^0.1.0: - version "0.1.2" - resolved "https://registry.npmmirror.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" - integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== - -universalify@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" - integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== - -untildify@^4.0.0: - version "4.0.0" - resolved "https://registry.npmmirror.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" - integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== - -update-browserslist-db@^1.0.11: - version "1.0.11" - resolved "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz#9a2a641ad2907ae7b3616506f4b977851db5b940" - integrity sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA== - dependencies: - escalade "^3.1.1" - picocolors "^1.0.0" - -uri-js@^4.2.2: - version "4.4.1" - resolved "https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" - integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== - dependencies: - punycode "^2.1.0" - -url@^0.11.0: - version "0.11.1" - resolved "https://registry.npmmirror.com/url/-/url-0.11.1.tgz#26f90f615427eca1b9f4d6a28288c147e2302a32" - integrity sha512-rWS3H04/+mzzJkv0eZ7vEDGiQbgquI1fGfOad6zKvgYQi1SzMmhl7c/DdRGxhaWrVH6z0qWITo8rpnxK/RfEhA== - dependencies: - punycode "^1.4.1" - qs "^6.11.0" - -use-isomorphic-layout-effect@^1.1.1: - version "1.1.2" - resolved "https://registry.npmmirror.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz#497cefb13d863d687b08477d9e5a164ad8c1a6fb" - integrity sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA== - -use-json-comparison@^1.0.3, use-json-comparison@^1.0.5: - version "1.0.6" - resolved "https://registry.npmmirror.com/use-json-comparison/-/use-json-comparison-1.0.6.tgz#a012bbc258ce745db1f56745dc653f575226cb21" - integrity sha512-xPadt5yMRbEmVfOSGFSMqjjICrq7nLbfSH3rYIXsrtcuFX7PmbYDN/ku8ObBn3v8o/yZelO1OxUS5+5TI3+fUw== - -use-media-antd-query@^1.1.0: - version "1.1.0" - resolved "https://registry.npmmirror.com/use-media-antd-query/-/use-media-antd-query-1.1.0.tgz#f083ad7e292c1c0261b6bbfaac0edc3e0920d85d" - integrity sha512-B6kKZwNV4R+l4Rl11sWO7HqOay9alzs1Vp1b4YJqjz33YxbltBCZtt/yxXxkXN9rc1S7OeEL/GbwC30Wmqhw6Q== - -use-sync-external-store@1.2.0, use-sync-external-store@^1.0.0, use-sync-external-store@^1.2.0: - version "1.2.0" - resolved "https://registry.npmmirror.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" - integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== - -utf8-byte-length@^1.0.1: - version "1.0.4" - resolved "https://registry.npmmirror.com/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz#f45f150c4c66eee968186505ab93fcbb8ad6bf61" - integrity sha512-4+wkEYLBbWxqTahEsWrhxepcoVOJ+1z5PGIjPZxRkytcdSUaNjIjBM7Xn8E+pdSuV7SzvWovBFA54FO0JSoqhA== - -util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== - -util@0.10.3: - version "0.10.3" - resolved "https://registry.npmmirror.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" - integrity sha512-5KiHfsmkqacuKjkRkdV7SsfDJ2EGiPsK92s2MhNSY0craxjTdKTtqKsJaCWp4LW33ZZ0OPUv1WO/TFvNQRiQxQ== - dependencies: - inherits "2.0.1" - -util@^0.11.0: - version "0.11.1" - resolved "https://registry.npmmirror.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61" - integrity sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ== - dependencies: - inherits "2.0.3" - -utila@~0.4: - version "0.4.0" - resolved "https://registry.npmmirror.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" - integrity sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA== - -uuid@^9.0.0: - version "9.0.0" - resolved "https://registry.npmmirror.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5" - integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg== - -valtio@1.9.0: - version "1.9.0" - resolved "https://registry.npmmirror.com/valtio/-/valtio-1.9.0.tgz#d5d9f664319eaf18dd98f758d50495eca28eb0b8" - integrity sha512-mQLFsAlKbYascZygFQh6lXuDjU5WHLoeZ8He4HqMnWfasM96V6rDbeFkw1XeG54xycmDonr/Jb4xgviHtuySrA== - dependencies: - proxy-compare "2.4.0" - use-sync-external-store "1.2.0" - -vary@^1: - version "1.1.2" - resolved "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== - -verror@^1.10.0: - version "1.10.1" - resolved "https://registry.npmmirror.com/verror/-/verror-1.10.1.tgz#4bf09eeccf4563b109ed4b3d458380c972b0cdeb" - integrity sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg== - dependencies: - assert-plus "^1.0.0" - core-util-is "1.0.2" - extsprintf "^1.2.0" - -vite@4.3.1: - version "4.3.1" - resolved "https://registry.npmmirror.com/vite/-/vite-4.3.1.tgz#9badb1377f995632cdcf05f32103414db6fbb95a" - integrity sha512-EPmfPLAI79Z/RofuMvkIS0Yr091T2ReUoXQqc5ppBX/sjFRhHKiPPF/R46cTdoci/XgeQpB23diiJxq5w30vdg== - dependencies: - esbuild "^0.17.5" - postcss "^8.4.21" - rollup "^3.20.2" - optionalDependencies: - fsevents "~2.3.2" - -vm-browserify@^1.0.1: - version "1.1.2" - resolved "https://registry.npmmirror.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" - integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== - -walker@^1.0.8: - version "1.0.8" - resolved "https://registry.npmmirror.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" - integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== - dependencies: - makeerror "1.0.12" - -warning@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/warning/-/warning-3.0.0.tgz#32e5377cb572de4ab04753bdf8821c01ed605b7c" - integrity sha512-jMBt6pUrKn5I+OGgtQ4YZLdhIeJmObddh6CsibPxyQ5yPZm1XExSyzC1LCNX7BzhxWgiHmizBWJTHJIjMjTQYQ== - dependencies: - loose-envify "^1.0.0" - -warning@^4.0.3: - version "4.0.3" - resolved "https://registry.npmmirror.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" - integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w== - dependencies: - loose-envify "^1.0.0" - -wbuf@^1.1.0, wbuf@^1.7.3: - version "1.7.3" - resolved "https://registry.npmmirror.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" - integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA== - dependencies: - minimalistic-assert "^1.0.0" - -web-streams-polyfill@^3.0.3: - version "3.2.1" - resolved "https://registry.npmmirror.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6" - integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q== - -whatwg-fetch@>=0.10.0: - version "3.6.16" - resolved "https://registry.npmmirror.com/whatwg-fetch/-/whatwg-fetch-3.6.16.tgz#2cf24cd621459be8137f9e3c6afb60262d78f963" - integrity sha512-83avoGbZ0qtjtNrU3UTT3/Xd3uZ7DyfSYLuc1fL5iYs+93P+UkIVF6/6xpRVWeQcvbc7kSnVybSAVbd6QFW5Fg== - -which-boxed-primitive@^1.0.2: - version "1.0.2" - resolved "https://registry.npmmirror.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" - integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== - dependencies: - is-bigint "^1.0.1" - is-boolean-object "^1.1.0" - is-number-object "^1.0.4" - is-string "^1.0.5" - is-symbol "^1.0.3" - -which-builtin-type@^1.1.3: - version "1.1.3" - resolved "https://registry.npmmirror.com/which-builtin-type/-/which-builtin-type-1.1.3.tgz#b1b8443707cc58b6e9bf98d32110ff0c2cbd029b" - integrity sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw== - dependencies: - function.prototype.name "^1.1.5" - has-tostringtag "^1.0.0" - is-async-function "^2.0.0" - is-date-object "^1.0.5" - is-finalizationregistry "^1.0.2" - is-generator-function "^1.0.10" - is-regex "^1.1.4" - is-weakref "^1.0.2" - isarray "^2.0.5" - which-boxed-primitive "^1.0.2" - which-collection "^1.0.1" - which-typed-array "^1.1.9" - -which-collection@^1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/which-collection/-/which-collection-1.0.1.tgz#70eab71ebbbd2aefaf32f917082fc62cdcb70906" - integrity sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A== - dependencies: - is-map "^2.0.1" - is-set "^2.0.1" - is-weakmap "^2.0.1" - is-weakset "^2.0.1" - -which-typed-array@^1.1.10, which-typed-array@^1.1.11, which-typed-array@^1.1.9: - version "1.1.11" - resolved "https://registry.npmmirror.com/which-typed-array/-/which-typed-array-1.1.11.tgz#99d691f23c72aab6768680805a271b69761ed61a" - integrity sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew== - dependencies: - available-typed-arrays "^1.0.5" - call-bind "^1.0.2" - for-each "^0.3.3" - gopd "^1.0.1" - has-tostringtag "^1.0.0" - -which@^2.0.1: - version "2.0.2" - resolved "https://registry.npmmirror.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== - -write-file-atomic@^4.0.2: - version "4.0.2" - resolved "https://registry.npmmirror.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" - integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== - dependencies: - imurmurhash "^0.1.4" - signal-exit "^3.0.7" - -xmlbuilder@>=11.0.1, xmlbuilder@^15.1.1: - version "15.1.1" - resolved "https://registry.npmmirror.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz#9dcdce49eea66d8d10b42cae94a79c3c8d0c2ec5" - integrity sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg== - -xtend@^4.0.0: - version "4.0.2" - resolved "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" - integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== - -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - -yallist@^3.0.2: - version "3.1.1" - resolved "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" - integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== - -yallist@^4.0.0: - version "4.0.0" - resolved "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" - integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== - -yaml@^1.10.0: - version "1.10.2" - resolved "https://registry.npmmirror.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" - integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== - -yaml@^2.1.1: - version "2.3.1" - resolved "https://registry.npmmirror.com/yaml/-/yaml-2.3.1.tgz#02fe0975d23cd441242aa7204e09fc28ac2ac33b" - integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ== - -yargs-parser@^21.1.1: - version "21.1.1" - resolved "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" - integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== - -yargs@^17.5.1, yargs@^17.7.2: - version "17.7.2" - resolved "https://registry.npmmirror.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" - integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== - dependencies: - cliui "^8.0.1" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.3" - y18n "^5.0.5" - yargs-parser "^21.1.1" - -yauzl@^2.10.0: - version "2.10.0" - resolved "https://registry.npmmirror.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" - integrity sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g== - dependencies: - buffer-crc32 "~0.2.3" - fd-slicer "~1.1.0" - -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== - -zrender@5.4.4: - version "5.4.4" - resolved "https://registry.npmmirror.com/zrender/-/zrender-5.4.4.tgz#8854f1d95ecc82cf8912f5a11f86657cb8c9e261" - integrity sha512-0VxCNJ7AGOMCWeHVyTrGzUgrK4asT4ml9PEkeGirAkKNYXYzoPJCLvmyfdoOXcjTHPs10OZVMfD1Rwg16AZyYw== - dependencies: - tslib "2.3.0" - -zustand@^4.4.4: - version "4.4.4" - resolved "https://registry.npmmirror.com/zustand/-/zustand-4.4.4.tgz#cc06202219972bd61cef1fd10105e6384ae1d5cf" - integrity sha512-5UTUIAiHMNf5+mFp7/AnzJXS7+XxktULFN0+D1sCiZWyX7ZG+AQpqs2qpYrynRij4QvoDdCD+U+bmg/cG3Ucxw== - dependencies: - use-sync-external-store "1.2.0" +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 8 + cacheKey: 10c0 + +"7zip-bin@npm:~5.1.1": + version: 5.1.1 + resolution: "7zip-bin@npm:5.1.1" + checksum: 10c0/528db0d93d8a1de62e12624570f49c733e707606602a48be211b2186b6453903b61c659bcc919bfbb021029157af06f43d5017b628fff2a1e66d0190b26eea0e + languageName: node + linkType: hard + +"@ahooksjs/use-request@npm:^2.0.0": + version: 2.8.15 + resolution: "@ahooksjs/use-request@npm:2.8.15" + dependencies: + lodash.debounce: "npm:^4.0.8" + lodash.throttle: "npm:^4.1.1" + peerDependencies: + react: ^16.8.0 || ^17.0.0 + checksum: 10c0/2520942e92eabdc47b9f2162cf7ed0a478e92ef540de69dc8deb09bb5c57c4fd55240d03779fd1c0a7512fd1ec313a8a480272d0212eb81dd4808a89207f420b + languageName: node + linkType: hard + +"@alloc/quick-lru@npm:^5.2.0": + version: 5.2.0 + resolution: "@alloc/quick-lru@npm:5.2.0" + checksum: 10c0/7b878c48b9d25277d0e1a9b8b2f2312a314af806b4129dc902f2bc29ab09b58236e53964689feec187b28c80d2203aff03829754773a707a8a5987f1b7682d92 + languageName: node + linkType: hard + +"@ampproject/remapping@npm:^2.2.0": + version: 2.3.0 + resolution: "@ampproject/remapping@npm:2.3.0" + dependencies: + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/81d63cca5443e0f0c72ae18b544cc28c7c0ec2cea46e7cb888bb0e0f411a1191d0d6b7af798d54e30777d8d1488b2ec0732aac2be342d3d7d3ffd271c6f489ed + languageName: node + linkType: hard + +"@ant-design/antd-theme-variable@npm:^1.0.0": + version: 1.0.0 + resolution: "@ant-design/antd-theme-variable@npm:1.0.0" + checksum: 10c0/9f621c480ff0147c9dc312aa3e4796781cd03983cd5c6654cd773f17573277d0ad27b742fd7c95068e12c77bdc5443d1a829889958fe572015ef281fef538539 + languageName: node + linkType: hard + +"@ant-design/colors@npm:^6.0.0": + version: 6.0.0 + resolution: "@ant-design/colors@npm:6.0.0" + dependencies: + "@ctrl/tinycolor": "npm:^3.4.0" + checksum: 10c0/4ff06fc0d0f9d28edb0c5d500c3cf6f31dbdd125c9224e3f99312eaea298c8513c4975902e7bd867c4cf53a3594febe05fa12cb80f640ba37097d0853c144f83 + languageName: node + linkType: hard + +"@ant-design/colors@npm:^7.0.0, @ant-design/colors@npm:^7.2.1": + version: 7.2.1 + resolution: "@ant-design/colors@npm:7.2.1" + dependencies: + "@ant-design/fast-color": "npm:^2.0.6" + checksum: 10c0/4748a0bfb1ea98e08e29dcd4f7afd2781ae2119f783e6e9f80e889fd15fc19f7137e2a3d91f26bae2ab1ee76c04d520cc35f2bb0a708cd71e463f4d9deb4192d + languageName: node + linkType: hard + +"@ant-design/cssinjs-utils@npm:^1.1.3": + version: 1.1.3 + resolution: "@ant-design/cssinjs-utils@npm:1.1.3" + dependencies: + "@ant-design/cssinjs": "npm:^1.21.0" + "@babel/runtime": "npm:^7.23.2" + rc-util: "npm:^5.38.0" + peerDependencies: + react: ">=16.9.0" + react-dom: ">=16.9.0" + checksum: 10c0/e8a443a613689c4e984f5cf44b799f6288a6debf9a35cafb27a0411ef77ae335ba5ace7a38efd8e04f04ac897ddf24c81f0ad6615ac586f2616cb7f2b72f6176 + languageName: node + linkType: hard + +"@ant-design/cssinjs@npm:^1.21.0, @ant-design/cssinjs@npm:^1.21.1, @ant-design/cssinjs@npm:^1.23.0, @ant-design/cssinjs@npm:^1.9.1": + version: 1.24.0 + resolution: "@ant-design/cssinjs@npm:1.24.0" + dependencies: + "@babel/runtime": "npm:^7.11.1" + "@emotion/hash": "npm:^0.8.0" + "@emotion/unitless": "npm:^0.7.5" + classnames: "npm:^2.3.1" + csstype: "npm:^3.1.3" + rc-util: "npm:^5.35.0" + stylis: "npm:^4.3.4" + peerDependencies: + react: ">=16.0.0" + react-dom: ">=16.0.0" + checksum: 10c0/e84bc33bd74d386f87813641287ad3ba7494adcde944277bbec3745ea14cc19bb430c0723d8e86058d23c450a4c22e7fe63281e0d5e50d3a0e94ea55185305b4 + languageName: node + linkType: hard + +"@ant-design/fast-color@npm:^2.0.6": + version: 2.0.6 + resolution: "@ant-design/fast-color@npm:2.0.6" + dependencies: + "@babel/runtime": "npm:^7.24.7" + checksum: 10c0/8d30649bd8d4e56d5c48393fcf0ad5c24d1099ec4cbf88f55bd9f4489e61efc30087d301da384c4ed21f2d5597087c8ba27dfbcc7693915310c26d307f5a8276 + languageName: node + linkType: hard + +"@ant-design/icons-svg@npm:^4.3.0, @ant-design/icons-svg@npm:^4.4.0": + version: 4.4.2 + resolution: "@ant-design/icons-svg@npm:4.4.2" + checksum: 10c0/d08f051824599850efcd691a67b0ee602ee886f23fe04e77890b083a0343cde72560317e3909fd029f999df00aef7b57142c863326fff7293251d9162828079b + languageName: node + linkType: hard + +"@ant-design/icons@npm:^4.7.0": + version: 4.8.3 + resolution: "@ant-design/icons@npm:4.8.3" + dependencies: + "@ant-design/colors": "npm:^6.0.0" + "@ant-design/icons-svg": "npm:^4.3.0" + "@babel/runtime": "npm:^7.11.2" + classnames: "npm:^2.2.6" + lodash: "npm:^4.17.15" + rc-util: "npm:^5.9.4" + peerDependencies: + react: ">=16.0.0" + react-dom: ">=16.0.0" + checksum: 10c0/1707c68b0c2b4d7679e1c0b9fba52ab91acbd720ff5b352b18c3574f7946002ae972772605f25ab30aaae29c293a999b361ac78747cd644895bc6921c3f3a195 + languageName: node + linkType: hard + +"@ant-design/icons@npm:^5.0.0, @ant-design/icons@npm:^5.6.1": + version: 5.6.1 + resolution: "@ant-design/icons@npm:5.6.1" + dependencies: + "@ant-design/colors": "npm:^7.0.0" + "@ant-design/icons-svg": "npm:^4.4.0" + "@babel/runtime": "npm:^7.24.8" + classnames: "npm:^2.2.6" + rc-util: "npm:^5.31.1" + peerDependencies: + react: ">=16.0.0" + react-dom: ">=16.0.0" + checksum: 10c0/7a9d9fd388c5c66d92818fd0eb794a54ef0b0dc3d75f15ac24c7cfde21c5c836c220cf35423768fd1faa28d55443a6917bf4260469c1485be83f799daa351976 + languageName: node + linkType: hard + +"@ant-design/moment-webpack-plugin@npm:^0.0.3": + version: 0.0.3 + resolution: "@ant-design/moment-webpack-plugin@npm:0.0.3" + checksum: 10c0/479697a134b4911680d32c9aaf7f9bb00bfcf6ca34cbe9c44a63e72abbab5033ba629ebc89c5d93b874c56b2b2bf175cc23d3f3285e12e393d07c9dee4e4aac4 + languageName: node + linkType: hard + +"@ant-design/pro-card@npm:2.10.0": + version: 2.10.0 + resolution: "@ant-design/pro-card@npm:2.10.0" + dependencies: + "@ant-design/cssinjs": "npm:^1.21.1" + "@ant-design/icons": "npm:^5.0.0" + "@ant-design/pro-provider": "npm:2.16.2" + "@ant-design/pro-utils": "npm:2.18.0" + "@babel/runtime": "npm:^7.18.0" + classnames: "npm:^2.3.2" + rc-resize-observer: "npm:^1.0.0" + rc-util: "npm:^5.4.0" + peerDependencies: + antd: ^4.24.15 || ^5.11.2 + react: ">=17.0.0" + checksum: 10c0/f881441f03f8daaf916ada0c0cf7e4260902fef8a1ef50992d3a0615a38d0e0fe5d168d1eb70be705091fed339929a0903a3a3aa26b1bca75cbae314278ee176 + languageName: node + linkType: hard + +"@ant-design/pro-components@npm:^2.0.1": + version: 2.8.10 + resolution: "@ant-design/pro-components@npm:2.8.10" + dependencies: + "@ant-design/pro-card": "npm:2.10.0" + "@ant-design/pro-descriptions": "npm:2.6.10" + "@ant-design/pro-field": "npm:3.1.0" + "@ant-design/pro-form": "npm:2.32.0" + "@ant-design/pro-layout": "npm:7.22.7" + "@ant-design/pro-list": "npm:2.6.10" + "@ant-design/pro-provider": "npm:2.16.2" + "@ant-design/pro-skeleton": "npm:2.2.1" + "@ant-design/pro-table": "npm:3.21.0" + "@ant-design/pro-utils": "npm:2.18.0" + "@babel/runtime": "npm:^7.16.3" + peerDependencies: + antd: ^4.24.15 || ^5.11.2 + react: ">=17.0.0" + react-dom: ">=17.0.0" + checksum: 10c0/0c68d5b05db3c64fccf5ff91c8f904a640f80299de8c56dcbd01494ab821a864e725bc0b3d48863837a72f5a1ff10f04d52101cf44f37bc4fd1678bc293f76c8 + languageName: node + linkType: hard + +"@ant-design/pro-descriptions@npm:2.6.10": + version: 2.6.10 + resolution: "@ant-design/pro-descriptions@npm:2.6.10" + dependencies: + "@ant-design/pro-field": "npm:3.1.0" + "@ant-design/pro-form": "npm:2.32.0" + "@ant-design/pro-provider": "npm:2.16.2" + "@ant-design/pro-skeleton": "npm:2.2.1" + "@ant-design/pro-utils": "npm:2.18.0" + "@babel/runtime": "npm:^7.18.0" + rc-resize-observer: "npm:^0.2.3" + rc-util: "npm:^5.0.6" + peerDependencies: + antd: ^4.24.15 || ^5.11.2 + react: ">=17.0.0" + checksum: 10c0/fe5c019536f3281ff24b89ca5e1a96f5e7bf6f3013614dd26ca8d7e40e035da34800dbfb5342bec5670f2a1994d1d26f97604a40e8fcf29d5bebd6f6ee7d8139 + languageName: node + linkType: hard + +"@ant-design/pro-field@npm:3.1.0": + version: 3.1.0 + resolution: "@ant-design/pro-field@npm:3.1.0" + dependencies: + "@ant-design/icons": "npm:^5.0.0" + "@ant-design/pro-provider": "npm:2.16.2" + "@ant-design/pro-utils": "npm:2.18.0" + "@babel/runtime": "npm:^7.18.0" + "@chenshuai2144/sketch-color": "npm:^1.0.8" + classnames: "npm:^2.3.2" + dayjs: "npm:^1.11.10" + lodash: "npm:^4.17.21" + lodash-es: "npm:^4.17.21" + rc-util: "npm:^5.4.0" + swr: "npm:^2.0.0" + peerDependencies: + antd: ^4.24.15 || ^5.11.2 + react: ">=17.0.0" + checksum: 10c0/4588d2fc82360bdfd135805db3a1c616d1af9f83c2c7c573cf475838788fa600e64ccaa97962f54ed9ec411c8c090c032d71bea617c4e28e34903fcd4abc08c7 + languageName: node + linkType: hard + +"@ant-design/pro-form@npm:2.32.0": + version: 2.32.0 + resolution: "@ant-design/pro-form@npm:2.32.0" + dependencies: + "@ant-design/icons": "npm:^5.0.0" + "@ant-design/pro-field": "npm:3.1.0" + "@ant-design/pro-provider": "npm:2.16.2" + "@ant-design/pro-utils": "npm:2.18.0" + "@babel/runtime": "npm:^7.18.0" + "@chenshuai2144/sketch-color": "npm:^1.0.7" + "@umijs/use-params": "npm:^1.0.9" + classnames: "npm:^2.3.2" + dayjs: "npm:^1.11.10" + lodash: "npm:^4.17.21" + lodash-es: "npm:^4.17.21" + rc-resize-observer: "npm:^1.1.0" + rc-util: "npm:^5.0.6" + peerDependencies: + antd: ^4.24.15 || ^5.11.2 + rc-field-form: ">=1.22.0" + react: ">=17.0.0" + react-dom: ">=17.0.0" + checksum: 10c0/598e8cc8bfd2dbbcd39d9013ba8ac89a84cff29b1f178e1dabd5095e7eb39509dc077af972432caba9ae4d8393d1b5af0587c4191d8c72df529c7266451256f0 + languageName: node + linkType: hard + +"@ant-design/pro-layout@npm:7.22.7": + version: 7.22.7 + resolution: "@ant-design/pro-layout@npm:7.22.7" + dependencies: + "@ant-design/cssinjs": "npm:^1.21.1" + "@ant-design/icons": "npm:^5.0.0" + "@ant-design/pro-provider": "npm:2.16.2" + "@ant-design/pro-utils": "npm:2.18.0" + "@babel/runtime": "npm:^7.18.0" + "@umijs/route-utils": "npm:^4.0.0" + "@umijs/use-params": "npm:^1.0.9" + classnames: "npm:^2.3.2" + lodash: "npm:^4.17.21" + lodash-es: "npm:^4.17.21" + path-to-regexp: "npm:8.2.0" + rc-resize-observer: "npm:^1.1.0" + rc-util: "npm:^5.0.6" + swr: "npm:^2.0.0" + warning: "npm:^4.0.3" + peerDependencies: + antd: ^4.24.15 || ^5.11.2 + react: ">=17.0.0" + react-dom: ">=17.0.0" + checksum: 10c0/28336fded9b38debf2456555447d3e5a048907d5dedf7393e40a86f82396cf9cd9edc35755841f57d952419a5a8b39a6dcecf004a5ed9a001422cd4a9feb8667 + languageName: node + linkType: hard + +"@ant-design/pro-list@npm:2.6.10": + version: 2.6.10 + resolution: "@ant-design/pro-list@npm:2.6.10" + dependencies: + "@ant-design/cssinjs": "npm:^1.21.1" + "@ant-design/icons": "npm:^5.0.0" + "@ant-design/pro-card": "npm:2.10.0" + "@ant-design/pro-field": "npm:3.1.0" + "@ant-design/pro-table": "npm:3.21.0" + "@ant-design/pro-utils": "npm:2.18.0" + "@babel/runtime": "npm:^7.18.0" + classnames: "npm:^2.3.2" + dayjs: "npm:^1.11.10" + rc-resize-observer: "npm:^1.0.0" + rc-util: "npm:^4.19.0" + peerDependencies: + antd: ^4.24.15 || ^5.11.2 + react: ">=17.0.0" + react-dom: ">=17.0.0" + checksum: 10c0/335a19d0818ee4fa6ba7d0b18422978f873f2051de9f492001a06167e1999e46791a579703209514e064c2713a8962d9324a8decf2af38e2037804dfa9fe5ca7 + languageName: node + linkType: hard + +"@ant-design/pro-provider@npm:2.16.2": + version: 2.16.2 + resolution: "@ant-design/pro-provider@npm:2.16.2" + dependencies: + "@ant-design/cssinjs": "npm:^1.21.1" + "@babel/runtime": "npm:^7.18.0" + "@ctrl/tinycolor": "npm:^3.4.0" + dayjs: "npm:^1.11.10" + rc-util: "npm:^5.0.1" + swr: "npm:^2.0.0" + peerDependencies: + antd: ^4.24.15 || ^5.11.2 + react: ">=17.0.0" + react-dom: ">=17.0.0" + checksum: 10c0/abc8dbddb6f6cd4f4cf73d10f7c66b673738776001fc5276855a64d19646b4133d60ce586d432dc49267ff66632698f632216b20d5a6f8e3e825c3122715006b + languageName: node + linkType: hard + +"@ant-design/pro-skeleton@npm:2.2.1": + version: 2.2.1 + resolution: "@ant-design/pro-skeleton@npm:2.2.1" + dependencies: + "@babel/runtime": "npm:^7.18.0" + peerDependencies: + antd: ^4.24.15 || ^5.11.2 + react: ">=17.0.0" + react-dom: ">=17.0.0" + checksum: 10c0/76b7a0ddf4f75c14d7ea2b3cc6d5ee86d40d64b8d73790c17fd51b0a5082f020f8ea38b63053d38352ea8223fd3bfbbe31749af495fa56b166613c0b6d080b15 + languageName: node + linkType: hard + +"@ant-design/pro-table@npm:3.21.0": + version: 3.21.0 + resolution: "@ant-design/pro-table@npm:3.21.0" + dependencies: + "@ant-design/cssinjs": "npm:^1.21.1" + "@ant-design/icons": "npm:^5.0.0" + "@ant-design/pro-card": "npm:2.10.0" + "@ant-design/pro-field": "npm:3.1.0" + "@ant-design/pro-form": "npm:2.32.0" + "@ant-design/pro-provider": "npm:2.16.2" + "@ant-design/pro-utils": "npm:2.18.0" + "@babel/runtime": "npm:^7.18.0" + "@dnd-kit/core": "npm:^6.0.8" + "@dnd-kit/modifiers": "npm:^6.0.1" + "@dnd-kit/sortable": "npm:^7.0.2" + "@dnd-kit/utilities": "npm:^3.2.1" + classnames: "npm:^2.3.2" + dayjs: "npm:^1.11.10" + lodash: "npm:^4.17.21" + lodash-es: "npm:^4.17.21" + rc-resize-observer: "npm:^1.0.0" + rc-util: "npm:^5.0.1" + peerDependencies: + antd: ^4.24.15 || ^5.11.2 + rc-field-form: ">=1.22.0" + react: ">=17.0.0" + react-dom: ">=17.0.0" + checksum: 10c0/552132d18c393fa2e967d81281b53a58f1939c0205da43616f7aa2838ada7b947766f432215f5e539c7a7eb0a8661516105322fb55374cb62fd1811c6001726c + languageName: node + linkType: hard + +"@ant-design/pro-utils@npm:2.18.0": + version: 2.18.0 + resolution: "@ant-design/pro-utils@npm:2.18.0" + dependencies: + "@ant-design/icons": "npm:^5.0.0" + "@ant-design/pro-provider": "npm:2.16.2" + "@babel/runtime": "npm:^7.18.0" + classnames: "npm:^2.3.2" + dayjs: "npm:^1.11.10" + lodash: "npm:^4.17.21" + lodash-es: "npm:^4.17.21" + rc-util: "npm:^5.0.6" + safe-stable-stringify: "npm:^2.4.3" + swr: "npm:^2.0.0" + peerDependencies: + antd: ^4.24.15 || ^5.11.2 + react: ">=17.0.0" + react-dom: ">=17.0.0" + checksum: 10c0/6f87970394045837bb1e0ca93682a522305f0eddafeec038f31746735c7d1da7c296c32007806d44eccddd64e4db383a63e6990ff83c48b5f2c10898fb459606 + languageName: node + linkType: hard + +"@ant-design/react-slick@npm:~1.1.2": + version: 1.1.2 + resolution: "@ant-design/react-slick@npm:1.1.2" + dependencies: + "@babel/runtime": "npm:^7.10.4" + classnames: "npm:^2.2.5" + json2mq: "npm:^0.2.0" + resize-observer-polyfill: "npm:^1.5.1" + throttle-debounce: "npm:^5.0.0" + peerDependencies: + react: ">=16.9.0" + checksum: 10c0/4f758e28cf8418e9f1a9b03da3814b2342fcee8a4039cae2fe6f77c01e9c8b7ea78a7e10961128a5ccba4992f520e88cb72c4f7fa1bd22314ce628b0d9fb3f5c + languageName: node + linkType: hard + +"@antfu/install-pkg@npm:^0.1.1": + version: 0.1.1 + resolution: "@antfu/install-pkg@npm:0.1.1" + dependencies: + execa: "npm:^5.1.1" + find-up: "npm:^5.0.0" + checksum: 10c0/ae3116cc0918765ad356901b9c8825340be27deac03eb4c8969377eab9731a3b41d96e920fa0b08adf91fba27a808d08c68852b110775ff79ba40481422cc8ba + languageName: node + linkType: hard + +"@antfu/utils@npm:^0.7.2": + version: 0.7.10 + resolution: "@antfu/utils@npm:0.7.10" + checksum: 10c0/98991f66a4752ef097280b4235b27d961a13a2c67ef8e5b716a120eb9823958e20566516711204e2bfb08f0b935814b715f49ecd79c3b9b93ce32747ac297752 + languageName: node + linkType: hard + +"@babel/code-frame@npm:7.22.5": + version: 7.22.5 + resolution: "@babel/code-frame@npm:7.22.5" + dependencies: + "@babel/highlight": "npm:^7.22.5" + checksum: 10c0/0b6c5eaf9e58be7140ac790b7bdf8148e8a24e26502dcaa50f157259c083b0584285748fd90d342ae311a5bb1eaad7835aec625296d2b46853464f9bd8991e28 + languageName: node + linkType: hard + +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.16.7, @babel/code-frame@npm:^7.23.5, @babel/code-frame@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/code-frame@npm:7.27.1" + dependencies: + "@babel/helper-validator-identifier": "npm:^7.27.1" + js-tokens: "npm:^4.0.0" + picocolors: "npm:^1.1.1" + checksum: 10c0/5dd9a18baa5fce4741ba729acc3a3272c49c25cb8736c4b18e113099520e7ef7b545a4096a26d600e4416157e63e87d66db46aa3fbf0a5f2286da2705c12da00 + languageName: node + linkType: hard + +"@babel/compat-data@npm:^7.27.2": + version: 7.28.5 + resolution: "@babel/compat-data@npm:7.28.5" + checksum: 10c0/702a25de73087b0eba325c1d10979eed7c9b6662677386ba7b5aa6eace0fc0676f78343bae080a0176ae26f58bd5535d73b9d0fbb547fef377692e8b249353a7 + languageName: node + linkType: hard + +"@babel/core@npm:7.23.6": + version: 7.23.6 + resolution: "@babel/core@npm:7.23.6" + dependencies: + "@ampproject/remapping": "npm:^2.2.0" + "@babel/code-frame": "npm:^7.23.5" + "@babel/generator": "npm:^7.23.6" + "@babel/helper-compilation-targets": "npm:^7.23.6" + "@babel/helper-module-transforms": "npm:^7.23.3" + "@babel/helpers": "npm:^7.23.6" + "@babel/parser": "npm:^7.23.6" + "@babel/template": "npm:^7.22.15" + "@babel/traverse": "npm:^7.23.6" + "@babel/types": "npm:^7.23.6" + convert-source-map: "npm:^2.0.0" + debug: "npm:^4.1.0" + gensync: "npm:^1.0.0-beta.2" + json5: "npm:^2.2.3" + semver: "npm:^6.3.1" + checksum: 10c0/a02bae7d916029b70706dc301535e1b31e5d216f55d4ee6f64a15825c6b69ee2c14c52a213d1497ec414e925ed4e9d897d41fb0d75df9fea28ed2c0008790e31 + languageName: node + linkType: hard + +"@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.17.9, @babel/core@npm:^7.19.6, @babel/core@npm:^7.21.4": + version: 7.28.5 + resolution: "@babel/core@npm:7.28.5" + dependencies: + "@babel/code-frame": "npm:^7.27.1" + "@babel/generator": "npm:^7.28.5" + "@babel/helper-compilation-targets": "npm:^7.27.2" + "@babel/helper-module-transforms": "npm:^7.28.3" + "@babel/helpers": "npm:^7.28.4" + "@babel/parser": "npm:^7.28.5" + "@babel/template": "npm:^7.27.2" + "@babel/traverse": "npm:^7.28.5" + "@babel/types": "npm:^7.28.5" + "@jridgewell/remapping": "npm:^2.3.5" + convert-source-map: "npm:^2.0.0" + debug: "npm:^4.1.0" + gensync: "npm:^1.0.0-beta.2" + json5: "npm:^2.2.3" + semver: "npm:^6.3.1" + checksum: 10c0/535f82238027621da6bdffbdbe896ebad3558b311d6f8abc680637a9859b96edbf929ab010757055381570b29cf66c4a295b5618318d27a4273c0e2033925e72 + languageName: node + linkType: hard + +"@babel/eslint-parser@npm:7.23.3": + version: 7.23.3 + resolution: "@babel/eslint-parser@npm:7.23.3" + dependencies: + "@nicolo-ribaudo/eslint-scope-5-internals": "npm:5.1.1-v1" + eslint-visitor-keys: "npm:^2.1.0" + semver: "npm:^6.3.1" + peerDependencies: + "@babel/core": ^7.11.0 + eslint: ^7.5.0 || ^8.0.0 + checksum: 10c0/abb01d23acd80e983125cd72c547baaf7775bfca7a98fc57a2a95f2b70197a34c6bf861e255ab5c8740ace27c50a9966481503875fcc23b2636598740e4881f4 + languageName: node + linkType: hard + +"@babel/generator@npm:7.2.0": + version: 7.2.0 + resolution: "@babel/generator@npm:7.2.0" + dependencies: + "@babel/types": "npm:^7.2.0" + jsesc: "npm:^2.5.1" + lodash: "npm:^4.17.10" + source-map: "npm:^0.5.0" + trim-right: "npm:^1.0.1" + checksum: 10c0/cbcc4a5380976c68b1725f8e1566f0f0706464628d42931f836e1034a06e3dfffac17283ebb37cc0e5dc38db39af0aa1ed29c9c3686ea028b8e105e23cc14436 + languageName: node + linkType: hard + +"@babel/generator@npm:^7.23.6, @babel/generator@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/generator@npm:7.28.5" + dependencies: + "@babel/parser": "npm:^7.28.5" + "@babel/types": "npm:^7.28.5" + "@jridgewell/gen-mapping": "npm:^0.3.12" + "@jridgewell/trace-mapping": "npm:^0.3.28" + jsesc: "npm:^3.0.2" + checksum: 10c0/9f219fe1d5431b6919f1a5c60db8d5d34fe546c0d8f5a8511b32f847569234ffc8032beb9e7404649a143f54e15224ecb53a3d11b6bb85c3203e573d91fca752 + languageName: node + linkType: hard + +"@babel/helper-annotate-as-pure@npm:^7.22.5": + version: 7.27.3 + resolution: "@babel/helper-annotate-as-pure@npm:7.27.3" + dependencies: + "@babel/types": "npm:^7.27.3" + checksum: 10c0/94996ce0a05b7229f956033e6dcd69393db2b0886d0db6aff41e704390402b8cdcca11f61449cb4f86cfd9e61b5ad3a73e4fa661eeed7846b125bd1c33dbc633 + languageName: node + linkType: hard + +"@babel/helper-compilation-targets@npm:^7.23.6, @babel/helper-compilation-targets@npm:^7.27.2": + version: 7.27.2 + resolution: "@babel/helper-compilation-targets@npm:7.27.2" + dependencies: + "@babel/compat-data": "npm:^7.27.2" + "@babel/helper-validator-option": "npm:^7.27.1" + browserslist: "npm:^4.24.0" + lru-cache: "npm:^5.1.1" + semver: "npm:^6.3.1" + checksum: 10c0/f338fa00dcfea931804a7c55d1a1c81b6f0a09787e528ec580d5c21b3ecb3913f6cb0f361368973ce953b824d910d3ac3e8a8ee15192710d3563826447193ad1 + languageName: node + linkType: hard + +"@babel/helper-globals@npm:^7.28.0": + version: 7.28.0 + resolution: "@babel/helper-globals@npm:7.28.0" + checksum: 10c0/5a0cd0c0e8c764b5f27f2095e4243e8af6fa145daea2b41b53c0c1414fe6ff139e3640f4e2207ae2b3d2153a1abd346f901c26c290ee7cb3881dd922d4ee9232 + languageName: node + linkType: hard + +"@babel/helper-module-imports@npm:^7.0.0, @babel/helper-module-imports@npm:^7.22.5, @babel/helper-module-imports@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-module-imports@npm:7.27.1" + dependencies: + "@babel/traverse": "npm:^7.27.1" + "@babel/types": "npm:^7.27.1" + checksum: 10c0/e00aace096e4e29290ff8648455c2bc4ed982f0d61dbf2db1b5e750b9b98f318bf5788d75a4f974c151bd318fd549e81dbcab595f46b14b81c12eda3023f51e8 + languageName: node + linkType: hard + +"@babel/helper-module-transforms@npm:^7.23.3, @babel/helper-module-transforms@npm:^7.28.3": + version: 7.28.3 + resolution: "@babel/helper-module-transforms@npm:7.28.3" + dependencies: + "@babel/helper-module-imports": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.27.1" + "@babel/traverse": "npm:^7.28.3" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/549be62515a6d50cd4cfefcab1b005c47f89bd9135a22d602ee6a5e3a01f27571868ada10b75b033569f24dc4a2bb8d04bfa05ee75c16da7ade2d0db1437fcdb + languageName: node + linkType: hard + +"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.22.5, @babel/helper-plugin-utils@npm:^7.27.1, @babel/helper-plugin-utils@npm:^7.8.0": + version: 7.27.1 + resolution: "@babel/helper-plugin-utils@npm:7.27.1" + checksum: 10c0/94cf22c81a0c11a09b197b41ab488d416ff62254ce13c57e62912c85700dc2e99e555225787a4099ff6bae7a1812d622c80fbaeda824b79baa10a6c5ac4cf69b + languageName: node + linkType: hard + +"@babel/helper-simple-access@npm:^7.22.5": + version: 7.27.1 + resolution: "@babel/helper-simple-access@npm:7.27.1" + dependencies: + "@babel/traverse": "npm:^7.27.1" + "@babel/types": "npm:^7.27.1" + checksum: 10c0/ebfe55945e1d1b0dbffb7d7510a4e7b4fd42c7349b93a805700f2c8841254cba5ebb54f2457558d27b856248d30e7b33794e37c56b99d4b81a5ef34bcdc9d27f + languageName: node + linkType: hard + +"@babel/helper-string-parser@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-string-parser@npm:7.27.1" + checksum: 10c0/8bda3448e07b5583727c103560bcf9c4c24b3c1051a4c516d4050ef69df37bb9a4734a585fe12725b8c2763de0a265aa1e909b485a4e3270b7cfd3e4dbe4b602 + languageName: node + linkType: hard + +"@babel/helper-validator-identifier@npm:^7.25.9, @babel/helper-validator-identifier@npm:^7.27.1, @babel/helper-validator-identifier@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/helper-validator-identifier@npm:7.28.5" + checksum: 10c0/42aaebed91f739a41f3d80b72752d1f95fd7c72394e8e4bd7cdd88817e0774d80a432451bcba17c2c642c257c483bf1d409dd4548883429ea9493a3bc4ab0847 + languageName: node + linkType: hard + +"@babel/helper-validator-option@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-validator-option@npm:7.27.1" + checksum: 10c0/6fec5f006eba40001a20f26b1ef5dbbda377b7b68c8ad518c05baa9af3f396e780bdfded24c4eef95d14bb7b8fd56192a6ed38d5d439b97d10efc5f1a191d148 + languageName: node + linkType: hard + +"@babel/helpers@npm:^7.23.6, @babel/helpers@npm:^7.28.4": + version: 7.28.4 + resolution: "@babel/helpers@npm:7.28.4" + dependencies: + "@babel/template": "npm:^7.27.2" + "@babel/types": "npm:^7.28.4" + checksum: 10c0/aaa5fb8098926dfed5f223adf2c5e4c7fbba4b911b73dfec2d7d3083f8ba694d201a206db673da2d9b3ae8c01793e795767654558c450c8c14b4c2175b4fcb44 + languageName: node + linkType: hard + +"@babel/highlight@npm:^7.22.5": + version: 7.25.9 + resolution: "@babel/highlight@npm:7.25.9" + dependencies: + "@babel/helper-validator-identifier": "npm:^7.25.9" + chalk: "npm:^2.4.2" + js-tokens: "npm:^4.0.0" + picocolors: "npm:^1.0.0" + checksum: 10c0/ae0ed93c151b85a07df42936117fa593ce91563a22dfc8944a90ae7088c9679645c33e00dcd20b081c1979665d65f986241172dae1fc9e5922692fc3ff685a49 + languageName: node + linkType: hard + +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.6, @babel/parser@npm:^7.27.2, @babel/parser@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/parser@npm:7.28.5" + dependencies: + "@babel/types": "npm:^7.28.5" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/5bbe48bf2c79594ac02b490a41ffde7ef5aa22a9a88ad6bcc78432a6ba8a9d638d531d868bd1f104633f1f6bba9905746e15185b8276a3756c42b765d131b1ef + languageName: node + linkType: hard + +"@babel/plugin-syntax-async-generators@npm:^7.8.4": + version: 7.8.4 + resolution: "@babel/plugin-syntax-async-generators@npm:7.8.4" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.8.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/d13efb282838481348c71073b6be6245b35d4f2f964a8f71e4174f235009f929ef7613df25f8d2338e2d3e44bc4265a9f8638c6aaa136d7a61fe95985f9725c8 + languageName: node + linkType: hard + +"@babel/plugin-syntax-bigint@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-bigint@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.8.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/686891b81af2bc74c39013655da368a480f17dd237bf9fbc32048e5865cb706d5a8f65438030da535b332b1d6b22feba336da8fa931f663b6b34e13147d12dde + languageName: node + linkType: hard + +"@babel/plugin-syntax-class-properties@npm:^7.12.13": + version: 7.12.13 + resolution: "@babel/plugin-syntax-class-properties@npm:7.12.13" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.12.13" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/95168fa186416195280b1264fb18afcdcdcea780b3515537b766cb90de6ce042d42dd6a204a39002f794ae5845b02afb0fd4861a3308a861204a55e68310a120 + languageName: node + linkType: hard + +"@babel/plugin-syntax-class-static-block@npm:^7.14.5": + version: 7.14.5 + resolution: "@babel/plugin-syntax-class-static-block@npm:7.14.5" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.14.5" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/4464bf9115f4a2d02ce1454411baf9cfb665af1da53709c5c56953e5e2913745b0fcce82982a00463d6facbdd93445c691024e310b91431a1e2f024b158f6371 + languageName: node + linkType: hard + +"@babel/plugin-syntax-import-attributes@npm:^7.24.7": + version: 7.27.1 + resolution: "@babel/plugin-syntax-import-attributes@npm:7.27.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/e66f7a761b8360419bbb93ab67d87c8a97465ef4637a985ff682ce7ba6918b34b29d81190204cf908d0933058ee7b42737423cd8a999546c21b3aabad4affa9a + languageName: node + linkType: hard + +"@babel/plugin-syntax-import-meta@npm:^7.10.4": + version: 7.10.4 + resolution: "@babel/plugin-syntax-import-meta@npm:7.10.4" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.10.4" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/0b08b5e4c3128523d8e346f8cfc86824f0da2697b1be12d71af50a31aff7a56ceb873ed28779121051475010c28d6146a6bfea8518b150b71eeb4e46190172ee + languageName: node + linkType: hard + +"@babel/plugin-syntax-json-strings@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-json-strings@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.8.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/e98f31b2ec406c57757d115aac81d0336e8434101c224edd9a5c93cefa53faf63eacc69f3138960c8b25401315af03df37f68d316c151c4b933136716ed6906e + languageName: node + linkType: hard + +"@babel/plugin-syntax-jsx@npm:^7.22.5": + version: 7.27.1 + resolution: "@babel/plugin-syntax-jsx@npm:7.27.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/bc5afe6a458d5f0492c02a54ad98c5756a0c13bd6d20609aae65acd560a9e141b0876da5f358dce34ea136f271c1016df58b461184d7ae9c4321e0f98588bc84 + languageName: node + linkType: hard + +"@babel/plugin-syntax-logical-assignment-operators@npm:^7.10.4": + version: 7.10.4 + resolution: "@babel/plugin-syntax-logical-assignment-operators@npm:7.10.4" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.10.4" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/2594cfbe29411ad5bc2ad4058de7b2f6a8c5b86eda525a993959438615479e59c012c14aec979e538d60a584a1a799b60d1b8942c3b18468cb9d99b8fd34cd0b + languageName: node + linkType: hard + +"@babel/plugin-syntax-nullish-coalescing-operator@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-nullish-coalescing-operator@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.8.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/2024fbb1162899094cfc81152449b12bd0cc7053c6d4bda8ac2852545c87d0a851b1b72ed9560673cbf3ef6248257262c3c04aabf73117215c1b9cc7dd2542ce + languageName: node + linkType: hard + +"@babel/plugin-syntax-numeric-separator@npm:^7.10.4": + version: 7.10.4 + resolution: "@babel/plugin-syntax-numeric-separator@npm:7.10.4" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.10.4" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/c55a82b3113480942c6aa2fcbe976ff9caa74b7b1109ff4369641dfbc88d1da348aceb3c31b6ed311c84d1e7c479440b961906c735d0ab494f688bf2fd5b9bb9 + languageName: node + linkType: hard + +"@babel/plugin-syntax-object-rest-spread@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-object-rest-spread@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.8.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/ee1eab52ea6437e3101a0a7018b0da698545230015fc8ab129d292980ec6dff94d265e9e90070e8ae5fed42f08f1622c14c94552c77bcac784b37f503a82ff26 + languageName: node + linkType: hard + +"@babel/plugin-syntax-optional-catch-binding@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-optional-catch-binding@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.8.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/27e2493ab67a8ea6d693af1287f7e9acec206d1213ff107a928e85e173741e1d594196f99fec50e9dde404b09164f39dec5864c767212154ffe1caa6af0bc5af + languageName: node + linkType: hard + +"@babel/plugin-syntax-optional-chaining@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-optional-chaining@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.8.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/46edddf2faa6ebf94147b8e8540dfc60a5ab718e2de4d01b2c0bdf250a4d642c2bd47cbcbb739febcb2bf75514dbcefad3c52208787994b8d0f8822490f55e81 + languageName: node + linkType: hard + +"@babel/plugin-syntax-private-property-in-object@npm:^7.14.5": + version: 7.14.5 + resolution: "@babel/plugin-syntax-private-property-in-object@npm:7.14.5" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.14.5" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/69822772561706c87f0a65bc92d0772cea74d6bc0911537904a676d5ff496a6d3ac4e05a166d8125fce4a16605bace141afc3611074e170a994e66e5397787f3 + languageName: node + linkType: hard + +"@babel/plugin-syntax-top-level-await@npm:^7.14.5": + version: 7.14.5 + resolution: "@babel/plugin-syntax-top-level-await@npm:7.14.5" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.14.5" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/14bf6e65d5bc1231ffa9def5f0ef30b19b51c218fcecaa78cd1bdf7939dfdf23f90336080b7f5196916368e399934ce5d581492d8292b46a2fb569d8b2da106f + languageName: node + linkType: hard + +"@babel/plugin-transform-modules-commonjs@npm:7.23.3": + version: 7.23.3 + resolution: "@babel/plugin-transform-modules-commonjs@npm:7.23.3" + dependencies: + "@babel/helper-module-transforms": "npm:^7.23.3" + "@babel/helper-plugin-utils": "npm:^7.22.5" + "@babel/helper-simple-access": "npm:^7.22.5" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/5c8840c5c9ecba39367ae17c973ed13dbc43234147b77ae780eec65010e2a9993c5d717721b23e8179f7cf49decdd325c509b241d69cfbf92aa647a1d8d5a37d + languageName: node + linkType: hard + +"@babel/plugin-transform-react-jsx-self@npm:^7.21.0": + version: 7.27.1 + resolution: "@babel/plugin-transform-react-jsx-self@npm:7.27.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/00a4f917b70a608f9aca2fb39aabe04a60aa33165a7e0105fd44b3a8531630eb85bf5572e9f242f51e6ad2fa38c2e7e780902176c863556c58b5ba6f6e164031 + languageName: node + linkType: hard + +"@babel/plugin-transform-react-jsx-source@npm:^7.19.6": + version: 7.27.1 + resolution: "@babel/plugin-transform-react-jsx-source@npm:7.27.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/5e67b56c39c4d03e59e03ba80692b24c5a921472079b63af711b1d250fc37c1733a17069b63537f750f3e937ec44a42b1ee6a46cd23b1a0df5163b17f741f7f2 + languageName: node + linkType: hard + +"@babel/runtime@npm:7.23.6": + version: 7.23.6 + resolution: "@babel/runtime@npm:7.23.6" + dependencies: + regenerator-runtime: "npm:^0.14.0" + checksum: 10c0/d886954e985ef8e421222f7a2848884d96a752e0020d3078b920dd104e672fdf23bcc6f51a44313a048796319f1ac9d09c2c88ec8cbb4e1f09174bcd3335b9ff + languageName: node + linkType: hard + +"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.10.4, @babel/runtime@npm:^7.10.5, @babel/runtime@npm:^7.11.1, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.16.7, @babel/runtime@npm:^7.18.0, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.20.0, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.22.5, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.6, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.24.4, @babel/runtime@npm:^7.24.7, @babel/runtime@npm:^7.24.8, @babel/runtime@npm:^7.25.7, @babel/runtime@npm:^7.26.0, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.7.7, @babel/runtime@npm:^7.9.2": + version: 7.28.4 + resolution: "@babel/runtime@npm:7.28.4" + checksum: 10c0/792ce7af9750fb9b93879cc9d1db175701c4689da890e6ced242ea0207c9da411ccf16dc04e689cc01158b28d7898c40d75598f4559109f761c12ce01e959bf7 + languageName: node + linkType: hard + +"@babel/template@npm:^7.22.15, @babel/template@npm:^7.27.2, @babel/template@npm:^7.3.3": + version: 7.27.2 + resolution: "@babel/template@npm:7.27.2" + dependencies: + "@babel/code-frame": "npm:^7.27.1" + "@babel/parser": "npm:^7.27.2" + "@babel/types": "npm:^7.27.1" + checksum: 10c0/ed9e9022651e463cc5f2cc21942f0e74544f1754d231add6348ff1b472985a3b3502041c0be62dc99ed2d12cfae0c51394bf827452b98a2f8769c03b87aadc81 + languageName: node + linkType: hard + +"@babel/traverse@npm:^7.23.6, @babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.28.3, @babel/traverse@npm:^7.28.5, @babel/traverse@npm:^7.4.5": + version: 7.28.5 + resolution: "@babel/traverse@npm:7.28.5" + dependencies: + "@babel/code-frame": "npm:^7.27.1" + "@babel/generator": "npm:^7.28.5" + "@babel/helper-globals": "npm:^7.28.0" + "@babel/parser": "npm:^7.28.5" + "@babel/template": "npm:^7.27.2" + "@babel/types": "npm:^7.28.5" + debug: "npm:^4.3.1" + checksum: 10c0/f6c4a595993ae2b73f2d4cd9c062f2e232174d293edd4abe1d715bd6281da8d99e47c65857e8d0917d9384c65972f4acdebc6749a7c40a8fcc38b3c7fb3e706f + languageName: node + linkType: hard + +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.19.0, @babel/types@npm:^7.2.0, @babel/types@npm:^7.20.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.23.6, @babel/types@npm:^7.27.1, @babel/types@npm:^7.27.3, @babel/types@npm:^7.28.2, @babel/types@npm:^7.28.4, @babel/types@npm:^7.28.5, @babel/types@npm:^7.3.3": + version: 7.28.5 + resolution: "@babel/types@npm:7.28.5" + dependencies: + "@babel/helper-string-parser": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.28.5" + checksum: 10c0/a5a483d2100befbf125793640dec26b90b95fd233a94c19573325898a5ce1e52cdfa96e495c7dcc31b5eca5b66ce3e6d4a0f5a4a62daec271455959f208ab08a + languageName: node + linkType: hard + +"@bloomberg/record-tuple-polyfill@npm:0.0.4": + version: 0.0.4 + resolution: "@bloomberg/record-tuple-polyfill@npm:0.0.4" + checksum: 10c0/42f9152cf09aea76e91d5bd58dfc18d05b26aa9556cf07372096d62071898c9db378af139a9c0debf08340b5e1d21d5e45793a21f7d26c62e279ba58d79638e1 + languageName: node + linkType: hard + +"@cacheable/memory@npm:^2.0.8": + version: 2.0.8 + resolution: "@cacheable/memory@npm:2.0.8" + dependencies: + "@cacheable/utils": "npm:^2.4.0" + "@keyv/bigmap": "npm:^1.3.1" + hookified: "npm:^1.15.1" + keyv: "npm:^5.6.0" + checksum: 10c0/d12ec24e1257cda849329f629a8d0508e8f220827e2c198936f5554899df54401861b775a03f24ab51f01bacfb798cc03d3a82848adcd88d662ea8f22e802057 + languageName: node + linkType: hard + +"@cacheable/utils@npm:^2.4.0": + version: 2.4.1 + resolution: "@cacheable/utils@npm:2.4.1" + dependencies: + hashery: "npm:^1.5.1" + keyv: "npm:^5.6.0" + checksum: 10c0/ca2af47636ed27ab26dfedf12add639f42b90c289ecd5d816fb7a299074d9df463751745a83abfb81f6236a70c8ea40f0902e984869638a5ca3a7274e203f987 + languageName: node + linkType: hard + +"@chenshuai2144/sketch-color@npm:^1.0.7, @chenshuai2144/sketch-color@npm:^1.0.8": + version: 1.0.9 + resolution: "@chenshuai2144/sketch-color@npm:1.0.9" + dependencies: + reactcss: "npm:^1.2.3" + tinycolor2: "npm:^1.4.2" + peerDependencies: + react: ">=16.12.0" + checksum: 10c0/65967bca4a452e427585c0069127f1d01c85c1e0423b2b3e2fb13fd693b7230cc3100cf18a2de210ba2c0accfda48e227b0eeeda2de2f8fd12ff781e52f5bb09 + languageName: node + linkType: hard + +"@csstools/css-calc@npm:^3.1.1": + version: 3.2.0 + resolution: "@csstools/css-calc@npm:3.2.0" + peerDependencies: + "@csstools/css-parser-algorithms": ^4.0.0 + "@csstools/css-tokenizer": ^4.0.0 + checksum: 10c0/a4dffeef3cb8ec9e8c1e44fa68c7634033050be52ea0b56ba6ac3815b635b587c6f3a8f8cd7b8f53881c2dd0ab9ec0af77227c532ed81b8e24a05aa997d22337 + languageName: node + linkType: hard + +"@csstools/css-parser-algorithms@npm:^4.0.0": + version: 4.0.0 + resolution: "@csstools/css-parser-algorithms@npm:4.0.0" + peerDependencies: + "@csstools/css-tokenizer": ^4.0.0 + checksum: 10c0/94558c2428d6ef0ddef542e86e0a8376aa1263a12a59770abb13ba50d7b83086822c75433f32aa2e7fef00555e1cc88292f9ca5bce79aed232bb3fed73b1528d + languageName: node + linkType: hard + +"@csstools/css-syntax-patches-for-csstree@npm:^1.1.2": + version: 1.1.3 + resolution: "@csstools/css-syntax-patches-for-csstree@npm:1.1.3" + peerDependencies: + css-tree: ^3.2.1 + peerDependenciesMeta: + css-tree: + optional: true + checksum: 10c0/3b8a686710a46bb460f9d560d52ce0de315828e6d452002b692013e95fbf53669d7a71e28c9b6b1333fa9f37f058fad93e5db3b8516444907713cb9aad299ce1 + languageName: node + linkType: hard + +"@csstools/css-tokenizer@npm:^4.0.0": + version: 4.0.0 + resolution: "@csstools/css-tokenizer@npm:4.0.0" + checksum: 10c0/669cf3d0f9c8e1ffdf8c9955ad8beba0c8cfe03197fe29a4fcbd9ee6f7a18856cfa42c62670021a75183d9ab37f5d14a866e6a9df753a6c07f59e36797a9ea9f + languageName: node + linkType: hard + +"@csstools/media-query-list-parser@npm:^5.0.0": + version: 5.0.0 + resolution: "@csstools/media-query-list-parser@npm:5.0.0" + peerDependencies: + "@csstools/css-parser-algorithms": ^4.0.0 + "@csstools/css-tokenizer": ^4.0.0 + checksum: 10c0/dbc22654769eca02c182f3a57be02cd5b8d0b958adc8397e66770b64b0e8fcd32faa93a3f6a99e1457bde11862485de3cd83a31dac7b03925d32f9891b31ccfd + languageName: node + linkType: hard + +"@csstools/postcss-color-function@npm:^1.1.0": + version: 1.1.1 + resolution: "@csstools/postcss-color-function@npm:1.1.1" + dependencies: + "@csstools/postcss-progressive-custom-properties": "npm:^1.1.0" + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.2 + checksum: 10c0/802e23fc5ac38aed7366be2ffc3ae5572b45c82b31a0ced10a8fb8e69e7e15f6e975053ce54a6dabb6e56aa5d90a396d49c24eea5723165316acc9b3f988a085 + languageName: node + linkType: hard + +"@csstools/postcss-font-format-keywords@npm:^1.0.0": + version: 1.0.1 + resolution: "@csstools/postcss-font-format-keywords@npm:1.0.1" + dependencies: + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.2 + checksum: 10c0/bbd52500809ddc62fe5052d43f3353797d47608bab59e0f62da8165de33404ed047a024f190d69b22e1d4883a43e5a48af443c390010bcc1d58d880cc808715e + languageName: node + linkType: hard + +"@csstools/postcss-hwb-function@npm:^1.0.0": + version: 1.0.2 + resolution: "@csstools/postcss-hwb-function@npm:1.0.2" + dependencies: + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.2 + checksum: 10c0/28dfbfc01b5b1d9dd33d2cc9c2ae9b57e73bdf90f2f698f786863c3e116145a1bbe4146b2db2fdfa470444cd8cc9cedac86cf893a9025a690a350a47a040107a + languageName: node + linkType: hard + +"@csstools/postcss-ic-unit@npm:^1.0.0": + version: 1.0.1 + resolution: "@csstools/postcss-ic-unit@npm:1.0.1" + dependencies: + "@csstools/postcss-progressive-custom-properties": "npm:^1.1.0" + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.2 + checksum: 10c0/f12ee4c3e6858be4fdf3cad05013898b7b8e62122709ef62c3b236232b1181bd142e7f19460e968fd7759e6d10b113e82a87c206f5adcaaf5ef3acf1c446e5f8 + languageName: node + linkType: hard + +"@csstools/postcss-is-pseudo-class@npm:^2.0.2": + version: 2.0.7 + resolution: "@csstools/postcss-is-pseudo-class@npm:2.0.7" + dependencies: + "@csstools/selector-specificity": "npm:^2.0.0" + postcss-selector-parser: "npm:^6.0.10" + peerDependencies: + postcss: ^8.2 + checksum: 10c0/7b0a511f6283b5a2c6f6fc2eecf08f7fbe3772c44cf3a2be327b41731aeafcc93cf7f2a4e01ff6dcb7c5fa88d941ae4b818f0ed2ec93f708d7efda5a3e5a8089 + languageName: node + linkType: hard + +"@csstools/postcss-normalize-display-values@npm:^1.0.0": + version: 1.0.1 + resolution: "@csstools/postcss-normalize-display-values@npm:1.0.1" + dependencies: + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.2 + checksum: 10c0/92361a0917b22f3d47c61706c4124560265d9b316b3d877ab2a759de9ae8fe4c50729cc79b99a81aa3a4b54e67d4acc7512c6d460bf308c2197acdc3e9f1287e + languageName: node + linkType: hard + +"@csstools/postcss-oklab-function@npm:^1.1.0": + version: 1.1.1 + resolution: "@csstools/postcss-oklab-function@npm:1.1.1" + dependencies: + "@csstools/postcss-progressive-custom-properties": "npm:^1.1.0" + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.2 + checksum: 10c0/f7a3734154bbe3658cee776417cadb99cedfe138b2c1893095a87694fce5498cb623c743cdd5eef933c450cfbba8961b3fa079ebcb5039636f81567deb9db5d5 + languageName: node + linkType: hard + +"@csstools/postcss-progressive-custom-properties@npm:^1.1.0, @csstools/postcss-progressive-custom-properties@npm:^1.3.0": + version: 1.3.0 + resolution: "@csstools/postcss-progressive-custom-properties@npm:1.3.0" + dependencies: + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.3 + checksum: 10c0/1910a564e433c7673ad9ceef04e08ec6ac91fa91b8e5b433d018c84983be341ba84232afcb8a4217fb7a31e3711f22115266bfe040efeb7d6ec2a314de826f7e + languageName: node + linkType: hard + +"@csstools/postcss-stepped-value-functions@npm:^1.0.0": + version: 1.0.1 + resolution: "@csstools/postcss-stepped-value-functions@npm:1.0.1" + dependencies: + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.2 + checksum: 10c0/ba04c94bf0b21616df278c317a047f809cfb855e4939f9511d82e80018386ccff1cef92c73c5382866491e7a1db61f7889703b97433381e882440c1f3668298a + languageName: node + linkType: hard + +"@csstools/postcss-unset-value@npm:^1.0.0": + version: 1.0.2 + resolution: "@csstools/postcss-unset-value@npm:1.0.2" + peerDependencies: + postcss: ^8.2 + checksum: 10c0/43d656360ffda504f22f3470cd8c1826362e8938da8eea1c2878302b878d38305c48c31090455fe760f40386c10ccbe17e9a95d63fb4e7934c035e805b641e12 + languageName: node + linkType: hard + +"@csstools/selector-resolve-nested@npm:^4.0.0": + version: 4.0.0 + resolution: "@csstools/selector-resolve-nested@npm:4.0.0" + peerDependencies: + postcss-selector-parser: ^7.1.1 + checksum: 10c0/f6bccfe24a47c3e55710d421740550b868bbe7f0820f32d14d1eb737eefe7ec26261fb726cc5cfdf8236b3173d0a657ebdc9602e0c53824603f719b14a76bcaf + languageName: node + linkType: hard + +"@csstools/selector-specificity@npm:^2.0.0": + version: 2.2.0 + resolution: "@csstools/selector-specificity@npm:2.2.0" + peerDependencies: + postcss-selector-parser: ^6.0.10 + checksum: 10c0/d81c9b437f7d45ad0171e09240454ced439fa3e67576daae4ec7bb9c03e7a6061afeb0fa21d41f5f45d54bf8e242a7aa8101fbbba7ca7632dd847601468b5d9e + languageName: node + linkType: hard + +"@csstools/selector-specificity@npm:^6.0.0": + version: 6.0.0 + resolution: "@csstools/selector-specificity@npm:6.0.0" + peerDependencies: + postcss-selector-parser: ^7.1.1 + checksum: 10c0/7a93973f9054f2e1f03c8543cde68e0b0c65e5e72da6e4e959974d28fe809e11bd2afa1ff2ca11a1690a4c9a2f2bbe00d00e2b07fb2108bf89c5e48fe441c432 + languageName: node + linkType: hard + +"@ctrl/tinycolor@npm:^3.4.0": + version: 3.6.1 + resolution: "@ctrl/tinycolor@npm:3.6.1" + checksum: 10c0/444d81612cd8c5c802a3d1253df83d5f77d3db87f351861655683a4743990e6b38976bf2e4129591c5a258607b63574b3c7bed702cf6a0eb7912222edf4570e9 + languageName: node + linkType: hard + +"@dagrejs/dagre@npm:^3.0.0": + version: 3.0.0 + resolution: "@dagrejs/dagre@npm:3.0.0" + dependencies: + "@dagrejs/graphlib": "npm:4.0.1" + checksum: 10c0/f5d43819697b33864e8aced460fbf972a7151758b5f0aea8ee47315b9ab957782b2ab8beb4e1e2bff892b1820b0ff8ab8a22abc12a65ebb37e205e3a7f63a6e0 + languageName: node + linkType: hard + +"@dagrejs/graphlib@npm:4.0.1": + version: 4.0.1 + resolution: "@dagrejs/graphlib@npm:4.0.1" + checksum: 10c0/03ab574f2eb7d87173af0b9d8bbae87c10e225778b8144a800c663afe307ff71d851c13d96d32ec91db85e325d64914cdabbab1ce76fb043e0a5538e60bb51bd + languageName: node + linkType: hard + +"@develar/schema-utils@npm:~2.6.5": + version: 2.6.5 + resolution: "@develar/schema-utils@npm:2.6.5" + dependencies: + ajv: "npm:^6.12.0" + ajv-keywords: "npm:^3.4.1" + checksum: 10c0/7c6075ce6742dd5c89b3cebf81351ec1d73dafc7c3409748860e4f8262fb26ffe6d998c5baab4eca579cd436e7c6c12c615fe89819c19484a22d25b3e6825cb5 + languageName: node + linkType: hard + +"@dnd-kit/accessibility@npm:^3.1.1": + version: 3.1.1 + resolution: "@dnd-kit/accessibility@npm:3.1.1" + dependencies: + tslib: "npm:^2.0.0" + peerDependencies: + react: ">=16.8.0" + checksum: 10c0/be0bf41716dc58f9386bc36906ec1ce72b7b42b6d1d0e631d347afe9bd8714a829bd6f58a346dd089b1519e93918ae2f94497411a61a4f5e4d9247c6cfd1fef8 + languageName: node + linkType: hard + +"@dnd-kit/core@npm:^6.0.8": + version: 6.3.1 + resolution: "@dnd-kit/core@npm:6.3.1" + dependencies: + "@dnd-kit/accessibility": "npm:^3.1.1" + "@dnd-kit/utilities": "npm:^3.2.2" + tslib: "npm:^2.0.0" + peerDependencies: + react: ">=16.8.0" + react-dom: ">=16.8.0" + checksum: 10c0/196db95d81096d9dc248983533eab91ba83591770fa5c894b1ac776f42af0d99522b3fd5bb3923411470e4733fcfa103e6ee17adc17b9b7eb54c7fbec5ff7c52 + languageName: node + linkType: hard + +"@dnd-kit/modifiers@npm:^6.0.1": + version: 6.0.1 + resolution: "@dnd-kit/modifiers@npm:6.0.1" + dependencies: + "@dnd-kit/utilities": "npm:^3.2.1" + tslib: "npm:^2.0.0" + peerDependencies: + "@dnd-kit/core": ^6.0.6 + react: ">=16.8.0" + checksum: 10c0/cf2a68f4b0c35c87fa143bce0d980ec82067dc023673f27d842d185a15041b5ca19140795351d9a774d86b1a7e3df00a118c4ff61ae121696021ba5678d50363 + languageName: node + linkType: hard + +"@dnd-kit/sortable@npm:^7.0.2": + version: 7.0.2 + resolution: "@dnd-kit/sortable@npm:7.0.2" + dependencies: + "@dnd-kit/utilities": "npm:^3.2.0" + tslib: "npm:^2.0.0" + peerDependencies: + "@dnd-kit/core": ^6.0.7 + react: ">=16.8.0" + checksum: 10c0/06aeb113eeeb470bb2443bf1c48d597157bb3a1caa9740e60c2fa73a3076e753cd083a2d381f0556bd7e9873e851a49ce8ea14796ac02e2d796eabea4e27196d + languageName: node + linkType: hard + +"@dnd-kit/utilities@npm:^3.2.0, @dnd-kit/utilities@npm:^3.2.1, @dnd-kit/utilities@npm:^3.2.2": + version: 3.2.2 + resolution: "@dnd-kit/utilities@npm:3.2.2" + dependencies: + tslib: "npm:^2.0.0" + peerDependencies: + react: ">=16.8.0" + checksum: 10c0/9aa90526f3e3fd567b5acc1b625a63177b9e8d00e7e50b2bd0e08fa2bf4dba7e19529777e001fdb8f89a7ce69f30b190c8364d390212634e0afdfa8c395e85a0 + languageName: node + linkType: hard + +"@electron/get@npm:^2.0.0": + version: 2.0.3 + resolution: "@electron/get@npm:2.0.3" + dependencies: + debug: "npm:^4.1.1" + env-paths: "npm:^2.2.0" + fs-extra: "npm:^8.1.0" + global-agent: "npm:^3.0.0" + got: "npm:^11.8.5" + progress: "npm:^2.0.3" + semver: "npm:^6.2.0" + sumchecker: "npm:^3.0.1" + dependenciesMeta: + global-agent: + optional: true + checksum: 10c0/148957d531bac50c29541515f2483c3e5c9c6ba9f0269a5d536540d2b8d849188a89588f18901f3a84c2b4fd376d1e0c5ea2159eb2d17bda68558f57df19015e + languageName: node + linkType: hard + +"@electron/universal@npm:1.2.1": + version: 1.2.1 + resolution: "@electron/universal@npm:1.2.1" + dependencies: + "@malept/cross-spawn-promise": "npm:^1.1.0" + asar: "npm:^3.1.0" + debug: "npm:^4.3.1" + dir-compare: "npm:^2.4.0" + fs-extra: "npm:^9.0.1" + minimatch: "npm:^3.0.4" + plist: "npm:^3.0.4" + checksum: 10c0/b6e2c5948db24944d23fb16621154af93fdee56ee87dcbe18534d44d2d519f50572fe784310640cd6a3be3ebe4b0f393fbc20e7aa57c410afff6834344f6bb05 + languageName: node + linkType: hard + +"@emotion/hash@npm:^0.8.0": + version: 0.8.0 + resolution: "@emotion/hash@npm:0.8.0" + checksum: 10c0/706303d35d416217cd7eb0d36dbda4627bb8bdf4a32ea387e8dd99be11b8e0a998e10af21216e8a5fade518ad955ff06aa8890f20e694ce3a038ae7fc1000556 + languageName: node + linkType: hard + +"@emotion/is-prop-valid@npm:1.2.2": + version: 1.2.2 + resolution: "@emotion/is-prop-valid@npm:1.2.2" + dependencies: + "@emotion/memoize": "npm:^0.8.1" + checksum: 10c0/bb1530dcb4e0e5a4fabb219279f2d0bc35796baf66f6241f98b0d03db1985c890a8cafbea268e0edefd5eeda143dbd5c09a54b5fba74cee8c69b98b13194af50 + languageName: node + linkType: hard + +"@emotion/is-prop-valid@npm:^1.1.0, @emotion/is-prop-valid@npm:^1.2.1": + version: 1.4.0 + resolution: "@emotion/is-prop-valid@npm:1.4.0" + dependencies: + "@emotion/memoize": "npm:^0.9.0" + checksum: 10c0/5f857814ec7d8c7e727727346dfb001af6b1fb31d621a3ce9c3edf944a484d8b0d619546c30899ae3ade2f317c76390ba4394449728e9bf628312defc2c41ac3 + languageName: node + linkType: hard + +"@emotion/memoize@npm:^0.8.1": + version: 0.8.1 + resolution: "@emotion/memoize@npm:0.8.1" + checksum: 10c0/dffed372fc3b9fa2ba411e76af22b6bb686fb0cb07694fdfaa6dd2baeb0d5e4968c1a7caa472bfcf06a5997d5e7c7d16b90e993f9a6ffae79a2c3dbdc76dfe78 + languageName: node + linkType: hard + +"@emotion/memoize@npm:^0.9.0": + version: 0.9.0 + resolution: "@emotion/memoize@npm:0.9.0" + checksum: 10c0/13f474a9201c7f88b543e6ea42f55c04fb2fdc05e6c5a3108aced2f7e7aa7eda7794c56bba02985a46d8aaa914fcdde238727a98341a96e2aec750d372dadd15 + languageName: node + linkType: hard + +"@emotion/stylis@npm:^0.8.4": + version: 0.8.5 + resolution: "@emotion/stylis@npm:0.8.5" + checksum: 10c0/f109e3f11cb0d48e8658aaa23578c5bcfe35e297819cfb089a3de6ba8dc0f89b0960474922690c6028df5d2e1895b4967f2fb280642c030054c312f1e137ce26 + languageName: node + linkType: hard + +"@emotion/unitless@npm:0.8.1, @emotion/unitless@npm:^0.8.0": + version: 0.8.1 + resolution: "@emotion/unitless@npm:0.8.1" + checksum: 10c0/a1ed508628288f40bfe6dd17d431ed899c067a899fa293a13afe3aed1d70fac0412b8a215fafab0b42829360db687fecd763e5f01a64ddc4a4b58ec3112ff548 + languageName: node + linkType: hard + +"@emotion/unitless@npm:^0.7.4, @emotion/unitless@npm:^0.7.5": + version: 0.7.5 + resolution: "@emotion/unitless@npm:0.7.5" + checksum: 10c0/4d0d94f53cb97b4481bbfa394953e1899a0b877644642ba9dd7247c27eb8c48e14e22aeb11411d7d9874685ad85dd5fb5b50eb78c6d8840eb56a84b92dcef2f4 + languageName: node + linkType: hard + +"@esbuild-kit/cjs-loader@npm:^2.4.1": + version: 2.4.4 + resolution: "@esbuild-kit/cjs-loader@npm:2.4.4" + dependencies: + "@esbuild-kit/core-utils": "npm:^3.2.3" + get-tsconfig: "npm:^4.7.0" + checksum: 10c0/021df0d4de26d4eb0fa1fb8dfe4da8f745a6e711ffaa352de566e9af4246e5d9e00a5926550c625e6ff5e439a362584cd7c4dda1130241c23cbd74c586670deb + languageName: node + linkType: hard + +"@esbuild-kit/core-utils@npm:^3.0.0, @esbuild-kit/core-utils@npm:^3.2.3, @esbuild-kit/core-utils@npm:^3.3.2": + version: 3.3.2 + resolution: "@esbuild-kit/core-utils@npm:3.3.2" + dependencies: + esbuild: "npm:~0.18.20" + source-map-support: "npm:^0.5.21" + checksum: 10c0/d856f5bd720814593f911d781ed7558a3f8ec1a39802f3831d0eea0d1306e0e2dc11b7b2443af621c413ec6557f1f3034a9a4f1472a4cb40e52cd6e3b356aa05 + languageName: node + linkType: hard + +"@esbuild-kit/esm-loader@npm:^2.5.4": + version: 2.6.5 + resolution: "@esbuild-kit/esm-loader@npm:2.6.5" + dependencies: + "@esbuild-kit/core-utils": "npm:^3.3.2" + get-tsconfig: "npm:^4.7.0" + checksum: 10c0/6894b29176eda62bdce0d458d57f32daed5cb8fcff14cb3ddfbc995cfe3e2fa8599f3b0b1af66db446903b30167f57069f27e9cf79a69cf9b41f557115811cde + languageName: node + linkType: hard + +"@esbuild/aix-ppc64@npm:0.21.4": + version: 0.21.4 + resolution: "@esbuild/aix-ppc64@npm:0.21.4" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/android-arm64@npm:0.18.20": + version: 0.18.20 + resolution: "@esbuild/android-arm64@npm:0.18.20" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/android-arm64@npm:0.21.4": + version: 0.21.4 + resolution: "@esbuild/android-arm64@npm:0.21.4" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/android-arm@npm:0.18.20": + version: 0.18.20 + resolution: "@esbuild/android-arm@npm:0.18.20" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@esbuild/android-arm@npm:0.21.4": + version: 0.21.4 + resolution: "@esbuild/android-arm@npm:0.21.4" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@esbuild/android-x64@npm:0.18.20": + version: 0.18.20 + resolution: "@esbuild/android-x64@npm:0.18.20" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/android-x64@npm:0.21.4": + version: 0.21.4 + resolution: "@esbuild/android-x64@npm:0.21.4" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/darwin-arm64@npm:0.18.20": + version: 0.18.20 + resolution: "@esbuild/darwin-arm64@npm:0.18.20" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/darwin-arm64@npm:0.21.4": + version: 0.21.4 + resolution: "@esbuild/darwin-arm64@npm:0.21.4" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/darwin-x64@npm:0.18.20": + version: 0.18.20 + resolution: "@esbuild/darwin-x64@npm:0.18.20" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/darwin-x64@npm:0.21.4": + version: 0.21.4 + resolution: "@esbuild/darwin-x64@npm:0.21.4" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/freebsd-arm64@npm:0.18.20": + version: 0.18.20 + resolution: "@esbuild/freebsd-arm64@npm:0.18.20" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/freebsd-arm64@npm:0.21.4": + version: 0.21.4 + resolution: "@esbuild/freebsd-arm64@npm:0.21.4" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/freebsd-x64@npm:0.18.20": + version: 0.18.20 + resolution: "@esbuild/freebsd-x64@npm:0.18.20" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/freebsd-x64@npm:0.21.4": + version: 0.21.4 + resolution: "@esbuild/freebsd-x64@npm:0.21.4" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/linux-arm64@npm:0.18.20": + version: 0.18.20 + resolution: "@esbuild/linux-arm64@npm:0.18.20" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/linux-arm64@npm:0.21.4": + version: 0.21.4 + resolution: "@esbuild/linux-arm64@npm:0.21.4" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/linux-arm@npm:0.18.20": + version: 0.18.20 + resolution: "@esbuild/linux-arm@npm:0.18.20" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@esbuild/linux-arm@npm:0.21.4": + version: 0.21.4 + resolution: "@esbuild/linux-arm@npm:0.21.4" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@esbuild/linux-ia32@npm:0.18.20": + version: 0.18.20 + resolution: "@esbuild/linux-ia32@npm:0.18.20" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/linux-ia32@npm:0.21.4": + version: 0.21.4 + resolution: "@esbuild/linux-ia32@npm:0.21.4" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/linux-loong64@npm:0.18.20": + version: 0.18.20 + resolution: "@esbuild/linux-loong64@npm:0.18.20" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + +"@esbuild/linux-loong64@npm:0.21.4": + version: 0.21.4 + resolution: "@esbuild/linux-loong64@npm:0.21.4" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + +"@esbuild/linux-mips64el@npm:0.18.20": + version: 0.18.20 + resolution: "@esbuild/linux-mips64el@npm:0.18.20" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + +"@esbuild/linux-mips64el@npm:0.21.4": + version: 0.21.4 + resolution: "@esbuild/linux-mips64el@npm:0.21.4" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + +"@esbuild/linux-ppc64@npm:0.18.20": + version: 0.18.20 + resolution: "@esbuild/linux-ppc64@npm:0.18.20" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/linux-ppc64@npm:0.21.4": + version: 0.21.4 + resolution: "@esbuild/linux-ppc64@npm:0.21.4" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/linux-riscv64@npm:0.18.20": + version: 0.18.20 + resolution: "@esbuild/linux-riscv64@npm:0.18.20" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + +"@esbuild/linux-riscv64@npm:0.21.4": + version: 0.21.4 + resolution: "@esbuild/linux-riscv64@npm:0.21.4" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + +"@esbuild/linux-s390x@npm:0.18.20": + version: 0.18.20 + resolution: "@esbuild/linux-s390x@npm:0.18.20" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + +"@esbuild/linux-s390x@npm:0.21.4": + version: 0.21.4 + resolution: "@esbuild/linux-s390x@npm:0.21.4" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + +"@esbuild/linux-x64@npm:0.18.20": + version: 0.18.20 + resolution: "@esbuild/linux-x64@npm:0.18.20" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/linux-x64@npm:0.21.4": + version: 0.21.4 + resolution: "@esbuild/linux-x64@npm:0.21.4" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/netbsd-x64@npm:0.18.20": + version: 0.18.20 + resolution: "@esbuild/netbsd-x64@npm:0.18.20" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/netbsd-x64@npm:0.21.4": + version: 0.21.4 + resolution: "@esbuild/netbsd-x64@npm:0.21.4" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-x64@npm:0.18.20": + version: 0.18.20 + resolution: "@esbuild/openbsd-x64@npm:0.18.20" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-x64@npm:0.21.4": + version: 0.21.4 + resolution: "@esbuild/openbsd-x64@npm:0.21.4" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/sunos-x64@npm:0.18.20": + version: 0.18.20 + resolution: "@esbuild/sunos-x64@npm:0.18.20" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/sunos-x64@npm:0.21.4": + version: 0.21.4 + resolution: "@esbuild/sunos-x64@npm:0.21.4" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/win32-arm64@npm:0.18.20": + version: 0.18.20 + resolution: "@esbuild/win32-arm64@npm:0.18.20" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/win32-arm64@npm:0.21.4": + version: 0.21.4 + resolution: "@esbuild/win32-arm64@npm:0.21.4" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/win32-ia32@npm:0.18.20": + version: 0.18.20 + resolution: "@esbuild/win32-ia32@npm:0.18.20" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/win32-ia32@npm:0.21.4": + version: 0.21.4 + resolution: "@esbuild/win32-ia32@npm:0.21.4" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/win32-x64@npm:0.18.20": + version: 0.18.20 + resolution: "@esbuild/win32-x64@npm:0.18.20" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/win32-x64@npm:0.21.4": + version: 0.21.4 + resolution: "@esbuild/win32-x64@npm:0.21.4" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.4.0": + version: 4.9.0 + resolution: "@eslint-community/eslint-utils@npm:4.9.0" + dependencies: + eslint-visitor-keys: "npm:^3.4.3" + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + checksum: 10c0/8881e22d519326e7dba85ea915ac7a143367c805e6ba1374c987aa2fbdd09195cc51183d2da72c0e2ff388f84363e1b220fd0d19bef10c272c63455162176817 + languageName: node + linkType: hard + +"@eslint-community/regexpp@npm:^4.4.0, @eslint-community/regexpp@npm:^4.5.1, @eslint-community/regexpp@npm:^4.6.1": + version: 4.12.2 + resolution: "@eslint-community/regexpp@npm:4.12.2" + checksum: 10c0/fddcbc66851b308478d04e302a4d771d6917a0b3740dc351513c0da9ca2eab8a1adf99f5e0aa7ab8b13fa0df005c81adeee7e63a92f3effd7d367a163b721c2d + languageName: node + linkType: hard + +"@eslint/eslintrc@npm:^2.1.4": + version: 2.1.4 + resolution: "@eslint/eslintrc@npm:2.1.4" + dependencies: + ajv: "npm:^6.12.4" + debug: "npm:^4.3.2" + espree: "npm:^9.6.0" + globals: "npm:^13.19.0" + ignore: "npm:^5.2.0" + import-fresh: "npm:^3.2.1" + js-yaml: "npm:^4.1.0" + minimatch: "npm:^3.1.2" + strip-json-comments: "npm:^3.1.1" + checksum: 10c0/32f67052b81768ae876c84569ffd562491ec5a5091b0c1e1ca1e0f3c24fb42f804952fdd0a137873bc64303ba368a71ba079a6f691cee25beee9722d94cc8573 + languageName: node + linkType: hard + +"@eslint/js@npm:8.57.1": + version: 8.57.1 + resolution: "@eslint/js@npm:8.57.1" + checksum: 10c0/b489c474a3b5b54381c62e82b3f7f65f4b8a5eaaed126546520bf2fede5532a8ed53212919fed1e9048dcf7f37167c8561d58d0ba4492a4244004e7793805223 + languageName: node + linkType: hard + +"@floating-ui/core@npm:^0.6.2": + version: 0.6.2 + resolution: "@floating-ui/core@npm:0.6.2" + checksum: 10c0/a27fca302fae8ee915e77efc3ab45bd03d52370a9e34f087a53e1d4ff6e5a41a77c2d4c9236fe4ea9145e1580a7cef2f96a69de74cb561a8e9a3bc3fcb40b32e + languageName: node + linkType: hard + +"@floating-ui/dom@npm:^0.4.5": + version: 0.4.5 + resolution: "@floating-ui/dom@npm:0.4.5" + dependencies: + "@floating-ui/core": "npm:^0.6.2" + checksum: 10c0/dad990e9164c92c251fd5687d9e1461083005b203468183468313a3907992242b6281de2b2e3e874ab84459bf18ea4b4c0459223ab66d89e11928fe060a946f5 + languageName: node + linkType: hard + +"@floating-ui/react-dom-interactions@npm:^0.3.1": + version: 0.3.1 + resolution: "@floating-ui/react-dom-interactions@npm:0.3.1" + dependencies: + "@floating-ui/react-dom": "npm:^0.6.3" + aria-hidden: "npm:^1.1.3" + point-in-polygon: "npm:^1.1.0" + use-isomorphic-layout-effect: "npm:^1.1.1" + checksum: 10c0/5dd53cce4e9a24d49a7bc10212b0608c684e58eada89fb9b6f3a423e39c258bc5a2c841e94ed3c1a1bdc55db23e0b1b64c3a230905e4097896ff1cdbce885c3b + languageName: node + linkType: hard + +"@floating-ui/react-dom@npm:^0.6.3": + version: 0.6.3 + resolution: "@floating-ui/react-dom@npm:0.6.3" + dependencies: + "@floating-ui/dom": "npm:^0.4.5" + use-isomorphic-layout-effect: "npm:^1.1.1" + peerDependencies: + react: ">=16.8.0" + react-dom: ">=16.8.0" + checksum: 10c0/83b89c0535b283ce2ae8b637237987bbece02bb15f9765d94004ce5ca0d77e32ef1402f6499c04b357787d010d9ec23d2eaf0b6ab7ab8b2c53a39ab813c8d046 + languageName: node + linkType: hard + +"@formatjs/intl-displaynames@npm:^1.2.0": + version: 1.2.10 + resolution: "@formatjs/intl-displaynames@npm:1.2.10" + dependencies: + "@formatjs/intl-utils": "npm:^2.3.0" + checksum: 10c0/924af87888623efcf37d373d52a570148318519d19b16b4bbbd088ae3d6b0af2e7baec4f1d17b523477adb1d88f916d11d8f67d7a4d432348d5d204304dfbb06 + languageName: node + linkType: hard + +"@formatjs/intl-listformat@npm:^1.4.1": + version: 1.4.8 + resolution: "@formatjs/intl-listformat@npm:1.4.8" + dependencies: + "@formatjs/intl-utils": "npm:^2.3.0" + checksum: 10c0/75c9fccded546994543c8ac55afbfc4e6c32d2b78a525409a5855347b813f0e6f2f95beeaf4862207102def715d89d3a50176de15edd8098bcf57cf60e8f2cf6 + languageName: node + linkType: hard + +"@formatjs/intl-relativetimeformat@npm:^4.5.9": + version: 4.5.16 + resolution: "@formatjs/intl-relativetimeformat@npm:4.5.16" + dependencies: + "@formatjs/intl-utils": "npm:^2.3.0" + checksum: 10c0/7beed7c530c1d8f5eb56cfbb07a298a5f6026d5727b2aea134854103c1b2d0a6c2e8e7f9e65a1842613324a9829af470cb42287d9575525865558b864be7656c + languageName: node + linkType: hard + +"@formatjs/intl-unified-numberformat@npm:^3.2.0": + version: 3.3.7 + resolution: "@formatjs/intl-unified-numberformat@npm:3.3.7" + dependencies: + "@formatjs/intl-utils": "npm:^2.3.0" + checksum: 10c0/083e95fd12fdc1a95a0738cca1df4e49caf74e0d4dbda5e6b3650c32798d46321f77f3f7de9691e5d5c691b0a0ddd98f849351c6b9c1e01591d088be9d49c9ec + languageName: node + linkType: hard + +"@formatjs/intl-utils@npm:^2.2.0, @formatjs/intl-utils@npm:^2.3.0": + version: 2.3.0 + resolution: "@formatjs/intl-utils@npm:2.3.0" + checksum: 10c0/9d39a7bf55c480d50d24ca8216314dd42dd90b329b4f4dd2c02804b906ed2b86c8d6dcc150570122149e49181e1c55da1a9834ca7a6ecf7dc2abef7a074c3895 + languageName: node + linkType: hard + +"@humanwhocodes/config-array@npm:^0.13.0": + version: 0.13.0 + resolution: "@humanwhocodes/config-array@npm:0.13.0" + dependencies: + "@humanwhocodes/object-schema": "npm:^2.0.3" + debug: "npm:^4.3.1" + minimatch: "npm:^3.0.5" + checksum: 10c0/205c99e756b759f92e1f44a3dc6292b37db199beacba8f26c2165d4051fe73a4ae52fdcfd08ffa93e7e5cb63da7c88648f0e84e197d154bbbbe137b2e0dd332e + languageName: node + linkType: hard + +"@humanwhocodes/module-importer@npm:^1.0.1": + version: 1.0.1 + resolution: "@humanwhocodes/module-importer@npm:1.0.1" + checksum: 10c0/909b69c3b86d482c26b3359db16e46a32e0fb30bd306a3c176b8313b9e7313dba0f37f519de6aa8b0a1921349e505f259d19475e123182416a506d7f87e7f529 + languageName: node + linkType: hard + +"@humanwhocodes/object-schema@npm:^2.0.3": + version: 2.0.3 + resolution: "@humanwhocodes/object-schema@npm:2.0.3" + checksum: 10c0/80520eabbfc2d32fe195a93557cef50dfe8c8905de447f022675aaf66abc33ae54098f5ea78548d925aa671cd4ab7c7daa5ad704fe42358c9b5e7db60f80696c + languageName: node + linkType: hard + +"@iconify/types@npm:^2.0.0": + version: 2.0.0 + resolution: "@iconify/types@npm:2.0.0" + checksum: 10c0/65a3be43500c7ccacf360e136d00e1717f050b7b91da644e94370256ac66f582d59212bdb30d00788aab4fc078262e91c95b805d1808d654b72f6d2072a7e4b2 + languageName: node + linkType: hard + +"@iconify/utils@npm:2.1.1": + version: 2.1.1 + resolution: "@iconify/utils@npm:2.1.1" + dependencies: + "@antfu/install-pkg": "npm:^0.1.1" + "@antfu/utils": "npm:^0.7.2" + "@iconify/types": "npm:^2.0.0" + debug: "npm:^4.3.4" + kolorist: "npm:^1.6.0" + local-pkg: "npm:^0.4.2" + checksum: 10c0/69b8d40888341afd36e03e80bdf20d641524da9fd0a5af048b7c43198320c5bf634aff05c50943809bd25eaea8c3738d7d95fc8fee4082fa6be94a3aea699459 + languageName: node + linkType: hard + +"@isaacs/balanced-match@npm:^4.0.1": + version: 4.0.1 + resolution: "@isaacs/balanced-match@npm:4.0.1" + checksum: 10c0/7da011805b259ec5c955f01cee903da72ad97c5e6f01ca96197267d3f33103d5b2f8a1af192140f3aa64526c593c8d098ae366c2b11f7f17645d12387c2fd420 + languageName: node + linkType: hard + +"@isaacs/brace-expansion@npm:^5.0.0": + version: 5.0.0 + resolution: "@isaacs/brace-expansion@npm:5.0.0" + dependencies: + "@isaacs/balanced-match": "npm:^4.0.1" + checksum: 10c0/b4d4812f4be53afc2c5b6c545001ff7a4659af68d4484804e9d514e183d20269bb81def8682c01a22b17c4d6aed14292c8494f7d2ac664e547101c1a905aa977 + languageName: node + linkType: hard + +"@isaacs/cliui@npm:^8.0.2": + version: 8.0.2 + resolution: "@isaacs/cliui@npm:8.0.2" + dependencies: + string-width: "npm:^5.1.2" + string-width-cjs: "npm:string-width@^4.2.0" + strip-ansi: "npm:^7.0.1" + strip-ansi-cjs: "npm:strip-ansi@^6.0.1" + wrap-ansi: "npm:^8.1.0" + wrap-ansi-cjs: "npm:wrap-ansi@^7.0.0" + checksum: 10c0/b1bf42535d49f11dc137f18d5e4e63a28c5569de438a221c369483731e9dac9fb797af554e8bf02b6192d1e5eba6e6402cf93900c3d0ac86391d00d04876789e + languageName: node + linkType: hard + +"@isaacs/fs-minipass@npm:^4.0.0": + version: 4.0.1 + resolution: "@isaacs/fs-minipass@npm:4.0.1" + dependencies: + minipass: "npm:^7.0.4" + checksum: 10c0/c25b6dc1598790d5b55c0947a9b7d111cfa92594db5296c3b907e2f533c033666f692a3939eadac17b1c7c40d362d0b0635dc874cbfe3e70db7c2b07cc97a5d2 + languageName: node + linkType: hard + +"@istanbuljs/load-nyc-config@npm:^1.0.0": + version: 1.1.0 + resolution: "@istanbuljs/load-nyc-config@npm:1.1.0" + dependencies: + camelcase: "npm:^5.3.1" + find-up: "npm:^4.1.0" + get-package-type: "npm:^0.1.0" + js-yaml: "npm:^3.13.1" + resolve-from: "npm:^5.0.0" + checksum: 10c0/dd2a8b094887da5a1a2339543a4933d06db2e63cbbc2e288eb6431bd832065df0c099d091b6a67436e71b7d6bf85f01ce7c15f9253b4cbebcc3b9a496165ba42 + languageName: node + linkType: hard + +"@istanbuljs/schema@npm:^0.1.2": + version: 0.1.3 + resolution: "@istanbuljs/schema@npm:0.1.3" + checksum: 10c0/61c5286771676c9ca3eb2bd8a7310a9c063fb6e0e9712225c8471c582d157392c88f5353581c8c9adbe0dff98892317d2fdfc56c3499aa42e0194405206a963a + languageName: node + linkType: hard + +"@jest/schemas@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/schemas@npm:29.6.3" + dependencies: + "@sinclair/typebox": "npm:^0.27.8" + checksum: 10c0/b329e89cd5f20b9278ae1233df74016ebf7b385e0d14b9f4c1ad18d096c4c19d1e687aa113a9c976b16ec07f021ae53dea811fb8c1248a50ac34fbe009fdf6be + languageName: node + linkType: hard + +"@jest/transform@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/transform@npm:29.7.0" + dependencies: + "@babel/core": "npm:^7.11.6" + "@jest/types": "npm:^29.6.3" + "@jridgewell/trace-mapping": "npm:^0.3.18" + babel-plugin-istanbul: "npm:^6.1.1" + chalk: "npm:^4.0.0" + convert-source-map: "npm:^2.0.0" + fast-json-stable-stringify: "npm:^2.1.0" + graceful-fs: "npm:^4.2.9" + jest-haste-map: "npm:^29.7.0" + jest-regex-util: "npm:^29.6.3" + jest-util: "npm:^29.7.0" + micromatch: "npm:^4.0.4" + pirates: "npm:^4.0.4" + slash: "npm:^3.0.0" + write-file-atomic: "npm:^4.0.2" + checksum: 10c0/7f4a7f73dcf45dfdf280c7aa283cbac7b6e5a904813c3a93ead7e55873761fc20d5c4f0191d2019004fac6f55f061c82eb3249c2901164ad80e362e7a7ede5a6 + languageName: node + linkType: hard + +"@jest/types@npm:27.5.1": + version: 27.5.1 + resolution: "@jest/types@npm:27.5.1" + dependencies: + "@types/istanbul-lib-coverage": "npm:^2.0.0" + "@types/istanbul-reports": "npm:^3.0.0" + "@types/node": "npm:*" + "@types/yargs": "npm:^16.0.0" + chalk: "npm:^4.0.0" + checksum: 10c0/4598b302398db0eb77168b75a6c58148ea02cc9b9f21c5d1bbe985c1c9257110a5653cf7b901c3cab87fba231e3fed83633687f1c0903b4bc6939ab2a8452504 + languageName: node + linkType: hard + +"@jest/types@npm:^24.9.0": + version: 24.9.0 + resolution: "@jest/types@npm:24.9.0" + dependencies: + "@types/istanbul-lib-coverage": "npm:^2.0.0" + "@types/istanbul-reports": "npm:^1.1.1" + "@types/yargs": "npm:^13.0.0" + checksum: 10c0/990b03f5e27de292a7fea6b12cd87256dd281263afe37020cad5dceb0b775945a528bafdbc2e41bf8a29c346f94a7aa5580517c5c65a2b33f245f43d3b9b4694 + languageName: node + linkType: hard + +"@jest/types@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/types@npm:29.6.3" + dependencies: + "@jest/schemas": "npm:^29.6.3" + "@types/istanbul-lib-coverage": "npm:^2.0.0" + "@types/istanbul-reports": "npm:^3.0.0" + "@types/node": "npm:*" + "@types/yargs": "npm:^17.0.8" + chalk: "npm:^4.0.0" + checksum: 10c0/ea4e493dd3fb47933b8ccab201ae573dcc451f951dc44ed2a86123cd8541b82aa9d2b1031caf9b1080d6673c517e2dcc25a44b2dc4f3fbc37bfc965d444888c0 + languageName: node + linkType: hard + +"@jridgewell/gen-mapping@npm:^0.3.12, @jridgewell/gen-mapping@npm:^0.3.2, @jridgewell/gen-mapping@npm:^0.3.5": + version: 0.3.13 + resolution: "@jridgewell/gen-mapping@npm:0.3.13" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.0" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/9a7d65fb13bd9aec1fbab74cda08496839b7e2ceb31f5ab922b323e94d7c481ce0fc4fd7e12e2610915ed8af51178bdc61e168e92a8c8b8303b030b03489b13b + languageName: node + linkType: hard + +"@jridgewell/remapping@npm:^2.3.5": + version: 2.3.5 + resolution: "@jridgewell/remapping@npm:2.3.5" + dependencies: + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/3de494219ffeb2c5c38711d0d7bb128097edf91893090a2dbc8ee0b55d092bb7347b1fd0f478486c5eab010e855c73927b1666f2107516d472d24a73017d1194 + languageName: node + linkType: hard + +"@jridgewell/resolve-uri@npm:^3.1.0": + version: 3.1.2 + resolution: "@jridgewell/resolve-uri@npm:3.1.2" + checksum: 10c0/d502e6fb516b35032331406d4e962c21fe77cdf1cbdb49c6142bcbd9e30507094b18972778a6e27cbad756209cfe34b1a27729e6fa08a2eb92b33943f680cf1e + languageName: node + linkType: hard + +"@jridgewell/source-map@npm:^0.3.3": + version: 0.3.11 + resolution: "@jridgewell/source-map@npm:0.3.11" + dependencies: + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.25" + checksum: 10c0/50a4fdafe0b8f655cb2877e59fe81320272eaa4ccdbe6b9b87f10614b2220399ae3e05c16137a59db1f189523b42c7f88bd097ee991dbd7bc0e01113c583e844 + languageName: node + linkType: hard + +"@jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.5.0": + version: 1.5.5 + resolution: "@jridgewell/sourcemap-codec@npm:1.5.5" + checksum: 10c0/f9e538f302b63c0ebc06eecb1dd9918dd4289ed36147a0ddce35d6ea4d7ebbda243cda7b2213b6a5e1d8087a298d5cf630fb2bd39329cdecb82017023f6081a0 + languageName: node + linkType: hard + +"@jridgewell/trace-mapping@npm:^0.3.18, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25, @jridgewell/trace-mapping@npm:^0.3.28": + version: 0.3.31 + resolution: "@jridgewell/trace-mapping@npm:0.3.31" + dependencies: + "@jridgewell/resolve-uri": "npm:^3.1.0" + "@jridgewell/sourcemap-codec": "npm:^1.4.14" + checksum: 10c0/4b30ec8cd56c5fd9a661f088230af01e0c1a3888d11ffb6b47639700f71225be21d1f7e168048d6d4f9449207b978a235c07c8f15c07705685d16dc06280e9d9 + languageName: node + linkType: hard + +"@keyv/bigmap@npm:^1.3.1": + version: 1.3.1 + resolution: "@keyv/bigmap@npm:1.3.1" + dependencies: + hashery: "npm:^1.4.0" + hookified: "npm:^1.15.0" + peerDependencies: + keyv: ^5.6.0 + checksum: 10c0/acc6a4a5edf462ce23e95672ab4bfaf7cd1941dff6bf3a2f671ce467961ace1fac9d3eb75a9ed9a8e92012e00a7d8b16ad1677bc539a52c3ad0cec31473e2349 + languageName: node + linkType: hard + +"@keyv/serialize@npm:^1.1.1": + version: 1.1.1 + resolution: "@keyv/serialize@npm:1.1.1" + checksum: 10c0/b0008cae4a54400c3abf587b8cc2474c6f528ee58969ce6cf9cb07a04006f80c73c85971d6be6544408318a2bc40108236a19a82aea0a6de95aae49533317374 + languageName: node + linkType: hard + +"@loadable/component@npm:5.15.2": + version: 5.15.2 + resolution: "@loadable/component@npm:5.15.2" + dependencies: + "@babel/runtime": "npm:^7.7.7" + hoist-non-react-statics: "npm:^3.3.1" + react-is: "npm:^16.12.0" + peerDependencies: + react: ">=16.3.0" + checksum: 10c0/d682c8bdff088b5093ac183782eca6e479aec427ede274b248cf0c887292564c3099faf97411dee33ef1f54deff06728ae07ffce13bee75b1a8bfa9be8e5f8bc + languageName: node + linkType: hard + +"@malept/cross-spawn-promise@npm:^1.1.0": + version: 1.1.1 + resolution: "@malept/cross-spawn-promise@npm:1.1.1" + dependencies: + cross-spawn: "npm:^7.0.1" + checksum: 10c0/74c427a152ffff0f19b74af6479d05bef1e996d5e081cfc3b8c47477b9240bd1c42a930884cbcd0c89ee3835201a3bd88d0b0bfd754c0cbb56fc84a28996a8e7 + languageName: node + linkType: hard + +"@malept/flatpak-bundler@npm:^0.4.0": + version: 0.4.0 + resolution: "@malept/flatpak-bundler@npm:0.4.0" + dependencies: + debug: "npm:^4.1.1" + fs-extra: "npm:^9.0.0" + lodash: "npm:^4.17.15" + tmp-promise: "npm:^3.0.2" + checksum: 10c0/b3c87f6482b1956411af1118c771afb39cd9a0568fbb5e86015547ff6d68d2e73a7f0d74b75a57f0a156391c347c8d0adc1037e75172b92da72b96e0a05a2f4f + languageName: node + linkType: hard + +"@module-federation/error-codes@npm:0.8.12": + version: 0.8.12 + resolution: "@module-federation/error-codes@npm:0.8.12" + checksum: 10c0/dd767f34cb518b4862a84c92f816ad90c44935fa33be93b25f9485bec8e40579f433c50a6b0ec23d509e1c453eb619d0a9b7b493f7df3d6a8200e9ce2690ec1e + languageName: node + linkType: hard + +"@module-federation/runtime-core@npm:0.6.20": + version: 0.6.20 + resolution: "@module-federation/runtime-core@npm:0.6.20" + dependencies: + "@module-federation/error-codes": "npm:0.8.12" + "@module-federation/sdk": "npm:0.8.12" + checksum: 10c0/5a850220e98eb7ec17db9086655177e1eaf192b9145058e93c3c9db6a4724b48383e314223b198ba9dec5432753b6fe25075b7471c95c97f39966e51d598ec1f + languageName: node + linkType: hard + +"@module-federation/runtime@npm:0.8.12": + version: 0.8.12 + resolution: "@module-federation/runtime@npm:0.8.12" + dependencies: + "@module-federation/error-codes": "npm:0.8.12" + "@module-federation/runtime-core": "npm:0.6.20" + "@module-federation/sdk": "npm:0.8.12" + checksum: 10c0/f1f85e8cb66fba1b3e2062fdca1fb40b33f0902e990298a1aaeb3049c7a3f5466e92311fad9658db8ab449aaaa0459c1bf0b3b1ecf61962761781c3b7c3c8ce8 + languageName: node + linkType: hard + +"@module-federation/sdk@npm:0.8.12": + version: 0.8.12 + resolution: "@module-federation/sdk@npm:0.8.12" + dependencies: + isomorphic-rslog: "npm:0.0.7" + checksum: 10c0/8e8867d2520d0b63db37746821da6969a968a4661c71e199c9ef3e4a3af9fb667abcc2e99e30ca4c06cf46b7c67a9ec089754f1a12775f76c062954f56095b09 + languageName: node + linkType: hard + +"@module-federation/webpack-bundler-runtime@npm:^0.8.0": + version: 0.8.12 + resolution: "@module-federation/webpack-bundler-runtime@npm:0.8.12" + dependencies: + "@module-federation/runtime": "npm:0.8.12" + "@module-federation/sdk": "npm:0.8.12" + checksum: 10c0/442d3dd6e44d4063e181ec58cba0fb6df677e1ffa7258f8ddb8e774cf6bca84f6aee7859e44d59548c0079cbd96aa5832506a64235b118089f8577cccf4f25a3 + languageName: node + linkType: hard + +"@napi-rs/nice-android-arm-eabi@npm:1.1.1": + version: 1.1.1 + resolution: "@napi-rs/nice-android-arm-eabi@npm:1.1.1" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@napi-rs/nice-android-arm64@npm:1.1.1": + version: 1.1.1 + resolution: "@napi-rs/nice-android-arm64@npm:1.1.1" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@napi-rs/nice-darwin-arm64@npm:1.1.1": + version: 1.1.1 + resolution: "@napi-rs/nice-darwin-arm64@npm:1.1.1" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@napi-rs/nice-darwin-x64@npm:1.1.1": + version: 1.1.1 + resolution: "@napi-rs/nice-darwin-x64@npm:1.1.1" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@napi-rs/nice-freebsd-x64@npm:1.1.1": + version: 1.1.1 + resolution: "@napi-rs/nice-freebsd-x64@npm:1.1.1" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@napi-rs/nice-linux-arm-gnueabihf@npm:1.1.1": + version: 1.1.1 + resolution: "@napi-rs/nice-linux-arm-gnueabihf@npm:1.1.1" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@napi-rs/nice-linux-arm64-gnu@npm:1.1.1": + version: 1.1.1 + resolution: "@napi-rs/nice-linux-arm64-gnu@npm:1.1.1" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@napi-rs/nice-linux-arm64-musl@npm:1.1.1": + version: 1.1.1 + resolution: "@napi-rs/nice-linux-arm64-musl@npm:1.1.1" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@napi-rs/nice-linux-ppc64-gnu@npm:1.1.1": + version: 1.1.1 + resolution: "@napi-rs/nice-linux-ppc64-gnu@npm:1.1.1" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@napi-rs/nice-linux-riscv64-gnu@npm:1.1.1": + version: 1.1.1 + resolution: "@napi-rs/nice-linux-riscv64-gnu@npm:1.1.1" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@napi-rs/nice-linux-s390x-gnu@npm:1.1.1": + version: 1.1.1 + resolution: "@napi-rs/nice-linux-s390x-gnu@npm:1.1.1" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@napi-rs/nice-linux-x64-gnu@npm:1.1.1": + version: 1.1.1 + resolution: "@napi-rs/nice-linux-x64-gnu@npm:1.1.1" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@napi-rs/nice-linux-x64-musl@npm:1.1.1": + version: 1.1.1 + resolution: "@napi-rs/nice-linux-x64-musl@npm:1.1.1" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@napi-rs/nice-openharmony-arm64@npm:1.1.1": + version: 1.1.1 + resolution: "@napi-rs/nice-openharmony-arm64@npm:1.1.1" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@napi-rs/nice-win32-arm64-msvc@npm:1.1.1": + version: 1.1.1 + resolution: "@napi-rs/nice-win32-arm64-msvc@npm:1.1.1" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@napi-rs/nice-win32-ia32-msvc@npm:1.1.1": + version: 1.1.1 + resolution: "@napi-rs/nice-win32-ia32-msvc@npm:1.1.1" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@napi-rs/nice-win32-x64-msvc@npm:1.1.1": + version: 1.1.1 + resolution: "@napi-rs/nice-win32-x64-msvc@npm:1.1.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@napi-rs/nice@npm:^1.0.1": + version: 1.1.1 + resolution: "@napi-rs/nice@npm:1.1.1" + dependencies: + "@napi-rs/nice-android-arm-eabi": "npm:1.1.1" + "@napi-rs/nice-android-arm64": "npm:1.1.1" + "@napi-rs/nice-darwin-arm64": "npm:1.1.1" + "@napi-rs/nice-darwin-x64": "npm:1.1.1" + "@napi-rs/nice-freebsd-x64": "npm:1.1.1" + "@napi-rs/nice-linux-arm-gnueabihf": "npm:1.1.1" + "@napi-rs/nice-linux-arm64-gnu": "npm:1.1.1" + "@napi-rs/nice-linux-arm64-musl": "npm:1.1.1" + "@napi-rs/nice-linux-ppc64-gnu": "npm:1.1.1" + "@napi-rs/nice-linux-riscv64-gnu": "npm:1.1.1" + "@napi-rs/nice-linux-s390x-gnu": "npm:1.1.1" + "@napi-rs/nice-linux-x64-gnu": "npm:1.1.1" + "@napi-rs/nice-linux-x64-musl": "npm:1.1.1" + "@napi-rs/nice-openharmony-arm64": "npm:1.1.1" + "@napi-rs/nice-win32-arm64-msvc": "npm:1.1.1" + "@napi-rs/nice-win32-ia32-msvc": "npm:1.1.1" + "@napi-rs/nice-win32-x64-msvc": "npm:1.1.1" + dependenciesMeta: + "@napi-rs/nice-android-arm-eabi": + optional: true + "@napi-rs/nice-android-arm64": + optional: true + "@napi-rs/nice-darwin-arm64": + optional: true + "@napi-rs/nice-darwin-x64": + optional: true + "@napi-rs/nice-freebsd-x64": + optional: true + "@napi-rs/nice-linux-arm-gnueabihf": + optional: true + "@napi-rs/nice-linux-arm64-gnu": + optional: true + "@napi-rs/nice-linux-arm64-musl": + optional: true + "@napi-rs/nice-linux-ppc64-gnu": + optional: true + "@napi-rs/nice-linux-riscv64-gnu": + optional: true + "@napi-rs/nice-linux-s390x-gnu": + optional: true + "@napi-rs/nice-linux-x64-gnu": + optional: true + "@napi-rs/nice-linux-x64-musl": + optional: true + "@napi-rs/nice-openharmony-arm64": + optional: true + "@napi-rs/nice-win32-arm64-msvc": + optional: true + "@napi-rs/nice-win32-ia32-msvc": + optional: true + "@napi-rs/nice-win32-x64-msvc": + optional: true + checksum: 10c0/517eacfd5d5de191f1469a6caad9f9e26924b25079550149fc792fb09d15184013a8a81966a666f08c0a93fbb17a458d50ba9e2e9d6a61141c6c515d083733b2 + languageName: node + linkType: hard + +"@nicolo-ribaudo/eslint-scope-5-internals@npm:5.1.1-v1": + version: 5.1.1-v1 + resolution: "@nicolo-ribaudo/eslint-scope-5-internals@npm:5.1.1-v1" + dependencies: + eslint-scope: "npm:5.1.1" + checksum: 10c0/75dda3e623b8ad7369ca22552d6beee337a814b2d0e8a32d23edd13fcb65c8082b32c5d86e436f3860dd7ade30d91d5db55d4ef9a08fb5a976c718ecc0d88a74 + languageName: node + linkType: hard + +"@nodelib/fs.scandir@npm:2.1.5": + version: 2.1.5 + resolution: "@nodelib/fs.scandir@npm:2.1.5" + dependencies: + "@nodelib/fs.stat": "npm:2.0.5" + run-parallel: "npm:^1.1.9" + checksum: 10c0/732c3b6d1b1e967440e65f284bd06e5821fedf10a1bea9ed2bb75956ea1f30e08c44d3def9d6a230666574edbaf136f8cfd319c14fd1f87c66e6a44449afb2eb + languageName: node + linkType: hard + +"@nodelib/fs.stat@npm:2.0.5, @nodelib/fs.stat@npm:^2.0.2": + version: 2.0.5 + resolution: "@nodelib/fs.stat@npm:2.0.5" + checksum: 10c0/88dafe5e3e29a388b07264680dc996c17f4bda48d163a9d4f5c1112979f0ce8ec72aa7116122c350b4e7976bc5566dc3ddb579be1ceaacc727872eb4ed93926d + languageName: node + linkType: hard + +"@nodelib/fs.walk@npm:^1.2.3, @nodelib/fs.walk@npm:^1.2.8": + version: 1.2.8 + resolution: "@nodelib/fs.walk@npm:1.2.8" + dependencies: + "@nodelib/fs.scandir": "npm:2.1.5" + fastq: "npm:^1.6.0" + checksum: 10c0/db9de047c3bb9b51f9335a7bb46f4fcfb6829fb628318c12115fbaf7d369bfce71c15b103d1fc3b464812d936220ee9bc1c8f762d032c9f6be9acc99249095b1 + languageName: node + linkType: hard + +"@npmcli/agent@npm:^4.0.0": + version: 4.0.0 + resolution: "@npmcli/agent@npm:4.0.0" + dependencies: + agent-base: "npm:^7.1.0" + http-proxy-agent: "npm:^7.0.0" + https-proxy-agent: "npm:^7.0.1" + lru-cache: "npm:^11.2.1" + socks-proxy-agent: "npm:^8.0.3" + checksum: 10c0/f7b5ce0f3dd42c3f8c6546e8433573d8049f67ef11ec22aa4704bc41483122f68bf97752e06302c455ead667af5cb753e6a09bff06632bc465c1cfd4c4b75a53 + languageName: node + linkType: hard + +"@npmcli/fs@npm:^5.0.0": + version: 5.0.0 + resolution: "@npmcli/fs@npm:5.0.0" + dependencies: + semver: "npm:^7.3.5" + checksum: 10c0/26e376d780f60ff16e874a0ac9bc3399186846baae0b6e1352286385ac134d900cc5dafaded77f38d77f86898fc923ae1cee9d7399f0275b1aa24878915d722b + languageName: node + linkType: hard + +"@pkgjs/parseargs@npm:^0.11.0": + version: 0.11.0 + resolution: "@pkgjs/parseargs@npm:0.11.0" + checksum: 10c0/5bd7576bb1b38a47a7fc7b51ac9f38748e772beebc56200450c4a817d712232b8f1d3ef70532c80840243c657d491cf6a6be1e3a214cff907645819fdc34aadd + languageName: node + linkType: hard + +"@pkgr/core@npm:^0.2.9": + version: 0.2.9 + resolution: "@pkgr/core@npm:0.2.9" + checksum: 10c0/ac8e4e8138b1a7a4ac6282873aef7389c352f1f8b577b4850778f5182e4a39a5241facbe48361fec817f56d02b51691b383010843fb08b34a8e8ea3614688fd5 + languageName: node + linkType: hard + +"@pkgr/utils@npm:^2.3.1": + version: 2.4.2 + resolution: "@pkgr/utils@npm:2.4.2" + dependencies: + cross-spawn: "npm:^7.0.3" + fast-glob: "npm:^3.3.0" + is-glob: "npm:^4.0.3" + open: "npm:^9.1.0" + picocolors: "npm:^1.0.0" + tslib: "npm:^2.6.0" + checksum: 10c0/7c3e68f6405a1d4c51f418d8d580e71d7bade2683d5db07e8413d8e57f7e389047eda44a2341f77a1b3085895fca7676a9d45e8812a58312524f8c4c65d501be + languageName: node + linkType: hard + +"@popperjs/core@npm:^2.9.1": + version: 2.11.8 + resolution: "@popperjs/core@npm:2.11.8" + checksum: 10c0/4681e682abc006d25eb380d0cf3efc7557043f53b6aea7a5057d0d1e7df849a00e281cd8ea79c902a35a414d7919621fc2ba293ecec05f413598e0b23d5a1e63 + languageName: node + linkType: hard + +"@rc-component/async-validator@npm:^5.0.3": + version: 5.0.4 + resolution: "@rc-component/async-validator@npm:5.0.4" + dependencies: + "@babel/runtime": "npm:^7.24.4" + checksum: 10c0/e874f87e67228d897801360ef1dbd9f3677e10c57c4b4c708c67f9929f44d8c36ca70ff3967e3fc6ef20b87982c966b27695df44899ca50588240e07130fe2ba + languageName: node + linkType: hard + +"@rc-component/color-picker@npm:~2.0.1": + version: 2.0.1 + resolution: "@rc-component/color-picker@npm:2.0.1" + dependencies: + "@ant-design/fast-color": "npm:^2.0.6" + "@babel/runtime": "npm:^7.23.6" + classnames: "npm:^2.2.6" + rc-util: "npm:^5.38.1" + peerDependencies: + react: ">=16.9.0" + react-dom: ">=16.9.0" + checksum: 10c0/be0b851a609fdb360367f38765b568c52f935dde4e70f7b832146a921eb221ddd51735144a35ce36c55810f47f830078e271508920585e3ab789e47c4f824775 + languageName: node + linkType: hard + +"@rc-component/context@npm:^1.4.0": + version: 1.4.0 + resolution: "@rc-component/context@npm:1.4.0" + dependencies: + "@babel/runtime": "npm:^7.10.1" + rc-util: "npm:^5.27.0" + peerDependencies: + react: ">=16.9.0" + react-dom: ">=16.9.0" + checksum: 10c0/4da3773deca15107adccf6635c703663f5a9202c09d8877ee003ccf144de9991c5eefbca4458f31d95d234e57babf0f8f1926e0b887f8a503a43119a39a653aa + languageName: node + linkType: hard + +"@rc-component/mini-decimal@npm:^1.0.1": + version: 1.1.0 + resolution: "@rc-component/mini-decimal@npm:1.1.0" + dependencies: + "@babel/runtime": "npm:^7.18.0" + checksum: 10c0/53a7ca71591bc03eba71ab844016df788e83c96c3c7c542710c3eeeae7f55340c88c4930d7a0b11ebe7f1cd9fc65cb5bc284f466fbe95589992dd9833edf6ddf + languageName: node + linkType: hard + +"@rc-component/mutate-observer@npm:^1.1.0": + version: 1.1.0 + resolution: "@rc-component/mutate-observer@npm:1.1.0" + dependencies: + "@babel/runtime": "npm:^7.18.0" + classnames: "npm:^2.3.2" + rc-util: "npm:^5.24.4" + peerDependencies: + react: ">=16.9.0" + react-dom: ">=16.9.0" + checksum: 10c0/90159acd831ed04b7d2d412354892fd042ad51e63bf17a9fcfa84f3b799e23479617fcc7404566a5b97e5b512b6097b87b8eb36e0c419d803692796a39186a2e + languageName: node + linkType: hard + +"@rc-component/portal@npm:^1.0.0-8, @rc-component/portal@npm:^1.0.0-9, @rc-component/portal@npm:^1.0.2, @rc-component/portal@npm:^1.1.0, @rc-component/portal@npm:^1.1.1": + version: 1.1.2 + resolution: "@rc-component/portal@npm:1.1.2" + dependencies: + "@babel/runtime": "npm:^7.18.0" + classnames: "npm:^2.3.2" + rc-util: "npm:^5.24.4" + peerDependencies: + react: ">=16.9.0" + react-dom: ">=16.9.0" + checksum: 10c0/3c0297356635d47f364be79de02bb16009f06b1ce82124c3e63da9a71b8e7d3ea2c147e4703ead9cae0f662435a21e9feb30d2edf7197108bc3dbf969f3ca51f + languageName: node + linkType: hard + +"@rc-component/qrcode@npm:~1.1.0": + version: 1.1.0 + resolution: "@rc-component/qrcode@npm:1.1.0" + dependencies: + "@babel/runtime": "npm:^7.24.7" + classnames: "npm:^2.3.2" + peerDependencies: + react: ">=16.9.0" + react-dom: ">=16.9.0" + checksum: 10c0/24c3bde6a16735f84330c0bfc499b4c912e31e90894e26a1a2b64ce5374b79de0c5328c7d731109508eed583ec6d5c085581cba665a7e69debb8d7ca7c3fcf84 + languageName: node + linkType: hard + +"@rc-component/tour@npm:~1.15.1": + version: 1.15.1 + resolution: "@rc-component/tour@npm:1.15.1" + dependencies: + "@babel/runtime": "npm:^7.18.0" + "@rc-component/portal": "npm:^1.0.0-9" + "@rc-component/trigger": "npm:^2.0.0" + classnames: "npm:^2.3.2" + rc-util: "npm:^5.24.4" + peerDependencies: + react: ">=16.9.0" + react-dom: ">=16.9.0" + checksum: 10c0/8324edb96bbca2838e9bdcca5ac02e615596593d0e79ee74ad2e7d3a4801975f205907170bb16f92303b22701bc730c34337cb10270281cea36659bac10193b5 + languageName: node + linkType: hard + +"@rc-component/trigger@npm:^2.0.0, @rc-component/trigger@npm:^2.1.1, @rc-component/trigger@npm:^2.3.0": + version: 2.3.0 + resolution: "@rc-component/trigger@npm:2.3.0" + dependencies: + "@babel/runtime": "npm:^7.23.2" + "@rc-component/portal": "npm:^1.1.0" + classnames: "npm:^2.3.2" + rc-motion: "npm:^2.0.0" + rc-resize-observer: "npm:^1.3.1" + rc-util: "npm:^5.44.0" + peerDependencies: + react: ">=16.9.0" + react-dom: ">=16.9.0" + checksum: 10c0/84a0f3da8beca249ac86d2870ef9e1603ed26ff6b869a545e6d5c3793f23413cf6a11fafe001406a86c49917d31ec105776402dba834f1b79db98305ff8bd2f4 + languageName: node + linkType: hard + +"@rtsao/scc@npm:^1.1.0": + version: 1.1.0 + resolution: "@rtsao/scc@npm:1.1.0" + checksum: 10c0/b5bcfb0d87f7d1c1c7c0f7693f53b07866ed9fec4c34a97a8c948fb9a7c0082e416ce4d3b60beb4f5e167cbe04cdeefbf6771320f3ede059b9ce91188c409a5b + languageName: node + linkType: hard + +"@sinclair/typebox@npm:^0.27.8": + version: 0.27.8 + resolution: "@sinclair/typebox@npm:0.27.8" + checksum: 10c0/ef6351ae073c45c2ac89494dbb3e1f87cc60a93ce4cde797b782812b6f97da0d620ae81973f104b43c9b7eaa789ad20ba4f6a1359f1cc62f63729a55a7d22d4e + languageName: node + linkType: hard + +"@sindresorhus/is@npm:^4.0.0": + version: 4.6.0 + resolution: "@sindresorhus/is@npm:4.6.0" + checksum: 10c0/33b6fb1d0834ec8dd7689ddc0e2781c2bfd8b9c4e4bacbcb14111e0ae00621f2c264b8a7d36541799d74888b5dccdf422a891a5cb5a709ace26325eedc81e22e + languageName: node + linkType: hard + +"@sindresorhus/merge-streams@npm:^4.0.0": + version: 4.0.0 + resolution: "@sindresorhus/merge-streams@npm:4.0.0" + checksum: 10c0/482ee543629aa1933b332f811a1ae805a213681ecdd98c042b1c1b89387df63e7812248bb4df3910b02b3cc5589d3d73e4393f30e197c9dde18046ccd471fc6b + languageName: node + linkType: hard + +"@stagewise/toolbar@npm:0.6.2": + version: 0.6.2 + resolution: "@stagewise/toolbar@npm:0.6.2" + checksum: 10c0/374ce08ce379521e148dd301da1bcbabf7fd4d1b6c3410c037b52d8f88a9a1c7b67ec7d500f4a31dfa2b8a06fb000682d12d9765d6d88cdaaccbae1ce6689ab1 + languageName: node + linkType: hard + +"@stylelint/postcss-css-in-js@npm:^0.38.0": + version: 0.38.0 + resolution: "@stylelint/postcss-css-in-js@npm:0.38.0" + dependencies: + "@babel/core": "npm:^7.17.9" + peerDependencies: + postcss: ">=7.0.0" + postcss-syntax: ">=0.36.2" + checksum: 10c0/74f2fdf177b50b4fbfaa4aa78c3916e2bc3a1e07a23f931ff7b4cf059de9458986f1e24df49f1f8ec61132ed74f09bed0a4e5d25b221cf6e2b9c45fccd76c510 + languageName: node + linkType: hard + +"@svgr/babel-plugin-add-jsx-attribute@npm:^6.5.1": + version: 6.5.1 + resolution: "@svgr/babel-plugin-add-jsx-attribute@npm:6.5.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/a13ed0797189d5497890530449029bec388310e260a96459e304e2729e7a2cf4d20d34f882d9a77ccce73dd3d36065afbb6987258fdff618d7d57955065a8ad4 + languageName: node + linkType: hard + +"@svgr/babel-plugin-remove-jsx-attribute@npm:*": + version: 8.0.0 + resolution: "@svgr/babel-plugin-remove-jsx-attribute@npm:8.0.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/8a98e59bd9971e066815b4129409932f7a4db4866834fe75677ea6d517972fb40b380a69a4413189f20e7947411f9ab1b0f029dd5e8068686a5a0188d3ccd4c7 + languageName: node + linkType: hard + +"@svgr/babel-plugin-remove-jsx-empty-expression@npm:*": + version: 8.0.0 + resolution: "@svgr/babel-plugin-remove-jsx-empty-expression@npm:8.0.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/517dcca75223bd05d3f056a8514dbba3031278bea4eadf0842c576d84f4651e7a4e0e7082d3ee4ef42456de0f9c4531d8a1917c04876ca64b014b859ca8f1bde + languageName: node + linkType: hard + +"@svgr/babel-plugin-replace-jsx-attribute-value@npm:^6.5.1": + version: 6.5.1 + resolution: "@svgr/babel-plugin-replace-jsx-attribute-value@npm:6.5.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/318786787c9a217c33a7340c8856436858e1fffa5a6df635fedc6b9a371f3afea080ea074b9e3cfbbd9dd962ead924fde8bc9855a394c38dd60e391883a58c81 + languageName: node + linkType: hard + +"@svgr/babel-plugin-svg-dynamic-title@npm:^6.5.1": + version: 6.5.1 + resolution: "@svgr/babel-plugin-svg-dynamic-title@npm:6.5.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/16ef228c793b909fec47dd7dc05c1c3c2d77a824f42055df37e141e0534081b1bc4aec6dcc51be50c221df9f262f59270fc1c379923bfd4f5db302abafabfd8d + languageName: node + linkType: hard + +"@svgr/babel-plugin-svg-em-dimensions@npm:^6.5.1": + version: 6.5.1 + resolution: "@svgr/babel-plugin-svg-em-dimensions@npm:6.5.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/dfdd5cbe6ae543505eaa0da69df0735b7407294c4b0504b3e74c0e7e371f1acb914eb99fd21ff39ef5bd626b3474f064a4cccc50f41b7c556ee834f9a6d6610a + languageName: node + linkType: hard + +"@svgr/babel-plugin-transform-react-native-svg@npm:^6.5.1": + version: 6.5.1 + resolution: "@svgr/babel-plugin-transform-react-native-svg@npm:6.5.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/332fbf3bbc19d938b744440dbab9c8acd8f7a2ed6bf9c4e23f40e3f2c25615a60b3bf00902a4f1f6c20b5f382a1547b3acc6f2b2d70d80e532b5d45945f1b979 + languageName: node + linkType: hard + +"@svgr/babel-plugin-transform-svg-component@npm:^6.5.1": + version: 6.5.1 + resolution: "@svgr/babel-plugin-transform-svg-component@npm:6.5.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/8d9e1c7c62abce23837e53cdacc6d09bc1f1f2b0ad7322105001c097995e9aa8dca4fa41acf39148af69f342e40081c438106949fb083e997ca497cb0448f27d + languageName: node + linkType: hard + +"@svgr/babel-preset@npm:^6.5.1": + version: 6.5.1 + resolution: "@svgr/babel-preset@npm:6.5.1" + dependencies: + "@svgr/babel-plugin-add-jsx-attribute": "npm:^6.5.1" + "@svgr/babel-plugin-remove-jsx-attribute": "npm:*" + "@svgr/babel-plugin-remove-jsx-empty-expression": "npm:*" + "@svgr/babel-plugin-replace-jsx-attribute-value": "npm:^6.5.1" + "@svgr/babel-plugin-svg-dynamic-title": "npm:^6.5.1" + "@svgr/babel-plugin-svg-em-dimensions": "npm:^6.5.1" + "@svgr/babel-plugin-transform-react-native-svg": "npm:^6.5.1" + "@svgr/babel-plugin-transform-svg-component": "npm:^6.5.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/8e8d7a0049279152f9ac308fbfd4ce74063d8a376154718cba6309bae4316318804a32201c75c5839c629f8e1e5d641a87822764000998161d0fc1de24b0374a + languageName: node + linkType: hard + +"@svgr/core@npm:6.5.1": + version: 6.5.1 + resolution: "@svgr/core@npm:6.5.1" + dependencies: + "@babel/core": "npm:^7.19.6" + "@svgr/babel-preset": "npm:^6.5.1" + "@svgr/plugin-jsx": "npm:^6.5.1" + camelcase: "npm:^6.2.0" + cosmiconfig: "npm:^7.0.1" + checksum: 10c0/60cce11e13391171132115dcc8da592d23e51f155ebadf9b819bd1836b8c13d40aa5c30a03a7d429f65e70a71c50669b2e10c94e4922de4e58bc898275f46c05 + languageName: node + linkType: hard + +"@svgr/hast-util-to-babel-ast@npm:^6.5.1": + version: 6.5.1 + resolution: "@svgr/hast-util-to-babel-ast@npm:6.5.1" + dependencies: + "@babel/types": "npm:^7.20.0" + entities: "npm:^4.4.0" + checksum: 10c0/18fa37b36581ba1678f5cc5a05ce0411e08df4db267f3cd900af7ffdf5bd90522f3a46465f315cd5d7345264949479133930aafdd27ce05c474e63756196256f + languageName: node + linkType: hard + +"@svgr/plugin-jsx@npm:^6.5.1": + version: 6.5.1 + resolution: "@svgr/plugin-jsx@npm:6.5.1" + dependencies: + "@babel/core": "npm:^7.19.6" + "@svgr/babel-preset": "npm:^6.5.1" + "@svgr/hast-util-to-babel-ast": "npm:^6.5.1" + svg-parser: "npm:^2.0.4" + peerDependencies: + "@svgr/core": ^6.0.0 + checksum: 10c0/365da6e43ceeff6b49258fa2fbb3c880210300e4a85ba74831e92d2dc9c53e6ab8dda422dc33fb6a339803227cf8d9a0024ce769401c46fd87209abe36d5ae43 + languageName: node + linkType: hard + +"@svgr/plugin-svgo@npm:^6.5.1": + version: 6.5.1 + resolution: "@svgr/plugin-svgo@npm:6.5.1" + dependencies: + cosmiconfig: "npm:^7.0.1" + deepmerge: "npm:^4.2.2" + svgo: "npm:^2.8.0" + peerDependencies: + "@svgr/core": "*" + checksum: 10c0/da40e461145af1a92fd2ec50ea64626681fa73786f218497a4b4fb85393a58812999ca2744ee33bb7ab771aa5ce9ab1dbd08a189cb3d7a89fb58fd96913ddf91 + languageName: node + linkType: hard + +"@swc/helpers@npm:0.5.1": + version: 0.5.1 + resolution: "@swc/helpers@npm:0.5.1" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/2e2272c8278351670e1daf27cc634ace793afb378dcc85be2800d30a7b4d3afad37707371ead2a6d96662fa30294da678d66cdc4dc7f3e698bd8e111235c60fc + languageName: node + linkType: hard + +"@swc/helpers@npm:0.5.15": + version: 0.5.15 + resolution: "@swc/helpers@npm:0.5.15" + dependencies: + tslib: "npm:^2.8.0" + checksum: 10c0/33002f74f6f885f04c132960835fdfc474186983ea567606db62e86acd0680ca82f34647e8e610f4e1e422d1c16fce729dde22cd3b797ab1fd9061a825dabca4 + languageName: node + linkType: hard + +"@szmarczak/http-timer@npm:^4.0.5": + version: 4.0.6 + resolution: "@szmarczak/http-timer@npm:4.0.6" + dependencies: + defer-to-connect: "npm:^2.0.0" + checksum: 10c0/73946918c025339db68b09abd91fa3001e87fc749c619d2e9c2003a663039d4c3cb89836c98a96598b3d47dec2481284ba85355392644911f5ecd2336536697f + languageName: node + linkType: hard + +"@tanstack/match-sorter-utils@npm:^8.7.0": + version: 8.19.4 + resolution: "@tanstack/match-sorter-utils@npm:8.19.4" + dependencies: + remove-accents: "npm:0.5.0" + checksum: 10c0/935022e3d639f19472131d289f3e1202253ff34301717c337e9bac0eeae6a0bd56450ed8ae2f7eb7ac9dfefa7ceaa7d126d8c5441021968b4a9eabc3ac4f8ba1 + languageName: node + linkType: hard + +"@tanstack/query-core@npm:4.41.0": + version: 4.41.0 + resolution: "@tanstack/query-core@npm:4.41.0" + checksum: 10c0/e8a502c855ac08006665293478a692dbbca39fddc1fff815d99c7dcfd01dec75705548237fe162d0db6c35f9c7bb9e8313aeaed81c952f244eed91cc1d54006c + languageName: node + linkType: hard + +"@tanstack/react-query-devtools@npm:^4.24.10": + version: 4.42.0 + resolution: "@tanstack/react-query-devtools@npm:4.42.0" + dependencies: + "@tanstack/match-sorter-utils": "npm:^8.7.0" + superjson: "npm:^1.10.0" + use-sync-external-store: "npm:^1.2.0" + peerDependencies: + "@tanstack/react-query": ^4.42.0 + 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 + checksum: 10c0/88e95d79469bd229c308680166df7a34425e1c4123a0716473b3584114e0f07d25777e1fa172ec17369f3ec3fec6dd7d9eff7f615f02ed79a9e3604d92cbc6db + languageName: node + linkType: hard + +"@tanstack/react-query@npm:^4.24.10": + version: 4.42.0 + resolution: "@tanstack/react-query@npm:4.42.0" + dependencies: + "@tanstack/query-core": "npm:4.41.0" + use-sync-external-store: "npm:^1.2.0" + 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 + react-native: "*" + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + checksum: 10c0/2258f4d592addc1c78061bc8eb059f1de3a0fa3cbf5106326a63122273dac64e96fe2a708cfbdb1b3e552810ebbe83ae27b1e5f1dec8cae77a195203c20cdcd5 + languageName: node + linkType: hard + +"@tootallnate/once@npm:2": + version: 2.0.0 + resolution: "@tootallnate/once@npm:2.0.0" + checksum: 10c0/073bfa548026b1ebaf1659eb8961e526be22fa77139b10d60e712f46d2f0f05f4e6c8bec62a087d41088ee9e29faa7f54838568e475ab2f776171003c3920858 + languageName: node + linkType: hard + +"@trysound/sax@npm:0.2.0": + version: 0.2.0 + resolution: "@trysound/sax@npm:0.2.0" + checksum: 10c0/44907308549ce775a41c38a815f747009ac45929a45d642b836aa6b0a536e4978d30b8d7d680bbd116e9dd73b7dbe2ef0d1369dcfc2d09e83ba381e485ecbe12 + languageName: node + linkType: hard + +"@types/babel__core@npm:^7.1.14": + version: 7.20.5 + resolution: "@types/babel__core@npm:7.20.5" + dependencies: + "@babel/parser": "npm:^7.20.7" + "@babel/types": "npm:^7.20.7" + "@types/babel__generator": "npm:*" + "@types/babel__template": "npm:*" + "@types/babel__traverse": "npm:*" + checksum: 10c0/bdee3bb69951e833a4b811b8ee9356b69a61ed5b7a23e1a081ec9249769117fa83aaaf023bb06562a038eb5845155ff663e2d5c75dd95c1d5ccc91db012868ff + languageName: node + linkType: hard + +"@types/babel__generator@npm:*": + version: 7.27.0 + resolution: "@types/babel__generator@npm:7.27.0" + dependencies: + "@babel/types": "npm:^7.0.0" + checksum: 10c0/9f9e959a8792df208a9d048092fda7e1858bddc95c6314857a8211a99e20e6830bdeb572e3587ae8be5429e37f2a96fcf222a9f53ad232f5537764c9e13a2bbd + languageName: node + linkType: hard + +"@types/babel__template@npm:*": + version: 7.4.4 + resolution: "@types/babel__template@npm:7.4.4" + dependencies: + "@babel/parser": "npm:^7.1.0" + "@babel/types": "npm:^7.0.0" + checksum: 10c0/cc84f6c6ab1eab1427e90dd2b76ccee65ce940b778a9a67be2c8c39e1994e6f5bbc8efa309f6cea8dc6754994524cd4d2896558df76d92e7a1f46ecffee7112b + languageName: node + linkType: hard + +"@types/babel__traverse@npm:*, @types/babel__traverse@npm:^7.0.6": + version: 7.28.0 + resolution: "@types/babel__traverse@npm:7.28.0" + dependencies: + "@babel/types": "npm:^7.28.2" + checksum: 10c0/b52d7d4e8fc6a9018fe7361c4062c1c190f5778cf2466817cb9ed19d69fbbb54f9a85ffedeb748ed8062d2cf7d4cc088ee739848f47c57740de1c48cbf0d0994 + languageName: node + linkType: hard + +"@types/cacheable-request@npm:^6.0.1": + version: 6.0.3 + resolution: "@types/cacheable-request@npm:6.0.3" + dependencies: + "@types/http-cache-semantics": "npm:*" + "@types/keyv": "npm:^3.1.4" + "@types/node": "npm:*" + "@types/responselike": "npm:^1.0.0" + checksum: 10c0/10816a88e4e5b144d43c1d15a81003f86d649776c7f410c9b5e6579d0ad9d4ca71c541962fb403077388b446e41af7ae38d313e46692144985f006ac5e11fa03 + languageName: node + linkType: hard + +"@types/classnames@npm:^2.2.9": + version: 2.3.0 + resolution: "@types/classnames@npm:2.3.0" + dependencies: + classnames: "npm:*" + checksum: 10c0/2a9eea8f5a9382b4aad2865f3ac414f298186447a45d53c227bc12d0c0e97b080765526632e8b2ae85013f28a275b42533fc60222a98ff556cce6efbd2d8d25b + languageName: node + linkType: hard + +"@types/d3-color@npm:*": + version: 3.1.3 + resolution: "@types/d3-color@npm:3.1.3" + checksum: 10c0/65eb0487de606eb5ad81735a9a5b3142d30bc5ea801ed9b14b77cb14c9b909f718c059f13af341264ee189acf171508053342142bdf99338667cea26a2d8d6ae + languageName: node + linkType: hard + +"@types/d3-drag@npm:^3.0.7": + version: 3.0.7 + resolution: "@types/d3-drag@npm:3.0.7" + dependencies: + "@types/d3-selection": "npm:*" + checksum: 10c0/65e29fa32a87c72d26c44b5e2df3bf15af21cd128386bcc05bcacca255927c0397d0cd7e6062aed5f0abd623490544a9d061c195f5ed9f018fe0b698d99c079d + languageName: node + linkType: hard + +"@types/d3-interpolate@npm:*, @types/d3-interpolate@npm:^3.0.4": + version: 3.0.4 + resolution: "@types/d3-interpolate@npm:3.0.4" + dependencies: + "@types/d3-color": "npm:*" + checksum: 10c0/066ebb8da570b518dd332df6b12ae3b1eaa0a7f4f0c702e3c57f812cf529cc3500ec2aac8dc094f31897790346c6b1ebd8cd7a077176727f4860c2b181a65ca4 + languageName: node + linkType: hard + +"@types/d3-selection@npm:*, @types/d3-selection@npm:^3.0.10": + version: 3.0.11 + resolution: "@types/d3-selection@npm:3.0.11" + checksum: 10c0/0c512956c7503ff5def4bb32e0c568cc757b9a2cc400a104fc0f4cfe5e56d83ebde2a97821b6f2cb26a7148079d3b86a2f28e11d68324ed311cf35c2ed980d1d + languageName: node + linkType: hard + +"@types/d3-transition@npm:^3.0.8": + version: 3.0.9 + resolution: "@types/d3-transition@npm:3.0.9" + dependencies: + "@types/d3-selection": "npm:*" + checksum: 10c0/4f68b9df7ac745b3491216c54203cbbfa0f117ae4c60e2609cdef2db963582152035407fdff995b10ee383bae2f05b7743493f48e1b8e46df54faa836a8fb7b5 + languageName: node + linkType: hard + +"@types/d3-zoom@npm:^3.0.8": + version: 3.0.8 + resolution: "@types/d3-zoom@npm:3.0.8" + dependencies: + "@types/d3-interpolate": "npm:*" + "@types/d3-selection": "npm:*" + checksum: 10c0/1dbdbcafddcae12efb5beb6948546963f29599e18bc7f2a91fb69cc617c2299a65354f2d47e282dfb86fec0968406cd4fb7f76ba2d2fb67baa8e8d146eb4a547 + languageName: node + linkType: hard + +"@types/debug@npm:^4.0.0, @types/debug@npm:^4.1.6": + version: 4.1.12 + resolution: "@types/debug@npm:4.1.12" + dependencies: + "@types/ms": "npm:*" + checksum: 10c0/5dcd465edbb5a7f226e9a5efd1f399c6172407ef5840686b73e3608ce135eeca54ae8037dcd9f16bdb2768ac74925b820a8b9ecc588a58ca09eca6acabe33e2f + languageName: node + linkType: hard + +"@types/event-source-polyfill@npm:^1.0.1": + version: 1.0.5 + resolution: "@types/event-source-polyfill@npm:1.0.5" + checksum: 10c0/62f06c58312097c17f8489771057bb9e859269243a815ef27c8b50b48e084412fce4f90cb18252f807ed9016028321ce2713c90b77e77abc9e84a21bce5db724 + languageName: node + linkType: hard + +"@types/fs-extra@npm:^9.0.11": + version: 9.0.13 + resolution: "@types/fs-extra@npm:9.0.13" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/576d4e9d382393316ed815c593f7f5c157408ec5e184521d077fcb15d514b5a985245f153ef52142b9b976cb9bd8f801850d51238153ebd0dc9e96b7a7548588 + languageName: node + linkType: hard + +"@types/glob@npm:^7.1.1": + version: 7.2.0 + resolution: "@types/glob@npm:7.2.0" + dependencies: + "@types/minimatch": "npm:*" + "@types/node": "npm:*" + checksum: 10c0/a8eb5d5cb5c48fc58c7ca3ff1e1ddf771ee07ca5043da6e4871e6757b4472e2e73b4cfef2644c38983174a4bc728c73f8da02845c28a1212f98cabd293ecae98 + languageName: node + linkType: hard + +"@types/graceful-fs@npm:^4.1.3": + version: 4.1.9 + resolution: "@types/graceful-fs@npm:4.1.9" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/235d2fc69741448e853333b7c3d1180a966dd2b8972c8cbcd6b2a0c6cd7f8d582ab2b8e58219dbc62cce8f1b40aa317ff78ea2201cdd8249da5025adebed6f0b + languageName: node + linkType: hard + +"@types/hapi__joi@npm:17.1.9": + version: 17.1.9 + resolution: "@types/hapi__joi@npm:17.1.9" + checksum: 10c0/9bf983763ac799ed1ca24704115b3aee24d8624ce1b9ac68a0a34f749839813369cce2120123d4c331d37831e7098e8bad9d2cbe20ecf6fdb41d5d80d81b9e56 + languageName: node + linkType: hard + +"@types/hast@npm:^2.0.0": + version: 2.3.10 + resolution: "@types/hast@npm:2.3.10" + dependencies: + "@types/unist": "npm:^2" + checksum: 10c0/16daac35d032e656defe1f103f9c09c341a6dc553c7ec17b388274076fa26e904a71ea5ea41fd368a6d5f1e9e53be275c80af7942b9c466d8511d261c9529c7e + languageName: node + linkType: hard + +"@types/hoist-non-react-statics@npm:^3.3.1": + version: 3.3.7 + resolution: "@types/hoist-non-react-statics@npm:3.3.7" + dependencies: + hoist-non-react-statics: "npm:^3.3.0" + peerDependencies: + "@types/react": "*" + checksum: 10c0/ed8f4e88338f7d021d0f956adf6089d2a12b2e254a03c05292324f2e986d2376eb9efdb8a4f04596823e8fca88c9d06361d20dab4a2a00dc935fb36ac911de55 + languageName: node + linkType: hard + +"@types/html-minifier-terser@npm:^6.0.0": + version: 6.1.0 + resolution: "@types/html-minifier-terser@npm:6.1.0" + checksum: 10c0/a62fb8588e2f3818d82a2d7b953ad60a4a52fd767ae04671de1c16f5788bd72f1ed3a6109ed63fd190c06a37d919e3c39d8adbc1793a005def76c15a3f5f5dab + languageName: node + linkType: hard + +"@types/http-cache-semantics@npm:*": + version: 4.0.4 + resolution: "@types/http-cache-semantics@npm:4.0.4" + checksum: 10c0/51b72568b4b2863e0fe8d6ce8aad72a784b7510d72dc866215642da51d84945a9459fa89f49ec48f1e9a1752e6a78e85a4cda0ded06b1c73e727610c925f9ce6 + languageName: node + linkType: hard + +"@types/invariant@npm:^2.2.31": + version: 2.2.37 + resolution: "@types/invariant@npm:2.2.37" + checksum: 10c0/f57ed8445036ebda8bc93804f088c2a13050bbeef4e4bc6ed531a70e2869250dbe59413f2a9ed7d8f3efa960f191e8dfca9d25414d63cbf604d348428f8c5b75 + languageName: node + linkType: hard + +"@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0": + version: 2.0.6 + resolution: "@types/istanbul-lib-coverage@npm:2.0.6" + checksum: 10c0/3948088654f3eeb45363f1db158354fb013b362dba2a5c2c18c559484d5eb9f6fd85b23d66c0a7c2fcfab7308d0a585b14dadaca6cc8bf89ebfdc7f8f5102fb7 + languageName: node + linkType: hard + +"@types/istanbul-lib-report@npm:*": + version: 3.0.3 + resolution: "@types/istanbul-lib-report@npm:3.0.3" + dependencies: + "@types/istanbul-lib-coverage": "npm:*" + checksum: 10c0/247e477bbc1a77248f3c6de5dadaae85ff86ac2d76c5fc6ab1776f54512a745ff2a5f791d22b942e3990ddbd40f3ef5289317c4fca5741bedfaa4f01df89051c + languageName: node + linkType: hard + +"@types/istanbul-reports@npm:^1.1.1": + version: 1.1.2 + resolution: "@types/istanbul-reports@npm:1.1.2" + dependencies: + "@types/istanbul-lib-coverage": "npm:*" + "@types/istanbul-lib-report": "npm:*" + checksum: 10c0/80b76715f4ac74a4ddfc82d7942b2faaefbe9fdce8e7dfdfa497b3fb60a3e707b632c6e70e1565cfe30045eaebaf7aad0d6c3d102652d1da8fdb0bf095924eb3 + languageName: node + linkType: hard + +"@types/istanbul-reports@npm:^3.0.0": + version: 3.0.4 + resolution: "@types/istanbul-reports@npm:3.0.4" + dependencies: + "@types/istanbul-lib-report": "npm:*" + checksum: 10c0/1647fd402aced5b6edac87274af14ebd6b3a85447ef9ad11853a70fd92a98d35f81a5d3ea9fcb5dbb5834e800c6e35b64475e33fcae6bfa9acc70d61497c54ee + languageName: node + linkType: hard + +"@types/js-cookie@npm:^3.0.6": + version: 3.0.6 + resolution: "@types/js-cookie@npm:3.0.6" + checksum: 10c0/173afaf5ea9d86c22395b9d2a00b6adb0006dcfef165d6dcb0395cdc32f5a5dcf9c3c60f97194119963a15849b8f85121e1ae730b03e40bc0c29b84396ba22f9 + languageName: node + linkType: hard + +"@types/json-schema@npm:^7.0.12, @types/json-schema@npm:^7.0.8, @types/json-schema@npm:^7.0.9": + version: 7.0.15 + resolution: "@types/json-schema@npm:7.0.15" + checksum: 10c0/a996a745e6c5d60292f36731dd41341339d4eeed8180bb09226e5c8d23759067692b1d88e5d91d72ee83dfc00d3aca8e7bd43ea120516c17922cbcb7c3e252db + languageName: node + linkType: hard + +"@types/json5@npm:^0.0.29": + version: 0.0.29 + resolution: "@types/json5@npm:0.0.29" + checksum: 10c0/6bf5337bc447b706bb5b4431d37686aa2ea6d07cfd6f79cc31de80170d6ff9b1c7384a9c0ccbc45b3f512bae9e9f75c2e12109806a15331dc94e8a8db6dbb4ac + languageName: node + linkType: hard + +"@types/keyv@npm:^3.1.4": + version: 3.1.4 + resolution: "@types/keyv@npm:3.1.4" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/ff8f54fc49621210291f815fe5b15d809fd7d032941b3180743440bd507ecdf08b9e844625fa346af568c84bf34114eb378dcdc3e921a08ba1e2a08d7e3c809c + languageName: node + linkType: hard + +"@types/lodash@npm:^4.14.195": + version: 4.17.21 + resolution: "@types/lodash@npm:4.17.21" + checksum: 10c0/73cb006e047d8871e9d63f3a165543bf16c44a5b6fe3f9f6299e37cb8d67a7b1d55ac730959a81f9def510fd07232ff7e30e05413e5d5a12793baad84ebe36c3 + languageName: node + linkType: hard + +"@types/mdast@npm:^3.0.0": + version: 3.0.15 + resolution: "@types/mdast@npm:3.0.15" + dependencies: + "@types/unist": "npm:^2" + checksum: 10c0/fcbf716c03d1ed5465deca60862e9691414f9c43597c288c7d2aefbe274552e1bbd7aeee91b88a02597e88a28c139c57863d0126fcf8416a95fdc681d054ee3d + languageName: node + linkType: hard + +"@types/minimatch@npm:*": + version: 5.1.2 + resolution: "@types/minimatch@npm:5.1.2" + checksum: 10c0/83cf1c11748891b714e129de0585af4c55dd4c2cafb1f1d5233d79246e5e1e19d1b5ad9e8db449667b3ffa2b6c80125c429dbee1054e9efb45758dbc4e118562 + languageName: node + linkType: hard + +"@types/ms@npm:*": + version: 2.1.0 + resolution: "@types/ms@npm:2.1.0" + checksum: 10c0/5ce692ffe1549e1b827d99ef8ff71187457e0eb44adbae38fdf7b9a74bae8d20642ee963c14516db1d35fa2652e65f47680fdf679dcbde52bbfadd021f497225 + languageName: node + linkType: hard + +"@types/node@npm:*": + version: 24.10.1 + resolution: "@types/node@npm:24.10.1" + dependencies: + undici-types: "npm:~7.16.0" + checksum: 10c0/d6bca7a78f550fbb376f236f92b405d676003a8a09a1b411f55920ef34286ee3ee51f566203920e835478784df52662b5b2af89159d9d319352e9ea21801c002 + languageName: node + linkType: hard + +"@types/node@npm:^16.11.26": + version: 16.18.126 + resolution: "@types/node@npm:16.18.126" + checksum: 10c0/5c137eb141d33de32b16ff26047ff6d449432b58d0d25f7cced2040c97727d81fe1099a7581eb336d14a6840f5b09e363bdd43d7a6995e8e81eb47aa51e413fc + languageName: node + linkType: hard + +"@types/parse-json@npm:^4.0.0": + version: 4.0.2 + resolution: "@types/parse-json@npm:4.0.2" + checksum: 10c0/b1b863ac34a2c2172fbe0807a1ec4d5cb684e48d422d15ec95980b81475fac4fdb3768a8b13eef39130203a7c04340fc167bae057c7ebcafd7dec9fe6c36aeb1 + languageName: node + linkType: hard + +"@types/plist@npm:^3.0.1": + version: 3.0.5 + resolution: "@types/plist@npm:3.0.5" + dependencies: + "@types/node": "npm:*" + xmlbuilder: "npm:>=11.0.1" + checksum: 10c0/2a929f4482e3bea8c3288a46ae589a2ae2d01df5b7841ead7032d7baa79d79af6c875a5798c90705eea9306c2fb1544d7ed12ab3c905c5626d5dd5dc9f464b94 + languageName: node + linkType: hard + +"@types/prop-types@npm:*, @types/prop-types@npm:^15.0.0": + version: 15.7.15 + resolution: "@types/prop-types@npm:15.7.15" + checksum: 10c0/b59aad1ad19bf1733cf524fd4e618196c6c7690f48ee70a327eb450a42aab8e8a063fbe59ca0a5701aebe2d92d582292c0fb845ea57474f6a15f6994b0e260b2 + languageName: node + linkType: hard + +"@types/react-dom@npm:^18.0.11": + version: 18.3.7 + resolution: "@types/react-dom@npm:18.3.7" + peerDependencies: + "@types/react": ^18.0.0 + checksum: 10c0/8bd309e2c3d1604a28a736a24f96cbadf6c05d5288cfef8883b74f4054c961b6b3a5e997fd5686e492be903c8f3380dba5ec017eff3906b1256529cd2d39603e + languageName: node + linkType: hard + +"@types/react@npm:^18.0.33": + version: 18.3.27 + resolution: "@types/react@npm:18.3.27" + dependencies: + "@types/prop-types": "npm:*" + csstype: "npm:^3.2.2" + checksum: 10c0/a761d2f58de03d0714806cc65d32bb3d73fb33a08dd030d255b47a295e5fff2a775cf1c20b786824d8deb6454eaccce9bc6998d9899c14fc04bbd1b0b0b72897 + languageName: node + linkType: hard + +"@types/resolve@npm:^1.20.6": + version: 1.20.6 + resolution: "@types/resolve@npm:1.20.6" + checksum: 10c0/a9b0549d816ff2c353077365d865a33655a141d066d0f5a3ba6fd4b28bc2f4188a510079f7c1f715b3e7af505a27374adce2a5140a3ece2a059aab3d6e1a4244 + languageName: node + linkType: hard + +"@types/responselike@npm:^1.0.0": + version: 1.0.3 + resolution: "@types/responselike@npm:1.0.3" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/a58ba341cb9e7d74f71810a88862da7b2a6fa42e2a1fc0ce40498f6ea1d44382f0640117057da779f74c47039f7166bf48fad02dc876f94e005c7afa50f5e129 + languageName: node + linkType: hard + +"@types/semver@npm:^7.3.12, @types/semver@npm:^7.5.0": + version: 7.7.1 + resolution: "@types/semver@npm:7.7.1" + checksum: 10c0/c938aef3bf79a73f0f3f6037c16e2e759ff40c54122ddf0b2583703393d8d3127130823facb880e694caa324eb6845628186aac1997ee8b31dc2d18fafe26268 + languageName: node + linkType: hard + +"@types/stylis@npm:4.2.5": + version: 4.2.5 + resolution: "@types/stylis@npm:4.2.5" + checksum: 10c0/23f5b35a3a04f6bb31a29d404fa1bc8e0035fcaff2356b4047743a057e0c37b2eba7efe14d57dd2b95b398cea3bac294d9c6cd93ed307d8c0b7f5d282224b469 + languageName: node + linkType: hard + +"@types/stylis@npm:^4.0.2": + version: 4.2.7 + resolution: "@types/stylis@npm:4.2.7" + checksum: 10c0/01a9679addb3f63951a9c09729564e2205581f2db40875a28b25cc461efc52ba17a711cc50cdb5e7d3a67c5f2cd60580e078c8a69b8df7b67699d89060d2a977 + languageName: node + linkType: hard + +"@types/unist@npm:^2, @types/unist@npm:^2.0.0": + version: 2.0.11 + resolution: "@types/unist@npm:2.0.11" + checksum: 10c0/24dcdf25a168f453bb70298145eb043cfdbb82472db0bc0b56d6d51cd2e484b9ed8271d4ac93000a80da568f2402e9339723db262d0869e2bf13bc58e081768d + languageName: node + linkType: hard + +"@types/use-sync-external-store@npm:^0.0.3": + version: 0.0.3 + resolution: "@types/use-sync-external-store@npm:0.0.3" + checksum: 10c0/82824c1051ba40a00e3d47964cdf4546a224e95f172e15a9c62aa3f118acee1c7518b627a34f3aa87298a2039f982e8509f92bfcc18bea7c255c189c293ba547 + languageName: node + linkType: hard + +"@types/uuid@npm:^9.0.1": + version: 9.0.8 + resolution: "@types/uuid@npm:9.0.8" + checksum: 10c0/b411b93054cb1d4361919579ef3508a1f12bf15b5fdd97337d3d351bece6c921b52b6daeef89b62340fd73fd60da407878432a1af777f40648cbe53a01723489 + languageName: node + linkType: hard + +"@types/verror@npm:^1.10.3": + version: 1.10.11 + resolution: "@types/verror@npm:1.10.11" + checksum: 10c0/6d815cb2b76501f976cf9fa0feaf572e83b2ec3043eff92507c9976e52b7844453bd47c84f1298bf583f8e6568f39063a2541f80656f4af8e179072a711f9ab5 + languageName: node + linkType: hard + +"@types/yargs-parser@npm:*": + version: 21.0.3 + resolution: "@types/yargs-parser@npm:21.0.3" + checksum: 10c0/e71c3bd9d0b73ca82e10bee2064c384ab70f61034bbfb78e74f5206283fc16a6d85267b606b5c22cb2a3338373586786fed595b2009825d6a9115afba36560a0 + languageName: node + linkType: hard + +"@types/yargs@npm:^13.0.0": + version: 13.0.12 + resolution: "@types/yargs@npm:13.0.12" + dependencies: + "@types/yargs-parser": "npm:*" + checksum: 10c0/81fdac6832d69f2f2a33bb3d77887f571677d5a9ccfd5a171ff3e76252a6c6a9773850a0df6ba9ed0328433a36596488ec4e2ce5d9bc49d713a59bbfef8e12a0 + languageName: node + linkType: hard + +"@types/yargs@npm:^16.0.0": + version: 16.0.11 + resolution: "@types/yargs@npm:16.0.11" + dependencies: + "@types/yargs-parser": "npm:*" + checksum: 10c0/c41bcb718e35b35561646e4297863628ec7dc745ea4e09a15ec787ad3b94f6c5a038322a36c30a958e348931f5e8be6ce220683040522bab2e2844a3b4eb7988 + languageName: node + linkType: hard + +"@types/yargs@npm:^17.0.1, @types/yargs@npm:^17.0.8": + version: 17.0.35 + resolution: "@types/yargs@npm:17.0.35" + dependencies: + "@types/yargs-parser": "npm:*" + checksum: 10c0/609557826a6b85e73ccf587923f6429850d6dc70e420b455bab4601b670bfadf684b09ae288bccedab042c48ba65f1666133cf375814204b544009f57d6eef63 + languageName: node + linkType: hard + +"@types/yauzl@npm:^2.9.1": + version: 2.10.3 + resolution: "@types/yauzl@npm:2.10.3" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/f1b7c1b99fef9f2fe7f1985ef7426d0cebe48cd031f1780fcdc7451eec7e31ac97028f16f50121a59bcf53086a1fc8c856fd5b7d3e00970e43d92ae27d6b43dc + languageName: node + linkType: hard + +"@typescript-eslint/eslint-plugin@npm:^5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/eslint-plugin@npm:5.62.0" + dependencies: + "@eslint-community/regexpp": "npm:^4.4.0" + "@typescript-eslint/scope-manager": "npm:5.62.0" + "@typescript-eslint/type-utils": "npm:5.62.0" + "@typescript-eslint/utils": "npm:5.62.0" + debug: "npm:^4.3.4" + graphemer: "npm:^1.4.0" + ignore: "npm:^5.2.0" + natural-compare-lite: "npm:^1.4.0" + semver: "npm:^7.3.7" + tsutils: "npm:^3.21.0" + peerDependencies: + "@typescript-eslint/parser": ^5.0.0 + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + typescript: + optional: true + checksum: 10c0/3f40cb6bab5a2833c3544e4621b9fdacd8ea53420cadc1c63fac3b89cdf5c62be1e6b7bcf56976dede5db4c43830de298ced3db60b5494a3b961ca1b4bff9f2a + languageName: node + linkType: hard + +"@typescript-eslint/eslint-plugin@npm:^6.7.2": + version: 6.21.0 + resolution: "@typescript-eslint/eslint-plugin@npm:6.21.0" + dependencies: + "@eslint-community/regexpp": "npm:^4.5.1" + "@typescript-eslint/scope-manager": "npm:6.21.0" + "@typescript-eslint/type-utils": "npm:6.21.0" + "@typescript-eslint/utils": "npm:6.21.0" + "@typescript-eslint/visitor-keys": "npm:6.21.0" + debug: "npm:^4.3.4" + graphemer: "npm:^1.4.0" + ignore: "npm:^5.2.4" + natural-compare: "npm:^1.4.0" + semver: "npm:^7.5.4" + ts-api-utils: "npm:^1.0.1" + peerDependencies: + "@typescript-eslint/parser": ^6.0.0 || ^6.0.0-alpha + eslint: ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + typescript: + optional: true + checksum: 10c0/f911a79ee64d642f814a3b6cdb0d324b5f45d9ef955c5033e78903f626b7239b4aa773e464a38c3e667519066169d983538f2bf8e5d00228af587c9d438fb344 + languageName: node + linkType: hard + +"@typescript-eslint/parser@npm:^5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/parser@npm:5.62.0" + dependencies: + "@typescript-eslint/scope-manager": "npm:5.62.0" + "@typescript-eslint/types": "npm:5.62.0" + "@typescript-eslint/typescript-estree": "npm:5.62.0" + debug: "npm:^4.3.4" + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + typescript: + optional: true + checksum: 10c0/315194b3bf39beb9bd16c190956c46beec64b8371e18d6bb72002108b250983eb1e186a01d34b77eb4045f4941acbb243b16155fbb46881105f65e37dc9e24d4 + languageName: node + linkType: hard + +"@typescript-eslint/parser@npm:^6.7.2": + version: 6.21.0 + resolution: "@typescript-eslint/parser@npm:6.21.0" + dependencies: + "@typescript-eslint/scope-manager": "npm:6.21.0" + "@typescript-eslint/types": "npm:6.21.0" + "@typescript-eslint/typescript-estree": "npm:6.21.0" + "@typescript-eslint/visitor-keys": "npm:6.21.0" + debug: "npm:^4.3.4" + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + typescript: + optional: true + checksum: 10c0/a8f99820679decd0d115c0af61903fb1de3b1b5bec412dc72b67670bf636de77ab07f2a68ee65d6da7976039bbf636907f9d5ca546db3f0b98a31ffbc225bc7d + languageName: node + linkType: hard + +"@typescript-eslint/scope-manager@npm:5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/scope-manager@npm:5.62.0" + dependencies: + "@typescript-eslint/types": "npm:5.62.0" + "@typescript-eslint/visitor-keys": "npm:5.62.0" + checksum: 10c0/861253235576c1c5c1772d23cdce1418c2da2618a479a7de4f6114a12a7ca853011a1e530525d0931c355a8fd237b9cd828fac560f85f9623e24054fd024726f + languageName: node + linkType: hard + +"@typescript-eslint/scope-manager@npm:6.21.0": + version: 6.21.0 + resolution: "@typescript-eslint/scope-manager@npm:6.21.0" + dependencies: + "@typescript-eslint/types": "npm:6.21.0" + "@typescript-eslint/visitor-keys": "npm:6.21.0" + checksum: 10c0/eaf868938d811cbbea33e97e44ba7050d2b6892202cea6a9622c486b85ab1cf801979edf78036179a8ba4ac26f1dfdf7fcc83a68c1ff66be0b3a8e9a9989b526 + languageName: node + linkType: hard + +"@typescript-eslint/type-utils@npm:5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/type-utils@npm:5.62.0" + dependencies: + "@typescript-eslint/typescript-estree": "npm:5.62.0" + "@typescript-eslint/utils": "npm:5.62.0" + debug: "npm:^4.3.4" + tsutils: "npm:^3.21.0" + peerDependencies: + eslint: "*" + peerDependenciesMeta: + typescript: + optional: true + checksum: 10c0/93112e34026069a48f0484b98caca1c89d9707842afe14e08e7390af51cdde87378df29d213d3bbd10a7cfe6f91b228031b56218515ce077bdb62ddea9d9f474 + languageName: node + linkType: hard + +"@typescript-eslint/type-utils@npm:6.21.0": + version: 6.21.0 + resolution: "@typescript-eslint/type-utils@npm:6.21.0" + dependencies: + "@typescript-eslint/typescript-estree": "npm:6.21.0" + "@typescript-eslint/utils": "npm:6.21.0" + debug: "npm:^4.3.4" + ts-api-utils: "npm:^1.0.1" + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + typescript: + optional: true + checksum: 10c0/7409c97d1c4a4386b488962739c4f1b5b04dc60cf51f8cd88e6b12541f84d84c6b8b67e491a147a2c95f9ec486539bf4519fb9d418411aef6537b9c156468117 + languageName: node + linkType: hard + +"@typescript-eslint/types@npm:5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/types@npm:5.62.0" + checksum: 10c0/7febd3a7f0701c0b927e094f02e82d8ee2cada2b186fcb938bc2b94ff6fbad88237afc304cbaf33e82797078bbbb1baf91475f6400912f8b64c89be79bfa4ddf + languageName: node + linkType: hard + +"@typescript-eslint/types@npm:6.21.0": + version: 6.21.0 + resolution: "@typescript-eslint/types@npm:6.21.0" + checksum: 10c0/020631d3223bbcff8a0da3efbdf058220a8f48a3de221563996ad1dcc30d6c08dadc3f7608cc08830d21c0d565efd2db19b557b9528921c78aabb605eef2d74d + languageName: node + linkType: hard + +"@typescript-eslint/typescript-estree@npm:5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/typescript-estree@npm:5.62.0" + dependencies: + "@typescript-eslint/types": "npm:5.62.0" + "@typescript-eslint/visitor-keys": "npm:5.62.0" + debug: "npm:^4.3.4" + globby: "npm:^11.1.0" + is-glob: "npm:^4.0.3" + semver: "npm:^7.3.7" + tsutils: "npm:^3.21.0" + peerDependenciesMeta: + typescript: + optional: true + checksum: 10c0/d7984a3e9d56897b2481940ec803cb8e7ead03df8d9cfd9797350be82ff765dfcf3cfec04e7355e1779e948da8f02bc5e11719d07a596eb1cb995c48a95e38cf + languageName: node + linkType: hard + +"@typescript-eslint/typescript-estree@npm:6.21.0": + version: 6.21.0 + resolution: "@typescript-eslint/typescript-estree@npm:6.21.0" + dependencies: + "@typescript-eslint/types": "npm:6.21.0" + "@typescript-eslint/visitor-keys": "npm:6.21.0" + debug: "npm:^4.3.4" + globby: "npm:^11.1.0" + is-glob: "npm:^4.0.3" + minimatch: "npm:9.0.3" + semver: "npm:^7.5.4" + ts-api-utils: "npm:^1.0.1" + peerDependenciesMeta: + typescript: + optional: true + checksum: 10c0/af1438c60f080045ebb330155a8c9bb90db345d5069cdd5d01b67de502abb7449d6c75500519df829f913a6b3f490ade3e8215279b6bdc63d0fb0ae61034df5f + languageName: node + linkType: hard + +"@typescript-eslint/utils@npm:5.62.0, @typescript-eslint/utils@npm:^5.10.0": + version: 5.62.0 + resolution: "@typescript-eslint/utils@npm:5.62.0" + dependencies: + "@eslint-community/eslint-utils": "npm:^4.2.0" + "@types/json-schema": "npm:^7.0.9" + "@types/semver": "npm:^7.3.12" + "@typescript-eslint/scope-manager": "npm:5.62.0" + "@typescript-eslint/types": "npm:5.62.0" + "@typescript-eslint/typescript-estree": "npm:5.62.0" + eslint-scope: "npm:^5.1.1" + semver: "npm:^7.3.7" + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + checksum: 10c0/f09b7d9952e4a205eb1ced31d7684dd55cee40bf8c2d78e923aa8a255318d97279825733902742c09d8690f37a50243f4c4d383ab16bd7aefaf9c4b438f785e1 + languageName: node + linkType: hard + +"@typescript-eslint/utils@npm:6.21.0": + version: 6.21.0 + resolution: "@typescript-eslint/utils@npm:6.21.0" + dependencies: + "@eslint-community/eslint-utils": "npm:^4.4.0" + "@types/json-schema": "npm:^7.0.12" + "@types/semver": "npm:^7.5.0" + "@typescript-eslint/scope-manager": "npm:6.21.0" + "@typescript-eslint/types": "npm:6.21.0" + "@typescript-eslint/typescript-estree": "npm:6.21.0" + semver: "npm:^7.5.4" + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + checksum: 10c0/ab2df3833b2582d4e5467a484d08942b4f2f7208f8e09d67de510008eb8001a9b7460f2f9ba11c12086fd3cdcac0c626761c7995c2c6b5657d5fa6b82030a32d + languageName: node + linkType: hard + +"@typescript-eslint/visitor-keys@npm:5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/visitor-keys@npm:5.62.0" + dependencies: + "@typescript-eslint/types": "npm:5.62.0" + eslint-visitor-keys: "npm:^3.3.0" + checksum: 10c0/7c3b8e4148e9b94d9b7162a596a1260d7a3efc4e65199693b8025c71c4652b8042501c0bc9f57654c1e2943c26da98c0f77884a746c6ae81389fcb0b513d995d + languageName: node + linkType: hard + +"@typescript-eslint/visitor-keys@npm:6.21.0": + version: 6.21.0 + resolution: "@typescript-eslint/visitor-keys@npm:6.21.0" + dependencies: + "@typescript-eslint/types": "npm:6.21.0" + eslint-visitor-keys: "npm:^3.4.1" + checksum: 10c0/7395f69739cfa1cb83c1fb2fad30afa2a814756367302fb4facd5893eff66abc807e8d8f63eba94ed3b0fe0c1c996ac9a1680bcbf0f83717acedc3f2bb724fbf + languageName: node + linkType: hard + +"@umijs/ast@npm:4.6.0": + version: 4.6.0 + resolution: "@umijs/ast@npm:4.6.0" + dependencies: + "@umijs/bundler-utils": "npm:4.6.0" + checksum: 10c0/166f77932af67534d66c747c26e733ff702d939bbf55e06a924ebd3716c17d6a490bedc1a269a4e806e24fa0f770b1255ed3b31ef9fc46a1c768745bd264cade + languageName: node + linkType: hard + +"@umijs/babel-preset-umi@npm:4.6.0": + version: 4.6.0 + resolution: "@umijs/babel-preset-umi@npm:4.6.0" + dependencies: + "@babel/runtime": "npm:7.23.6" + "@bloomberg/record-tuple-polyfill": "npm:0.0.4" + "@umijs/bundler-utils": "npm:4.6.0" + "@umijs/utils": "npm:4.6.0" + core-js: "npm:3.34.0" + checksum: 10c0/76713ece6664551ef0be3a9a7ce42ff7ea5ab9310f114aaa02ae0bba4fcb35f260d2be06f32369982a1e2df68ef069506a24e7c9ca803d84169839cca897fa79 + languageName: node + linkType: hard + +"@umijs/bundler-esbuild@npm:4.6.0": + version: 4.6.0 + resolution: "@umijs/bundler-esbuild@npm:4.6.0" + dependencies: + "@umijs/bundler-utils": "npm:4.6.0" + "@umijs/utils": "npm:4.6.0" + enhanced-resolve: "npm:5.9.3" + postcss: "npm:^8.4.21" + postcss-flexbugs-fixes: "npm:5.0.2" + postcss-preset-env: "npm:7.5.0" + bin: + bundler-esbuild: bin/bundler-esbuild.js + checksum: 10c0/2a990b2477d621d5fa1ed05200152e33becb701195ae597ad6a8fe1325948601258ee31c09137ed59a6cb01e3fc9a50917fb6c127f8f49afb1d4c7a131feb658 + languageName: node + linkType: hard + +"@umijs/bundler-mako@npm:0.11.10": + version: 0.11.10 + resolution: "@umijs/bundler-mako@npm:0.11.10" + dependencies: + "@umijs/bundler-utils": "npm:^4.0.81" + "@umijs/mako": "npm:0.11.10" + chalk: "npm:^4.1.2" + compression: "npm:^1.7.4" + connect-history-api-fallback: "npm:^2.0.0" + cors: "npm:^2.8.5" + express: "npm:^4.18.2" + express-http-proxy: "npm:^2.1.1" + get-tsconfig: "npm:4.7.5" + lodash: "npm:^4.17.21" + rimraf: "npm:5.0.1" + webpack-5-chain: "npm:8.0.1" + checksum: 10c0/3718eb7d03905d557c53a46eae9bd07366fe976fa0d515750015d4072e09fff8d0299d4425c5ee512305286c119015d4c18225022faac44c394f5d02f51a6225 + languageName: node + linkType: hard + +"@umijs/bundler-utils@npm:4.6.0, @umijs/bundler-utils@npm:^4.0.81": + version: 4.6.0 + resolution: "@umijs/bundler-utils@npm:4.6.0" + dependencies: + "@umijs/utils": "npm:4.6.0" + esbuild: "npm:0.21.4" + regenerate: "npm:^1.4.2" + regenerate-unicode-properties: "npm:10.1.1" + spdy: "npm:^4.0.2" + checksum: 10c0/d02e98f373cf340105cd9d8c60a52c3f54b2ab1bcbfa85279b6f3714ecd1cde20d4d1bfd8b99ef34783e901877916205685fb2a0f5c0eec2d826f7bb8c7f2822 + languageName: node + linkType: hard + +"@umijs/bundler-utoopack@npm:4.6.0": + version: 4.6.0 + resolution: "@umijs/bundler-utoopack@npm:4.6.0" + dependencies: + "@umijs/bundler-utils": "npm:4.6.0" + "@umijs/bundler-webpack": "npm:4.6.0" + "@utoo/pack": "npm:1.0.0" + compression: "npm:^1.7.4" + connect-history-api-fallback: "npm:^2.0.0" + cors: "npm:^2.8.5" + express: "npm:^4.18.2" + express-http-proxy: "npm:^2.1.1" + checksum: 10c0/50d05116336e73bd6a21baafe257f99d39df8af471cfe7fe664fc0b98eadf8302ab2f0ea1d82a9b6331b001d3be231aa9a10af7b99fdd70b5e9319d91a95de7e + languageName: node + linkType: hard + +"@umijs/bundler-vite@npm:4.6.0": + version: 4.6.0 + resolution: "@umijs/bundler-vite@npm:4.6.0" + dependencies: + "@svgr/core": "npm:6.5.1" + "@umijs/bundler-utils": "npm:4.6.0" + "@umijs/utils": "npm:4.6.0" + "@vitejs/plugin-react": "npm:4.0.0" + core-js: "npm:3.34.0" + less: "npm:4.1.3" + postcss-preset-env: "npm:7.5.0" + rollup-plugin-visualizer: "npm:5.9.0" + systemjs: "npm:^6.14.1" + vite: "npm:4.5.2" + bin: + bundler-vite: bin/bundler-vite.js + checksum: 10c0/0e82d60dbde45ac01ca9963c968f896182567d86944a5445015c37d9966be32142f6510e95fc6ab4c1e866837be40fae5dfee4b0c9a42a1bb27e638aca41bcf2 + languageName: node + linkType: hard + +"@umijs/bundler-webpack@npm:4.6.0": + version: 4.6.0 + resolution: "@umijs/bundler-webpack@npm:4.6.0" + dependencies: + "@svgr/core": "npm:6.5.1" + "@svgr/plugin-jsx": "npm:^6.5.1" + "@svgr/plugin-svgo": "npm:^6.5.1" + "@types/hapi__joi": "npm:17.1.9" + "@umijs/babel-preset-umi": "npm:4.6.0" + "@umijs/bundler-utils": "npm:4.6.0" + "@umijs/case-sensitive-paths-webpack-plugin": "npm:^1.0.1" + "@umijs/mfsu": "npm:4.6.0" + "@umijs/react-refresh-webpack-plugin": "npm:0.5.11" + "@umijs/utils": "npm:4.6.0" + cors: "npm:^2.8.5" + css-loader: "npm:6.7.1" + es5-imcompatible-versions: "npm:^0.1.78" + fork-ts-checker-webpack-plugin: "npm:8.0.0" + jest-worker: "npm:29.4.3" + lightningcss: "npm:1.22.1" + node-libs-browser: "npm:2.2.1" + postcss: "npm:^8.4.21" + postcss-preset-env: "npm:7.5.0" + react-error-overlay: "npm:6.0.9" + react-refresh: "npm:0.14.0" + bin: + bundler-webpack: bin/bundler-webpack.js + checksum: 10c0/3898972e795582f68579ca4f618728fd92c479a6e14eac7967eb53279bb0a046db2a38f85c67deda48c44925546a7134900064e1ab553356869af687c4a54c68 + languageName: node + linkType: hard + +"@umijs/case-sensitive-paths-webpack-plugin@npm:^1.0.1": + version: 1.0.1 + resolution: "@umijs/case-sensitive-paths-webpack-plugin@npm:1.0.1" + checksum: 10c0/5fbb1588f32525f569da07c585c4504a246cf3d5a3174002b16d240ee71c347a13e7dcf7adae2884ff01dac87cc3f937e9e87910b39b254e24c93d0ba72c2aaa + languageName: node + linkType: hard + +"@umijs/core@npm:4.6.0": + version: 4.6.0 + resolution: "@umijs/core@npm:4.6.0" + dependencies: + "@umijs/bundler-utils": "npm:4.6.0" + "@umijs/utils": "npm:4.6.0" + checksum: 10c0/a5e4b0850c8cd036e536fed02fcae3cb5bcfe50552a9067de76a4e784d9df032b98d4e7a0b68c53972d4dd0986888969896a230a46bfa4f32a19595377965d10 + languageName: node + linkType: hard + +"@umijs/did-you-know@npm:^1.0.4": + version: 1.0.4 + resolution: "@umijs/did-you-know@npm:1.0.4" + checksum: 10c0/6d6d6d02200a5a83c6b6c7956fba6ca01d5a7c976c07480e118efe4742bd7291a6503220ebcff73884ecabf5774b50910343398ba3087773b2033c8146e79daa + languageName: node + linkType: hard + +"@umijs/es-module-parser-darwin-arm64@npm:0.0.7": + version: 0.0.7 + resolution: "@umijs/es-module-parser-darwin-arm64@npm:0.0.7" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@umijs/es-module-parser-darwin-x64@npm:0.0.7": + version: 0.0.7 + resolution: "@umijs/es-module-parser-darwin-x64@npm:0.0.7" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@umijs/es-module-parser-linux-arm-gnueabihf@npm:0.0.7": + version: 0.0.7 + resolution: "@umijs/es-module-parser-linux-arm-gnueabihf@npm:0.0.7" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@umijs/es-module-parser-linux-arm64-gnu@npm:0.0.7": + version: 0.0.7 + resolution: "@umijs/es-module-parser-linux-arm64-gnu@npm:0.0.7" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@umijs/es-module-parser-linux-arm64-musl@npm:0.0.7": + version: 0.0.7 + resolution: "@umijs/es-module-parser-linux-arm64-musl@npm:0.0.7" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@umijs/es-module-parser-linux-x64-gnu@npm:0.0.7": + version: 0.0.7 + resolution: "@umijs/es-module-parser-linux-x64-gnu@npm:0.0.7" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@umijs/es-module-parser-linux-x64-musl@npm:0.0.7": + version: 0.0.7 + resolution: "@umijs/es-module-parser-linux-x64-musl@npm:0.0.7" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@umijs/es-module-parser-win32-arm64-msvc@npm:0.0.7": + version: 0.0.7 + resolution: "@umijs/es-module-parser-win32-arm64-msvc@npm:0.0.7" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@umijs/es-module-parser-win32-x64-msvc@npm:0.0.7": + version: 0.0.7 + resolution: "@umijs/es-module-parser-win32-x64-msvc@npm:0.0.7" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@umijs/es-module-parser@npm:0.0.7": + version: 0.0.7 + resolution: "@umijs/es-module-parser@npm:0.0.7" + dependencies: + "@umijs/es-module-parser-darwin-arm64": "npm:0.0.7" + "@umijs/es-module-parser-darwin-x64": "npm:0.0.7" + "@umijs/es-module-parser-linux-arm-gnueabihf": "npm:0.0.7" + "@umijs/es-module-parser-linux-arm64-gnu": "npm:0.0.7" + "@umijs/es-module-parser-linux-arm64-musl": "npm:0.0.7" + "@umijs/es-module-parser-linux-x64-gnu": "npm:0.0.7" + "@umijs/es-module-parser-linux-x64-musl": "npm:0.0.7" + "@umijs/es-module-parser-win32-arm64-msvc": "npm:0.0.7" + "@umijs/es-module-parser-win32-x64-msvc": "npm:0.0.7" + dependenciesMeta: + "@umijs/es-module-parser-darwin-arm64": + optional: true + "@umijs/es-module-parser-darwin-x64": + optional: true + "@umijs/es-module-parser-linux-arm-gnueabihf": + optional: true + "@umijs/es-module-parser-linux-arm64-gnu": + optional: true + "@umijs/es-module-parser-linux-arm64-musl": + optional: true + "@umijs/es-module-parser-linux-x64-gnu": + optional: true + "@umijs/es-module-parser-linux-x64-musl": + optional: true + "@umijs/es-module-parser-win32-arm64-msvc": + optional: true + "@umijs/es-module-parser-win32-x64-msvc": + optional: true + checksum: 10c0/455b4d461634a2ee71e3a595c1adf352b029ec41b77ebf6ca01206b5ed724d2392f6b486e0ed441b6f03f7bc150a92a6ce5f40040109979c08d93c3239fd1309 + languageName: node + linkType: hard + +"@umijs/history@npm:5.3.1": + version: 5.3.1 + resolution: "@umijs/history@npm:5.3.1" + dependencies: + "@babel/runtime": "npm:^7.7.6" + query-string: "npm:^6.13.6" + checksum: 10c0/1c430715a2a9a2d25db3636bdd21fc24981b8088fc836fed0f5fe467ef8bc0cad6739545fc79d2750580af3df849dc5f44003663aff0b88d45950e984493b20c + languageName: node + linkType: hard + +"@umijs/lint@npm:4.6.0": + version: 4.6.0 + resolution: "@umijs/lint@npm:4.6.0" + dependencies: + "@babel/core": "npm:7.23.6" + "@babel/eslint-parser": "npm:7.23.3" + "@stylelint/postcss-css-in-js": "npm:^0.38.0" + "@typescript-eslint/eslint-plugin": "npm:^5.62.0" + "@typescript-eslint/parser": "npm:^5.62.0" + "@umijs/babel-preset-umi": "npm:4.6.0" + eslint-plugin-jest: "npm:27.2.3" + eslint-plugin-react: "npm:7.33.2" + eslint-plugin-react-hooks: "npm:4.6.0" + postcss: "npm:^8.4.21" + postcss-syntax: "npm:0.36.2" + stylelint-config-standard: "npm:25.0.0" + checksum: 10c0/4989687250c68ea6d6364493de12b771c12694e4e7fbd6c79f2b7758e8714b65e1bbeb3c9899670ecec66a19bec0a36b4bfddb53bd4142a787f1ebb72ae2de3e + languageName: node + linkType: hard + +"@umijs/mako-darwin-arm64@npm:0.11.10": + version: 0.11.10 + resolution: "@umijs/mako-darwin-arm64@npm:0.11.10" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@umijs/mako-darwin-x64@npm:0.11.10": + version: 0.11.10 + resolution: "@umijs/mako-darwin-x64@npm:0.11.10" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@umijs/mako-linux-arm64-gnu@npm:0.11.10": + version: 0.11.10 + resolution: "@umijs/mako-linux-arm64-gnu@npm:0.11.10" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@umijs/mako-linux-arm64-musl@npm:0.11.10": + version: 0.11.10 + resolution: "@umijs/mako-linux-arm64-musl@npm:0.11.10" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@umijs/mako-linux-x64-gnu@npm:0.11.10": + version: 0.11.10 + resolution: "@umijs/mako-linux-x64-gnu@npm:0.11.10" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@umijs/mako-linux-x64-musl@npm:0.11.10": + version: 0.11.10 + resolution: "@umijs/mako-linux-x64-musl@npm:0.11.10" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@umijs/mako-win32-ia32-msvc@npm:0.11.10": + version: 0.11.10 + resolution: "@umijs/mako-win32-ia32-msvc@npm:0.11.10" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@umijs/mako-win32-x64-msvc@npm:0.11.10": + version: 0.11.10 + resolution: "@umijs/mako-win32-x64-msvc@npm:0.11.10" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@umijs/mako@npm:0.11.10": + version: 0.11.10 + resolution: "@umijs/mako@npm:0.11.10" + dependencies: + "@module-federation/webpack-bundler-runtime": "npm:^0.8.0" + "@swc/helpers": "npm:0.5.1" + "@types/resolve": "npm:^1.20.6" + "@umijs/mako-darwin-arm64": "npm:0.11.10" + "@umijs/mako-darwin-x64": "npm:0.11.10" + "@umijs/mako-linux-arm64-gnu": "npm:0.11.10" + "@umijs/mako-linux-arm64-musl": "npm:0.11.10" + "@umijs/mako-linux-x64-gnu": "npm:0.11.10" + "@umijs/mako-linux-x64-musl": "npm:0.11.10" + "@umijs/mako-win32-ia32-msvc": "npm:0.11.10" + "@umijs/mako-win32-x64-msvc": "npm:0.11.10" + chalk: "npm:^4.1.2" + enhanced-resolve: "npm:^5.18.1" + less: "npm:^4.2.0" + less-loader: "npm:^12.2.0" + loader-runner: "npm:^4.3.0" + loader-utils: "npm:^3.3.1" + lodash: "npm:^4.17.21" + node-libs-browser-okam: "npm:^2.2.5" + piscina: "npm:^4.5.1" + postcss-loader: "npm:^8.1.1" + react-error-overlay: "npm:6.0.9" + react-refresh: "npm:^0.14.0" + resolve: "npm:^1.22.8" + sass-loader: "npm:^16.0.5" + semver: "npm:^7.6.2" + yargs-parser: "npm:^21.1.1" + dependenciesMeta: + "@umijs/mako-darwin-arm64": + optional: true + "@umijs/mako-darwin-x64": + optional: true + "@umijs/mako-linux-arm64-gnu": + optional: true + "@umijs/mako-linux-arm64-musl": + optional: true + "@umijs/mako-linux-x64-gnu": + optional: true + "@umijs/mako-linux-x64-musl": + optional: true + "@umijs/mako-win32-ia32-msvc": + optional: true + "@umijs/mako-win32-x64-msvc": + optional: true + bin: + mako: bin/mako.js + checksum: 10c0/fdfcbdda479f3a1f6202c3f22891fd0d2e833f93a229e7b19f58ed4b499119db7e9f6ef8e361b218d992aad372a9503c8ad028095ecd04da23f4c5974ee65634 + languageName: node + linkType: hard + +"@umijs/mfsu@npm:4.6.0": + version: 4.6.0 + resolution: "@umijs/mfsu@npm:4.6.0" + dependencies: + "@umijs/bundler-esbuild": "npm:4.6.0" + "@umijs/bundler-utils": "npm:4.6.0" + "@umijs/utils": "npm:4.6.0" + enhanced-resolve: "npm:5.9.3" + is-equal: "npm:^1.6.4" + checksum: 10c0/66423d53d1fd87be7fd36fc1d6d1db47eef89f10f65b7362b180ad5150833890b33d60838f518457ce59f13b7ba6c2b5b7be9cb75b161c12d0527f97ef7a7842 + languageName: node + linkType: hard + +"@umijs/plugin-run@npm:4.6.0": + version: 4.6.0 + resolution: "@umijs/plugin-run@npm:4.6.0" + dependencies: + tsx: "npm:3.12.2" + checksum: 10c0/0b4fe2fecc59746217c1dfb89d31eb95a09af0dcfbd9b1c390488bd8ad17dfe840915fa6ad7200e66f341a01d1086edb3108e9a1c74bbbf22cf032ba6896381c + languageName: node + linkType: hard + +"@umijs/plugins@npm:^4.0.55": + version: 4.6.0 + resolution: "@umijs/plugins@npm:4.6.0" + dependencies: + "@ahooksjs/use-request": "npm:^2.0.0" + "@ant-design/antd-theme-variable": "npm:^1.0.0" + "@ant-design/cssinjs": "npm:^1.9.1" + "@ant-design/icons": "npm:^4.7.0" + "@ant-design/moment-webpack-plugin": "npm:^0.0.3" + "@ant-design/pro-components": "npm:^2.0.1" + "@tanstack/react-query": "npm:^4.24.10" + "@tanstack/react-query-devtools": "npm:^4.24.10" + "@umijs/bundler-utils": "npm:4.6.0" + "@umijs/valtio": "npm:1.0.4" + antd-dayjs-webpack-plugin: "npm:^1.0.6" + axios: "npm:^0.27.2" + babel-plugin-import: "npm:^1.13.8" + babel-plugin-styled-components: "npm:2.1.4" + dayjs: "npm:^1.11.7" + dva-core: "npm:^2.0.4" + dva-immer: "npm:^1.0.0" + dva-loading: "npm:^3.0.22" + event-emitter: "npm:~0.3.5" + fast-deep-equal: "npm:3.1.3" + intl: "npm:1.2.5" + lodash: "npm:^4.17.21" + moment: "npm:^2.29.4" + qiankun: "npm:^2.10.1" + react-intl: "npm:3.12.1" + react-redux: "npm:^8.0.5" + redux: "npm:^4.2.1" + styled-components: "npm:6.1.1" + tslib: "npm:^2" + warning: "npm:^4.0.3" + checksum: 10c0/afe98f96d90a82cdcb7c7ffb115268f0d481a4018b5ad606f0bd175af9ea9e94f69b3081b24a32e10b71005e7e167e202e08dcbad9bd6ec15a642799420f5a10 + languageName: node + linkType: hard + +"@umijs/preset-umi@npm:4.6.0": + version: 4.6.0 + resolution: "@umijs/preset-umi@npm:4.6.0" + dependencies: + "@iconify/utils": "npm:2.1.1" + "@stagewise/toolbar": "npm:0.6.2" + "@svgr/core": "npm:6.5.1" + "@umijs/ast": "npm:4.6.0" + "@umijs/babel-preset-umi": "npm:4.6.0" + "@umijs/bundler-esbuild": "npm:4.6.0" + "@umijs/bundler-mako": "npm:0.11.10" + "@umijs/bundler-utils": "npm:4.6.0" + "@umijs/bundler-utoopack": "npm:4.6.0" + "@umijs/bundler-vite": "npm:4.6.0" + "@umijs/bundler-webpack": "npm:4.6.0" + "@umijs/core": "npm:4.6.0" + "@umijs/did-you-know": "npm:^1.0.4" + "@umijs/es-module-parser": "npm:0.0.7" + "@umijs/history": "npm:5.3.1" + "@umijs/mfsu": "npm:4.6.0" + "@umijs/plugin-run": "npm:4.6.0" + "@umijs/renderer-react": "npm:4.6.0" + "@umijs/server": "npm:4.6.0" + "@umijs/ui": "npm:3.0.1" + "@umijs/utils": "npm:4.6.0" + "@umijs/zod2ts": "npm:4.6.0" + babel-plugin-dynamic-import-node: "npm:2.3.3" + babel-plugin-react-compiler: "npm:0.0.0-experimental-c23de8d-20240515" + click-to-react-component: "npm:1.1.0" + core-js: "npm:3.34.0" + current-script-polyfill: "npm:1.0.0" + enhanced-resolve: "npm:5.9.3" + fast-glob: "npm:3.2.12" + html-webpack-plugin: "npm:5.5.0" + less-plugin-resolve: "npm:1.0.2" + path-to-regexp: "npm:1.7.0" + postcss: "npm:^8.4.21" + postcss-prefix-selector: "npm:1.16.0" + react: "npm:18.3.1" + react-dom: "npm:18.3.1" + react-router: "npm:6.3.0" + react-router-dom: "npm:6.3.0" + regenerator-runtime: "npm:0.13.11" + checksum: 10c0/e023a7ac278d4d369dc989e10d31e80d100fb3d832d76b326c507166bebf6ec585fecc6e1e84b61229045bcd0b07905a583a55d6c75b258ac048712a1f603a5f + languageName: node + linkType: hard + +"@umijs/react-refresh-webpack-plugin@npm:0.5.11": + version: 0.5.11 + resolution: "@umijs/react-refresh-webpack-plugin@npm:0.5.11" + dependencies: + ansi-html-community: "npm:^0.0.8" + common-path-prefix: "npm:^3.0.0" + core-js-pure: "npm:^3.23.3" + error-stack-parser: "npm:^2.0.6" + find-up: "npm:^5.0.0" + html-entities: "npm:^2.1.0" + loader-utils: "npm:^2.0.4" + schema-utils: "npm:^3.0.0" + source-map: "npm:^0.7.3" + peerDependencies: + "@types/webpack": 4.x || 5.x + react-refresh: ">=0.10.0 <1.0.0" + sockjs-client: ^1.4.0 + type-fest: ">=0.17.0 <5.0.0" + webpack: ">=4.43.0 <6.0.0" + webpack-dev-server: 3.x || 4.x + webpack-hot-middleware: 2.x + webpack-plugin-serve: 0.x || 1.x + peerDependenciesMeta: + "@types/webpack": + optional: true + sockjs-client: + optional: true + type-fest: + optional: true + webpack-dev-server: + optional: true + webpack-hot-middleware: + optional: true + webpack-plugin-serve: + optional: true + checksum: 10c0/18f4c36c365247168641192e54f3d80fd011f5646abb24eef3cbf4f6220d2509da3e62c6e768c26ad018515702fe9e128c27152f9ffbf519685bbbff68ccef04 + languageName: node + linkType: hard + +"@umijs/renderer-react@npm:4.6.0": + version: 4.6.0 + resolution: "@umijs/renderer-react@npm:4.6.0" + dependencies: + "@babel/runtime": "npm:7.23.6" + "@loadable/component": "npm:5.15.2" + history: "npm:5.3.0" + react-helmet-async: "npm:1.3.0" + react-router-dom: "npm:6.3.0" + peerDependencies: + react: ">=16.8" + react-dom: ">=16.8" + checksum: 10c0/56af998b26f90d9b67db409cf33faaa3919e50e9e24bee604012c2a043d9856ae9488d181e3b1017b40db825c449e7c08e5537e46c95cf2c7c0a9da45dc95139 + languageName: node + linkType: hard + +"@umijs/route-utils@npm:^4.0.0": + version: 4.0.3 + resolution: "@umijs/route-utils@npm:4.0.3" + checksum: 10c0/6f949fa76169d5f33a89d6bc12e389650c94f8eb337a9123e34e4e862eea479bbd32b0b691682048bb3d941950bf6333ce35562c765464e90dd811250997d818 + languageName: node + linkType: hard + +"@umijs/server@npm:4.6.0": + version: 4.6.0 + resolution: "@umijs/server@npm:4.6.0" + dependencies: + "@umijs/bundler-utils": "npm:4.6.0" + history: "npm:5.3.0" + react: "npm:18.3.1" + react-dom: "npm:18.3.1" + react-router-dom: "npm:6.3.0" + checksum: 10c0/81cd8205594cc01f65324ee4523eeca7e8553ff4da0b42b686f964b6ea18b42afb73b87a82e65974332c2706d44a1946e6d5e81397d93be4d08cf06ef68084bb + languageName: node + linkType: hard + +"@umijs/test@npm:4.6.0": + version: 4.6.0 + resolution: "@umijs/test@npm:4.6.0" + dependencies: + "@babel/plugin-transform-modules-commonjs": "npm:7.23.3" + "@jest/types": "npm:27.5.1" + "@umijs/bundler-utils": "npm:4.6.0" + "@umijs/utils": "npm:4.6.0" + babel-jest: "npm:^29.7.0" + esbuild: "npm:0.21.4" + identity-obj-proxy: "npm:3.0.0" + isomorphic-unfetch: "npm:4.0.2" + checksum: 10c0/be37f45d6ffe3e2ac2185bf154c77cf0e8a084758502cdf8e1a1be2a0bf2b96dad80cb4e47a4c0ee6692a921b9bd6d1dd4876f68ea011c1ae21e742bc98a4c6f + languageName: node + linkType: hard + +"@umijs/ui@npm:3.0.1": + version: 3.0.1 + resolution: "@umijs/ui@npm:3.0.1" + checksum: 10c0/1889eb9a888057c5e830e97f044d1954082f64821bd109724ae7891729c74f41e096c8fff82028587dc2fab4fb48bf22d37ff8d348c895327ffe3a3f266140a8 + languageName: node + linkType: hard + +"@umijs/use-params@npm:^1.0.9": + version: 1.0.9 + resolution: "@umijs/use-params@npm:1.0.9" + peerDependencies: + react: "*" + checksum: 10c0/7589cd2e8790ed59c18e5c3bacfb79845c2d292cecceb54ed049a0cc953e7057e73e8a6fa8b5d1aed9b16644ac3574e87bcd0a2b0bd09fa7aa2b520fcca89be6 + languageName: node + linkType: hard + +"@umijs/utils@npm:4.6.0": + version: 4.6.0 + resolution: "@umijs/utils@npm:4.6.0" + dependencies: + chokidar: "npm:3.5.3" + pino: "npm:7.11.0" + checksum: 10c0/898aa3ff89c83ed8aae9cc15b62f831e0ad0daf0fc60ef39132b4860bddb827e9f37f4b29adc132dc226c7a5a8f9e40b0304bd648f3b052e65cac6e8a5ff0ef0 + languageName: node + linkType: hard + +"@umijs/valtio@npm:1.0.4": + version: 1.0.4 + resolution: "@umijs/valtio@npm:1.0.4" + dependencies: + valtio: "npm:1.11.2" + checksum: 10c0/ce8cb6146b3a2d0d07d97d1062d863bb93b565f352602772d6c7f1419b5834e30b637e84db8d228c332c0e6fc32b2e483323c431275b8a0385b11acade42ec2a + languageName: node + linkType: hard + +"@umijs/zod2ts@npm:4.6.0": + version: 4.6.0 + resolution: "@umijs/zod2ts@npm:4.6.0" + checksum: 10c0/22b4dab4084db3c98a9886717c41a7d852cceaf0467fa0f9a05f09458182bbc6762c505155d5fa1875ae992a7b5f32afccdab07d02cc0b20d426a8740bd637f1 + languageName: node + linkType: hard + +"@ungap/structured-clone@npm:^1.2.0": + version: 1.3.0 + resolution: "@ungap/structured-clone@npm:1.3.0" + checksum: 10c0/0fc3097c2540ada1fc340ee56d58d96b5b536a2a0dab6e3ec17d4bfc8c4c86db345f61a375a8185f9da96f01c69678f836a2b57eeaa9e4b8eeafd26428e57b0a + languageName: node + linkType: hard + +"@utoo/pack-darwin-arm64@npm:1.0.0": + version: 1.0.0 + resolution: "@utoo/pack-darwin-arm64@npm:1.0.0" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@utoo/pack-darwin-x64@npm:1.0.0": + version: 1.0.0 + resolution: "@utoo/pack-darwin-x64@npm:1.0.0" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@utoo/pack-linux-arm64-gnu@npm:1.0.0": + version: 1.0.0 + resolution: "@utoo/pack-linux-arm64-gnu@npm:1.0.0" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@utoo/pack-linux-arm64-musl@npm:1.0.0": + version: 1.0.0 + resolution: "@utoo/pack-linux-arm64-musl@npm:1.0.0" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@utoo/pack-linux-x64-gnu@npm:1.0.0": + version: 1.0.0 + resolution: "@utoo/pack-linux-x64-gnu@npm:1.0.0" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@utoo/pack-linux-x64-musl@npm:1.0.0": + version: 1.0.0 + resolution: "@utoo/pack-linux-x64-musl@npm:1.0.0" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@utoo/pack-shared@npm:^0.0.7": + version: 0.0.7 + resolution: "@utoo/pack-shared@npm:0.0.7" + dependencies: + "@babel/code-frame": "npm:7.22.5" + picocolors: "npm:^1.1.1" + checksum: 10c0/c3c1ff5e6dc32512435b450667a80859dd04b949a58e325002cd698c91581a858844cf08a3854342ff61f851317104f6b4f12bb4f0b218ae6a398c54a34609b6 + languageName: node + linkType: hard + +"@utoo/pack-win32-x64-msvc@npm:1.0.0": + version: 1.0.0 + resolution: "@utoo/pack-win32-x64-msvc@npm:1.0.0" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@utoo/pack@npm:1.0.0": + version: 1.0.0 + resolution: "@utoo/pack@npm:1.0.0" + dependencies: + "@babel/code-frame": "npm:7.22.5" + "@swc/helpers": "npm:0.5.15" + "@utoo/pack-darwin-arm64": "npm:1.0.0" + "@utoo/pack-darwin-x64": "npm:1.0.0" + "@utoo/pack-linux-arm64-gnu": "npm:1.0.0" + "@utoo/pack-linux-arm64-musl": "npm:1.0.0" + "@utoo/pack-linux-x64-gnu": "npm:1.0.0" + "@utoo/pack-linux-x64-musl": "npm:1.0.0" + "@utoo/pack-shared": "npm:^0.0.7" + "@utoo/pack-win32-x64-msvc": "npm:1.0.0" + "@utoo/style-loader": "npm:^1.0.0" + find-up: "npm:4.1.0" + less: "npm:^4.0.0" + less-loader: "npm:^12.0.0" + loader-runner: "npm:^4.3.0" + nanoid: "npm:^3.3.11" + picocolors: "npm:^1.1.1" + postcss: "npm:8.4.31" + resolve-url-loader: "npm:^5.0.0" + sass: "npm:1.54.0" + sass-loader: "npm:^13.2.0" + send: "npm:0.17.1" + ws: "npm:^8.18.1" + peerDependencies: + "@types/webpack": ^5.28.5 + dependenciesMeta: + "@utoo/pack-darwin-arm64": + optional: true + "@utoo/pack-darwin-x64": + optional: true + "@utoo/pack-linux-arm64-gnu": + optional: true + "@utoo/pack-linux-arm64-musl": + optional: true + "@utoo/pack-linux-x64-gnu": + optional: true + "@utoo/pack-linux-x64-musl": + optional: true + "@utoo/pack-win32-x64-msvc": + optional: true + checksum: 10c0/112c9b07a41681d854d41ea7b9b3ff7ac1f0a73f6b4375ee0d48b97a79087996c02f8349298fd2c93c58791074b35cadb311567388bf3052ec879145a7e46157 + languageName: node + linkType: hard + +"@utoo/style-loader@npm:^1.0.0": + version: 1.0.1 + resolution: "@utoo/style-loader@npm:1.0.1" + checksum: 10c0/46afe36cd24fd92071a670b05e8e6fb72eeaa29c4e80ab74dba035ccabeaa5e373fc0becb7f97460058cbd66c916fa1e8f4e91272bc65b5dd512b363ea505fde + languageName: node + linkType: hard + +"@vitejs/plugin-react@npm:4.0.0": + version: 4.0.0 + resolution: "@vitejs/plugin-react@npm:4.0.0" + dependencies: + "@babel/core": "npm:^7.21.4" + "@babel/plugin-transform-react-jsx-self": "npm:^7.21.0" + "@babel/plugin-transform-react-jsx-source": "npm:^7.19.6" + react-refresh: "npm:^0.14.0" + peerDependencies: + vite: ^4.2.0 + checksum: 10c0/3cf2e044fb4c95dd7b0b3092dcc6c77d6f459ddfae6b1f8ea4ee1d57b33c158072ae9f1067eb1737b6706bad644457f261c70af196f676477fdf3a3ad5653da8 + languageName: node + linkType: hard + +"@xmldom/xmldom@npm:^0.8.8": + version: 0.8.11 + resolution: "@xmldom/xmldom@npm:0.8.11" + checksum: 10c0/e768623de72c95d3dae6b5da8e33dda0d81665047811b5498d23a328d45b13feb5536fe921d0308b96a4a8dd8addf80b1f6ef466508051c0b581e63e0dc74ed5 + languageName: node + linkType: hard + +"@xyflow/react@npm:^12.10.2": + version: 12.10.2 + resolution: "@xyflow/react@npm:12.10.2" + dependencies: + "@xyflow/system": "npm:0.0.76" + classcat: "npm:^5.0.3" + zustand: "npm:^4.4.0" + peerDependencies: + react: ">=17" + react-dom: ">=17" + checksum: 10c0/e6706bfd85c294c4b475851d4aa14f00a76e33106e67b620c349275c33f1bc46895a2daf75323630d049d6094b9f70d5fb940b823ac34dbf696449df4def1a94 + languageName: node + linkType: hard + +"@xyflow/system@npm:0.0.76": + version: 0.0.76 + resolution: "@xyflow/system@npm:0.0.76" + dependencies: + "@types/d3-drag": "npm:^3.0.7" + "@types/d3-interpolate": "npm:^3.0.4" + "@types/d3-selection": "npm:^3.0.10" + "@types/d3-transition": "npm:^3.0.8" + "@types/d3-zoom": "npm:^3.0.8" + d3-drag: "npm:^3.0.0" + d3-interpolate: "npm:^3.0.1" + d3-selection: "npm:^3.0.0" + d3-zoom: "npm:^3.0.0" + checksum: 10c0/49c78f7e7d1015bfd990011030d9375e237b7fd6bdbb3908f2b0cdb9cc7baddd49f104c0e75bf3a218ce9258845956293b62d81ff6df02db920c4ba3cabb3cfb + languageName: node + linkType: hard + +"abbrev@npm:^4.0.0": + version: 4.0.0 + resolution: "abbrev@npm:4.0.0" + checksum: 10c0/b4cc16935235e80702fc90192e349e32f8ef0ed151ef506aa78c81a7c455ec18375c4125414b99f84b2e055199d66383e787675f0bcd87da7a4dbd59f9eac1d5 + languageName: node + linkType: hard + +"accepts@npm:~1.3.8": + version: 1.3.8 + resolution: "accepts@npm:1.3.8" + dependencies: + mime-types: "npm:~2.1.34" + negotiator: "npm:0.6.3" + checksum: 10c0/3a35c5f5586cfb9a21163ca47a5f77ac34fa8ceb5d17d2fa2c0d81f41cbd7f8c6fa52c77e2c039acc0f4d09e71abdc51144246900f6bef5e3c4b333f77d89362 + languageName: node + linkType: hard + +"acorn-jsx@npm:^5.3.2": + version: 5.3.2 + resolution: "acorn-jsx@npm:5.3.2" + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + checksum: 10c0/4c54868fbef3b8d58927d5e33f0a4de35f59012fe7b12cf9dfbb345fb8f46607709e1c4431be869a23fb63c151033d84c4198fa9f79385cec34fcb1dd53974c1 + languageName: node + linkType: hard + +"acorn@npm:^8.15.0, acorn@npm:^8.9.0": + version: 8.15.0 + resolution: "acorn@npm:8.15.0" + bin: + acorn: bin/acorn + checksum: 10c0/dec73ff59b7d6628a01eebaece7f2bdb8bb62b9b5926dcad0f8931f2b8b79c2be21f6c68ac095592adb5adb15831a3635d9343e6a91d028bbe85d564875ec3ec + languageName: node + linkType: hard + +"add-dom-event-listener@npm:^1.1.0": + version: 1.1.0 + resolution: "add-dom-event-listener@npm:1.1.0" + dependencies: + object-assign: "npm:4.x" + checksum: 10c0/79e490bebebbc1dbded6d86240d1532cd319a4cdd2b7682e46411bd6224bb2d3ea41661eeccebbc53a004005dac8edaaf5c56c7981d3697ec8c5c83008f2b6e7 + languageName: node + linkType: hard + +"adjust-sourcemap-loader@npm:^4.0.0": + version: 4.0.0 + resolution: "adjust-sourcemap-loader@npm:4.0.0" + dependencies: + loader-utils: "npm:^2.0.0" + regex-parser: "npm:^2.2.11" + checksum: 10c0/6a6e5bb8b670e4e1238c708f6163e92aa2ad0308fe5913de73c89e4cbf41738ee0bcc5552b94d0b7bf8be435ee49b78c6de8a6db7badd80762051e843c8aa14f + languageName: node + linkType: hard + +"agent-base@npm:6": + version: 6.0.2 + resolution: "agent-base@npm:6.0.2" + dependencies: + debug: "npm:4" + checksum: 10c0/dc4f757e40b5f3e3d674bc9beb4f1048f4ee83af189bae39be99f57bf1f48dde166a8b0a5342a84b5944ee8e6ed1e5a9d801858f4ad44764e84957122fe46261 + languageName: node + linkType: hard + +"agent-base@npm:^7.1.0, agent-base@npm:^7.1.2": + version: 7.1.4 + resolution: "agent-base@npm:7.1.4" + checksum: 10c0/c2c9ab7599692d594b6a161559ada307b7a624fa4c7b03e3afdb5a5e31cd0e53269115b620fcab024c5ac6a6f37fa5eb2e004f076ad30f5f7e6b8b671f7b35fe + languageName: node + linkType: hard + +"ahooks@npm:^3.7.8": + version: 3.9.6 + resolution: "ahooks@npm:3.9.6" + dependencies: + "@babel/runtime": "npm:^7.21.0" + "@types/js-cookie": "npm:^3.0.6" + dayjs: "npm:^1.9.1" + intersection-observer: "npm:^0.12.0" + js-cookie: "npm:^3.0.5" + lodash: "npm:^4.17.21" + react-fast-compare: "npm:^3.2.2" + resize-observer-polyfill: "npm:^1.5.1" + screenfull: "npm:^5.0.0" + tslib: "npm:^2.4.1" + 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 + checksum: 10c0/5292a7934b2049cb9c41661d706b68c03e44cb8907d6bd4e19f48bc5261b611365bc5fc652487223fb60f590584adefa2ea1b42aeaab63888e35bba63cb131c4 + languageName: node + linkType: hard + +"ajv-keywords@npm:^3.4.1, ajv-keywords@npm:^3.5.2": + version: 3.5.2 + resolution: "ajv-keywords@npm:3.5.2" + peerDependencies: + ajv: ^6.9.1 + checksum: 10c0/0c57a47cbd656e8cdfd99d7c2264de5868918ffa207c8d7a72a7f63379d4333254b2ba03d69e3c035e996a3fd3eb6d5725d7a1597cca10694296e32510546360 + languageName: node + linkType: hard + +"ajv@npm:^6.10.0, ajv@npm:^6.12.0, ajv@npm:^6.12.4, ajv@npm:^6.12.5": + version: 6.12.6 + resolution: "ajv@npm:6.12.6" + dependencies: + fast-deep-equal: "npm:^3.1.1" + fast-json-stable-stringify: "npm:^2.0.0" + json-schema-traverse: "npm:^0.4.1" + uri-js: "npm:^4.2.2" + checksum: 10c0/41e23642cbe545889245b9d2a45854ebba51cda6c778ebced9649420d9205f2efb39cb43dbc41e358409223b1ea43303ae4839db682c848b891e4811da1a5a71 + languageName: node + linkType: hard + +"ajv@npm:^8.0.1": + version: 8.18.0 + resolution: "ajv@npm:8.18.0" + dependencies: + fast-deep-equal: "npm:^3.1.3" + fast-uri: "npm:^3.0.1" + json-schema-traverse: "npm:^1.0.0" + require-from-string: "npm:^2.0.2" + checksum: 10c0/e7517c426173513a07391be951879932bdf3348feaebd2199f5b901c20f99d60db8cd1591502d4d551dc82f594e82a05c4fe1c70139b15b8937f7afeaed9532f + languageName: node + linkType: hard + +"ali-react-table@npm:^2.6.1": + version: 2.6.1 + resolution: "ali-react-table@npm:2.6.1" + dependencies: + "@popperjs/core": "npm:^2.9.1" + "@types/classnames": "npm:^2.2.9" + classnames: "npm:^2.2.6" + resize-observer-polyfill: "npm:^1.5.1" + rxjs: "npm:^6.5.4" + styled-components: "npm:^3.4.10 || ^5.0.1" + peerDependencies: + react: ^16.8.0 || ^17.0.1 + checksum: 10c0/d32ac6dc81a6d417bc141f503461a4aaa52fc53edb5c1409ca565e55cc974d48fd6837f55af3e8dae8850606b2dfe8b9b8ab5d3eb81052bf0b793fc6ad1127ca + languageName: node + linkType: hard + +"ansi-html-community@npm:^0.0.8": + version: 0.0.8 + resolution: "ansi-html-community@npm:0.0.8" + bin: + ansi-html: bin/ansi-html + checksum: 10c0/45d3a6f0b4f10b04fdd44bef62972e2470bfd917bf00439471fa7473d92d7cbe31369c73db863cc45dda115cb42527f39e232e9256115534b8ee5806b0caeed4 + languageName: node + linkType: hard + +"ansi-regex@npm:^4.0.0": + version: 4.1.1 + resolution: "ansi-regex@npm:4.1.1" + checksum: 10c0/d36d34234d077e8770169d980fed7b2f3724bfa2a01da150ccd75ef9707c80e883d27cdf7a0eac2f145ac1d10a785a8a855cffd05b85f778629a0db62e7033da + languageName: node + linkType: hard + +"ansi-regex@npm:^5.0.1": + version: 5.0.1 + resolution: "ansi-regex@npm:5.0.1" + checksum: 10c0/9a64bb8627b434ba9327b60c027742e5d17ac69277960d041898596271d992d4d52ba7267a63ca10232e29f6107fc8a835f6ce8d719b88c5f8493f8254813737 + languageName: node + linkType: hard + +"ansi-regex@npm:^6.0.1, ansi-regex@npm:^6.2.2": + version: 6.2.2 + resolution: "ansi-regex@npm:6.2.2" + checksum: 10c0/05d4acb1d2f59ab2cf4b794339c7b168890d44dda4bf0ce01152a8da0213aca207802f930442ce8cd22d7a92f44907664aac6508904e75e038fa944d2601b30f + languageName: node + linkType: hard + +"ansi-styles@npm:^3.2.0, ansi-styles@npm:^3.2.1": + version: 3.2.1 + resolution: "ansi-styles@npm:3.2.1" + dependencies: + color-convert: "npm:^1.9.0" + checksum: 10c0/ece5a8ef069fcc5298f67e3f4771a663129abd174ea2dfa87923a2be2abf6cd367ef72ac87942da00ce85bd1d651d4cd8595aebdb1b385889b89b205860e977b + languageName: node + linkType: hard + +"ansi-styles@npm:^4.0.0, ansi-styles@npm:^4.1.0": + version: 4.3.0 + resolution: "ansi-styles@npm:4.3.0" + dependencies: + color-convert: "npm:^2.0.1" + checksum: 10c0/895a23929da416f2bd3de7e9cb4eabd340949328ab85ddd6e484a637d8f6820d485f53933446f5291c3b760cbc488beb8e88573dd0f9c7daf83dccc8fe81b041 + languageName: node + linkType: hard + +"ansi-styles@npm:^6.1.0": + version: 6.2.3 + resolution: "ansi-styles@npm:6.2.3" + checksum: 10c0/23b8a4ce14e18fb854693b95351e286b771d23d8844057ed2e7d083cd3e708376c3323707ec6a24365f7d7eda3ca00327fe04092e29e551499ec4c8b7bfac868 + languageName: node + linkType: hard + +"antd-dayjs-webpack-plugin@npm:^1.0.6": + version: 1.0.6 + resolution: "antd-dayjs-webpack-plugin@npm:1.0.6" + peerDependencies: + dayjs: "*" + checksum: 10c0/4bff4231172961a27e4f72382ea7ef2674356eddbc54fd3386156e5e9cc2e5c7b6adfd0679229c239cea4d48641eba9b70cd63e9d5a3794217b9acd8d88947fa + languageName: node + linkType: hard + +"antd@npm:^5.12.1": + version: 5.29.1 + resolution: "antd@npm:5.29.1" + dependencies: + "@ant-design/colors": "npm:^7.2.1" + "@ant-design/cssinjs": "npm:^1.23.0" + "@ant-design/cssinjs-utils": "npm:^1.1.3" + "@ant-design/fast-color": "npm:^2.0.6" + "@ant-design/icons": "npm:^5.6.1" + "@ant-design/react-slick": "npm:~1.1.2" + "@babel/runtime": "npm:^7.26.0" + "@rc-component/color-picker": "npm:~2.0.1" + "@rc-component/mutate-observer": "npm:^1.1.0" + "@rc-component/qrcode": "npm:~1.1.0" + "@rc-component/tour": "npm:~1.15.1" + "@rc-component/trigger": "npm:^2.3.0" + classnames: "npm:^2.5.1" + copy-to-clipboard: "npm:^3.3.3" + dayjs: "npm:^1.11.11" + rc-cascader: "npm:~3.34.0" + rc-checkbox: "npm:~3.5.0" + rc-collapse: "npm:~3.9.0" + rc-dialog: "npm:~9.6.0" + rc-drawer: "npm:~7.3.0" + rc-dropdown: "npm:~4.2.1" + rc-field-form: "npm:~2.7.1" + rc-image: "npm:~7.12.0" + rc-input: "npm:~1.8.0" + rc-input-number: "npm:~9.5.0" + rc-mentions: "npm:~2.20.0" + rc-menu: "npm:~9.16.1" + rc-motion: "npm:^2.9.5" + rc-notification: "npm:~5.6.4" + rc-pagination: "npm:~5.1.0" + rc-picker: "npm:~4.11.3" + rc-progress: "npm:~4.0.0" + rc-rate: "npm:~2.13.1" + rc-resize-observer: "npm:^1.4.3" + rc-segmented: "npm:~2.7.0" + rc-select: "npm:~14.16.8" + rc-slider: "npm:~11.1.9" + rc-steps: "npm:~6.0.1" + rc-switch: "npm:~4.1.0" + rc-table: "npm:~7.54.0" + rc-tabs: "npm:~15.7.0" + rc-textarea: "npm:~1.10.2" + rc-tooltip: "npm:~6.4.0" + rc-tree: "npm:~5.13.1" + rc-tree-select: "npm:~5.27.0" + rc-upload: "npm:~4.11.0" + rc-util: "npm:^5.44.4" + scroll-into-view-if-needed: "npm:^3.1.0" + throttle-debounce: "npm:^5.0.2" + peerDependencies: + react: ">=16.9.0" + react-dom: ">=16.9.0" + checksum: 10c0/e4d5698294839ec30de306a40adcdf297a21cbdbf73a7be03a3739244aa949f6d7b1b8df274b5e3d95585e5ab853f2f4e2079116970b99302daa22369bffbd78 + languageName: node + linkType: hard + +"any-promise@npm:^1.0.0": + version: 1.3.0 + resolution: "any-promise@npm:1.3.0" + checksum: 10c0/60f0298ed34c74fef50daab88e8dab786036ed5a7fad02e012ab57e376e0a0b4b29e83b95ea9b5e7d89df762f5f25119b83e00706ecaccb22cfbacee98d74889 + languageName: node + linkType: hard + +"anymatch@npm:^3.0.3, anymatch@npm:~3.1.2": + version: 3.1.3 + resolution: "anymatch@npm:3.1.3" + dependencies: + normalize-path: "npm:^3.0.0" + picomatch: "npm:^2.0.4" + checksum: 10c0/57b06ae984bc32a0d22592c87384cd88fe4511b1dd7581497831c56d41939c8a001b28e7b853e1450f2bf61992dfcaa8ae2d0d161a0a90c4fb631ef07098fbac + languageName: node + linkType: hard + +"app-builder-bin@npm:4.0.0": + version: 4.0.0 + resolution: "app-builder-bin@npm:4.0.0" + checksum: 10c0/9df57b2460aa058971c8619132c4ab5b7b4572449c8f5b562e44c9d6c1c73ec7284f4d1e170549c42eef53cd9e0b7579409fb49fba862ab4d3050433579ef14c + languageName: node + linkType: hard + +"app-builder-lib@npm:23.6.0": + version: 23.6.0 + resolution: "app-builder-lib@npm:23.6.0" + dependencies: + 7zip-bin: "npm:~5.1.1" + "@develar/schema-utils": "npm:~2.6.5" + "@electron/universal": "npm:1.2.1" + "@malept/flatpak-bundler": "npm:^0.4.0" + async-exit-hook: "npm:^2.0.1" + bluebird-lst: "npm:^1.0.9" + builder-util: "npm:23.6.0" + builder-util-runtime: "npm:9.1.1" + chromium-pickle-js: "npm:^0.2.0" + debug: "npm:^4.3.4" + ejs: "npm:^3.1.7" + electron-osx-sign: "npm:^0.6.0" + electron-publish: "npm:23.6.0" + form-data: "npm:^4.0.0" + fs-extra: "npm:^10.1.0" + hosted-git-info: "npm:^4.1.0" + is-ci: "npm:^3.0.0" + isbinaryfile: "npm:^4.0.10" + js-yaml: "npm:^4.1.0" + lazy-val: "npm:^1.0.5" + minimatch: "npm:^3.1.2" + read-config-file: "npm:6.2.0" + sanitize-filename: "npm:^1.6.3" + semver: "npm:^7.3.7" + tar: "npm:^6.1.11" + temp-file: "npm:^3.4.0" + checksum: 10c0/a4878df17dc24e7ac3cc9e4536fedff921bae8b6b953d708fff8a37bf4d533a23a112d3b904aec3a901b81222ef1fbe08b63e172ec94ae466d871d289b64b8fa + languageName: node + linkType: hard + +"arg@npm:^5.0.2": + version: 5.0.2 + resolution: "arg@npm:5.0.2" + checksum: 10c0/ccaf86f4e05d342af6666c569f844bec426595c567d32a8289715087825c2ca7edd8a3d204e4d2fb2aa4602e09a57d0c13ea8c9eea75aac3dbb4af5514e6800e + languageName: node + linkType: hard + +"argparse@npm:^1.0.7": + version: 1.0.10 + resolution: "argparse@npm:1.0.10" + dependencies: + sprintf-js: "npm:~1.0.2" + checksum: 10c0/b2972c5c23c63df66bca144dbc65d180efa74f25f8fd9b7d9a0a6c88ae839db32df3d54770dcb6460cf840d232b60695d1a6b1053f599d84e73f7437087712de + languageName: node + linkType: hard + +"argparse@npm:^2.0.1": + version: 2.0.1 + resolution: "argparse@npm:2.0.1" + checksum: 10c0/c5640c2d89045371c7cedd6a70212a04e360fd34d6edeae32f6952c63949e3525ea77dbec0289d8213a99bbaeab5abfa860b5c12cf88a2e6cf8106e90dd27a7e + languageName: node + linkType: hard + +"aria-hidden@npm:^1.1.3": + version: 1.2.6 + resolution: "aria-hidden@npm:1.2.6" + dependencies: + tslib: "npm:^2.0.0" + checksum: 10c0/7720cb539497a9f760f68f98a4b30f22c6767aa0e72fa7d58279f7c164e258fc38b2699828f8de881aab0fc8e9c56d1313a3f1a965046fc0381a554dbc72b54a + languageName: node + linkType: hard + +"array-buffer-byte-length@npm:^1.0.1, array-buffer-byte-length@npm:^1.0.2": + version: 1.0.2 + resolution: "array-buffer-byte-length@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.3" + is-array-buffer: "npm:^3.0.5" + checksum: 10c0/74e1d2d996941c7a1badda9cabb7caab8c449db9086407cad8a1b71d2604cc8abf105db8ca4e02c04579ec58b7be40279ddb09aea4784832984485499f48432d + languageName: node + linkType: hard + +"array-flatten@npm:1.1.1": + version: 1.1.1 + resolution: "array-flatten@npm:1.1.1" + checksum: 10c0/806966c8abb2f858b08f5324d9d18d7737480610f3bd5d3498aaae6eb5efdc501a884ba019c9b4a8f02ff67002058749d05548fd42fa8643f02c9c7f22198b91 + languageName: node + linkType: hard + +"array-includes@npm:^3.1.6, array-includes@npm:^3.1.8, array-includes@npm:^3.1.9": + version: 3.1.9 + resolution: "array-includes@npm:3.1.9" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.24.0" + es-object-atoms: "npm:^1.1.1" + get-intrinsic: "npm:^1.3.0" + is-string: "npm:^1.1.1" + math-intrinsics: "npm:^1.1.0" + checksum: 10c0/0235fa69078abeac05ac4250699c44996bc6f774a9cbe45db48674ce6bd142f09b327d31482ff75cf03344db4ea03eae23edb862d59378b484b47ed842574856 + languageName: node + linkType: hard + +"array-union@npm:^2.1.0": + version: 2.1.0 + resolution: "array-union@npm:2.1.0" + checksum: 10c0/429897e68110374f39b771ec47a7161fc6a8fc33e196857c0a396dc75df0b5f65e4d046674db764330b6bb66b39ef48dd7c53b6a2ee75cfb0681e0c1a7033962 + languageName: node + linkType: hard + +"array.prototype.findlast@npm:^1.2.5": + version: 1.2.5 + resolution: "array.prototype.findlast@npm:1.2.5" + dependencies: + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.2" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.0.0" + es-shim-unscopables: "npm:^1.0.2" + checksum: 10c0/ddc952b829145ab45411b9d6adcb51a8c17c76bf89c9dd64b52d5dffa65d033da8c076ed2e17091779e83bc892b9848188d7b4b33453c5565e65a92863cb2775 + languageName: node + linkType: hard + +"array.prototype.findlastindex@npm:^1.2.6": + version: 1.2.6 + resolution: "array.prototype.findlastindex@npm:1.2.6" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.9" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.1.1" + es-shim-unscopables: "npm:^1.1.0" + checksum: 10c0/82559310d2e57ec5f8fc53d7df420e3abf0ba497935de0a5570586035478ba7d07618cb18e2d4ada2da514c8fb98a034aaf5c06caa0a57e2f7f4c4adedef5956 + languageName: node + linkType: hard + +"array.prototype.flat@npm:^1.3.1, array.prototype.flat@npm:^1.3.3": + version: 1.3.3 + resolution: "array.prototype.flat@npm:1.3.3" + dependencies: + call-bind: "npm:^1.0.8" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.5" + es-shim-unscopables: "npm:^1.0.2" + checksum: 10c0/d90e04dfbc43bb96b3d2248576753d1fb2298d2d972e29ca7ad5ec621f0d9e16ff8074dae647eac4f31f4fb7d3f561a7ac005fb01a71f51705a13b5af06a7d8a + languageName: node + linkType: hard + +"array.prototype.flatmap@npm:^1.3.1, array.prototype.flatmap@npm:^1.3.3": + version: 1.3.3 + resolution: "array.prototype.flatmap@npm:1.3.3" + dependencies: + call-bind: "npm:^1.0.8" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.5" + es-shim-unscopables: "npm:^1.0.2" + checksum: 10c0/ba899ea22b9dc9bf276e773e98ac84638ed5e0236de06f13d63a90b18ca9e0ec7c97d622d899796e3773930b946cd2413d098656c0c5d8cc58c6f25c21e6bd54 + languageName: node + linkType: hard + +"array.prototype.tosorted@npm:^1.1.1, array.prototype.tosorted@npm:^1.1.4": + version: 1.1.4 + resolution: "array.prototype.tosorted@npm:1.1.4" + dependencies: + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.3" + es-errors: "npm:^1.3.0" + es-shim-unscopables: "npm:^1.0.2" + checksum: 10c0/eb3c4c4fc0381b0bf6dba2ea4d48d367c2827a0d4236a5718d97caaccc6b78f11f4cadf090736e86301d295a6aa4967ed45568f92ced51be8cbbacd9ca410943 + languageName: node + linkType: hard + +"arraybuffer.prototype.slice@npm:^1.0.4": + version: 1.0.4 + resolution: "arraybuffer.prototype.slice@npm:1.0.4" + dependencies: + array-buffer-byte-length: "npm:^1.0.1" + call-bind: "npm:^1.0.8" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.5" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.6" + is-array-buffer: "npm:^3.0.4" + checksum: 10c0/2f2459caa06ae0f7f615003f9104b01f6435cc803e11bd2a655107d52a1781dc040532dc44d93026b694cc18793993246237423e13a5337e86b43ed604932c06 + languageName: node + linkType: hard + +"asar@npm:^3.1.0": + version: 3.2.0 + resolution: "asar@npm:3.2.0" + dependencies: + "@types/glob": "npm:^7.1.1" + chromium-pickle-js: "npm:^0.2.0" + commander: "npm:^5.0.0" + glob: "npm:^7.1.6" + minimatch: "npm:^3.0.4" + dependenciesMeta: + "@types/glob": + optional: true + bin: + asar: bin/asar.js + checksum: 10c0/1eea9686e3df8102251b911951d374c4bb758ce2881471c94c3999f7c473c96be6036ac09aafbd9453ba43b901e96ad0082d7e1bafc12f6768571353297c516f + languageName: node + linkType: hard + +"asn1.js@npm:^4.10.1": + version: 4.10.1 + resolution: "asn1.js@npm:4.10.1" + dependencies: + bn.js: "npm:^4.0.0" + inherits: "npm:^2.0.1" + minimalistic-assert: "npm:^1.0.0" + checksum: 10c0/afa7f3ab9e31566c80175a75b182e5dba50589dcc738aa485be42bdd787e2a07246a4b034d481861123cbe646a7656f318f4f1cad2e9e5e808a210d5d6feaa88 + languageName: node + linkType: hard + +"assert-okam@npm:^1.1.1": + version: 1.5.0 + resolution: "assert-okam@npm:1.5.0" + dependencies: + object-assign: "npm:^4.1.1" + util: "npm:0.10.3" + checksum: 10c0/e8fe47a1db80b6caf698c530b20742d593acd7e3c98812b6fc184d6885767bd6c88a8ca9d21b374a64e69627a9356bcd5b841629a7d9f67a5db0c5daac266b16 + languageName: node + linkType: hard + +"assert-plus@npm:^1.0.0": + version: 1.0.0 + resolution: "assert-plus@npm:1.0.0" + checksum: 10c0/b194b9d50c3a8f872ee85ab110784911e696a4d49f7ee6fc5fb63216dedbefd2c55999c70cb2eaeb4cf4a0e0338b44e9ace3627117b5bf0d42460e9132f21b91 + languageName: node + linkType: hard + +"assert@npm:^1.1.1": + version: 1.5.1 + resolution: "assert@npm:1.5.1" + dependencies: + object.assign: "npm:^4.1.4" + util: "npm:^0.10.4" + checksum: 10c0/836688b928b68b7fc5bbc165443e16a62623d57676a1e8a980a0316f9ae86e5e0a102c63470491bf55a8545e75766303640c0c7ad1cf6bfa5450130396043bbd + languageName: node + linkType: hard + +"astral-regex@npm:^2.0.0": + version: 2.0.0 + resolution: "astral-regex@npm:2.0.0" + checksum: 10c0/f63d439cc383db1b9c5c6080d1e240bd14dae745f15d11ec5da863e182bbeca70df6c8191cffef5deba0b566ef98834610a68be79ac6379c95eeb26e1b310e25 + languageName: node + linkType: hard + +"async-exit-hook@npm:^2.0.1": + version: 2.0.1 + resolution: "async-exit-hook@npm:2.0.1" + checksum: 10c0/81407a440ef0aab328df2369f1a9d957ee53e9a5a43e3b3dcb2be05151a68de0e4ff5e927f4718c88abf85800731f5b3f69a47a6642ce135f5e7d43ca0fce41d + languageName: node + linkType: hard + +"async-function@npm:^1.0.0": + version: 1.0.0 + resolution: "async-function@npm:1.0.0" + checksum: 10c0/669a32c2cb7e45091330c680e92eaeb791bc1d4132d827591e499cd1f776ff5a873e77e5f92d0ce795a8d60f10761dec9ddfe7225a5de680f5d357f67b1aac73 + languageName: node + linkType: hard + +"async-generator-function@npm:^1.0.0": + version: 1.0.0 + resolution: "async-generator-function@npm:1.0.0" + checksum: 10c0/2c50ef856c543ad500d8d8777d347e3c1ba623b93e99c9263ecc5f965c1b12d2a140e2ab6e43c3d0b85366110696f28114649411cbcd10b452a92a2318394186 + languageName: node + linkType: hard + +"async@npm:^3.2.6": + version: 3.2.6 + resolution: "async@npm:3.2.6" + checksum: 10c0/36484bb15ceddf07078688d95e27076379cc2f87b10c03b6dd8a83e89475a3c8df5848859dd06a4c95af1e4c16fc973de0171a77f18ea00be899aca2a4f85e70 + languageName: node + linkType: hard + +"asynckit@npm:^0.4.0": + version: 0.4.0 + resolution: "asynckit@npm:0.4.0" + checksum: 10c0/d73e2ddf20c4eb9337e1b3df1a0f6159481050a5de457c55b14ea2e5cb6d90bb69e004c9af54737a5ee0917fcf2c9e25de67777bbe58261847846066ba75bc9d + languageName: node + linkType: hard + +"at-least-node@npm:^1.0.0": + version: 1.0.0 + resolution: "at-least-node@npm:1.0.0" + checksum: 10c0/4c058baf6df1bc5a1697cf182e2029c58cd99975288a13f9e70068ef5d6f4e1f1fd7c4d2c3c4912eae44797d1725be9700995736deca441b39f3e66d8dee97ef + languageName: node + linkType: hard + +"atomic-sleep@npm:^1.0.0": + version: 1.0.0 + resolution: "atomic-sleep@npm:1.0.0" + checksum: 10c0/e329a6665512736a9bbb073e1761b4ec102f7926cce35037753146a9db9c8104f5044c1662e4a863576ce544fb8be27cd2be6bc8c1a40147d03f31eb1cfb6e8a + languageName: node + linkType: hard + +"autoprefixer@npm:^10.4.6": + version: 10.4.22 + resolution: "autoprefixer@npm:10.4.22" + dependencies: + browserslist: "npm:^4.27.0" + caniuse-lite: "npm:^1.0.30001754" + fraction.js: "npm:^5.3.4" + normalize-range: "npm:^0.1.2" + picocolors: "npm:^1.1.1" + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.1.0 + bin: + autoprefixer: bin/autoprefixer + checksum: 10c0/2ae8d135af2deaaa5065a3a466c877787373c0ed766b8a8e8259d7871db79c1a7e1d9f6c9541c54fa95647511d3c2066bb08a30160e58c9bfa75506f9c18f3aa + languageName: node + linkType: hard + +"available-typed-arrays@npm:^1.0.7": + version: 1.0.7 + resolution: "available-typed-arrays@npm:1.0.7" + dependencies: + possible-typed-array-names: "npm:^1.0.0" + checksum: 10c0/d07226ef4f87daa01bd0fe80f8f310982e345f372926da2e5296aecc25c41cab440916bbaa4c5e1034b453af3392f67df5961124e4b586df1e99793a1374bdb2 + languageName: node + linkType: hard + +"axios@npm:^0.27.2": + version: 0.27.2 + resolution: "axios@npm:0.27.2" + dependencies: + follow-redirects: "npm:^1.14.9" + form-data: "npm:^4.0.0" + checksum: 10c0/76d673d2a90629944b44d6f345f01e58e9174690f635115d5ffd4aca495d99bcd8f95c590d5ccb473513f5ebc1d1a6e8934580d0c57cdd0498c3a101313ef771 + languageName: node + linkType: hard + +"babel-jest@npm:^29.7.0": + version: 29.7.0 + resolution: "babel-jest@npm:29.7.0" + dependencies: + "@jest/transform": "npm:^29.7.0" + "@types/babel__core": "npm:^7.1.14" + babel-plugin-istanbul: "npm:^6.1.1" + babel-preset-jest: "npm:^29.6.3" + chalk: "npm:^4.0.0" + graceful-fs: "npm:^4.2.9" + slash: "npm:^3.0.0" + peerDependencies: + "@babel/core": ^7.8.0 + checksum: 10c0/2eda9c1391e51936ca573dd1aedfee07b14c59b33dbe16ef347873ddd777bcf6e2fc739681e9e9661ab54ef84a3109a03725be2ac32cd2124c07ea4401cbe8c1 + languageName: node + linkType: hard + +"babel-plugin-dynamic-import-node@npm:2.3.3": + version: 2.3.3 + resolution: "babel-plugin-dynamic-import-node@npm:2.3.3" + dependencies: + object.assign: "npm:^4.1.0" + checksum: 10c0/1bd80df981e1fc1aff0cd4e390cf27aaa34f95f7620cd14dff07ba3bad56d168c098233a7d2deb2c9b1dc13643e596a6b94fc608a3412ee3c56e74a25cd2167e + languageName: node + linkType: hard + +"babel-plugin-import@npm:^1.13.8": + version: 1.13.8 + resolution: "babel-plugin-import@npm:1.13.8" + dependencies: + "@babel/helper-module-imports": "npm:^7.0.0" + checksum: 10c0/91da78cd28dff17188b025e0c40cdc823af8d9bceae4aac2a232d5510082c05a7d9b624303be4342b8178551bc38d435a5bd26ccc85f60aecebd7ab30477c315 + languageName: node + linkType: hard + +"babel-plugin-istanbul@npm:^6.1.1": + version: 6.1.1 + resolution: "babel-plugin-istanbul@npm:6.1.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.0.0" + "@istanbuljs/load-nyc-config": "npm:^1.0.0" + "@istanbuljs/schema": "npm:^0.1.2" + istanbul-lib-instrument: "npm:^5.0.4" + test-exclude: "npm:^6.0.0" + checksum: 10c0/1075657feb705e00fd9463b329921856d3775d9867c5054b449317d39153f8fbcebd3e02ebf00432824e647faff3683a9ca0a941325ef1afe9b3c4dd51b24beb + languageName: node + linkType: hard + +"babel-plugin-jest-hoist@npm:^29.6.3": + version: 29.6.3 + resolution: "babel-plugin-jest-hoist@npm:29.6.3" + dependencies: + "@babel/template": "npm:^7.3.3" + "@babel/types": "npm:^7.3.3" + "@types/babel__core": "npm:^7.1.14" + "@types/babel__traverse": "npm:^7.0.6" + checksum: 10c0/7e6451caaf7dce33d010b8aafb970e62f1b0c0b57f4978c37b0d457bbcf0874d75a395a102daf0bae0bd14eafb9f6e9a165ee5e899c0a4f1f3bb2e07b304ed2e + languageName: node + linkType: hard + +"babel-plugin-react-compiler@npm:0.0.0-experimental-c23de8d-20240515": + version: 0.0.0-experimental-c23de8d-20240515 + resolution: "babel-plugin-react-compiler@npm:0.0.0-experimental-c23de8d-20240515" + dependencies: + "@babel/generator": "npm:7.2.0" + "@babel/types": "npm:^7.19.0" + chalk: "npm:4" + invariant: "npm:^2.2.4" + pretty-format: "npm:^24" + zod: "npm:^3.22.4" + zod-validation-error: "npm:^2.1.0" + checksum: 10c0/6ee81977b34098dfbe087a1aa576188fdce92116cb8bfce0b2c563b3e6cf9710c5518aa8e99253cd8ec4c18a426ceabc232ce4cd3c65afb671159d44527465a7 + languageName: node + linkType: hard + +"babel-plugin-styled-components@npm:2.1.4, babel-plugin-styled-components@npm:>= 1.12.0": + version: 2.1.4 + resolution: "babel-plugin-styled-components@npm:2.1.4" + dependencies: + "@babel/helper-annotate-as-pure": "npm:^7.22.5" + "@babel/helper-module-imports": "npm:^7.22.5" + "@babel/plugin-syntax-jsx": "npm:^7.22.5" + lodash: "npm:^4.17.21" + picomatch: "npm:^2.3.1" + peerDependencies: + styled-components: ">= 2" + checksum: 10c0/553f35f5feb4b51fda9c9aeef8a31c1b66f430687ab17830b7cdacfe7e93f912aef55bf59e402f4e0a1fa7ad039768ab3626512bbb9bf1f76fcc67ba47e7a56e + languageName: node + linkType: hard + +"babel-preset-current-node-syntax@npm:^1.0.0": + version: 1.2.0 + resolution: "babel-preset-current-node-syntax@npm:1.2.0" + dependencies: + "@babel/plugin-syntax-async-generators": "npm:^7.8.4" + "@babel/plugin-syntax-bigint": "npm:^7.8.3" + "@babel/plugin-syntax-class-properties": "npm:^7.12.13" + "@babel/plugin-syntax-class-static-block": "npm:^7.14.5" + "@babel/plugin-syntax-import-attributes": "npm:^7.24.7" + "@babel/plugin-syntax-import-meta": "npm:^7.10.4" + "@babel/plugin-syntax-json-strings": "npm:^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators": "npm:^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator": "npm:^7.8.3" + "@babel/plugin-syntax-numeric-separator": "npm:^7.10.4" + "@babel/plugin-syntax-object-rest-spread": "npm:^7.8.3" + "@babel/plugin-syntax-optional-catch-binding": "npm:^7.8.3" + "@babel/plugin-syntax-optional-chaining": "npm:^7.8.3" + "@babel/plugin-syntax-private-property-in-object": "npm:^7.14.5" + "@babel/plugin-syntax-top-level-await": "npm:^7.14.5" + peerDependencies: + "@babel/core": ^7.0.0 || ^8.0.0-0 + checksum: 10c0/94a4f81cddf9b051045d08489e4fff7336292016301664c138cfa3d9ffe3fe2ba10a24ad6ae589fd95af1ac72ba0216e1653555c187e694d7b17be0c002bea10 + languageName: node + linkType: hard + +"babel-preset-jest@npm:^29.6.3": + version: 29.6.3 + resolution: "babel-preset-jest@npm:29.6.3" + dependencies: + babel-plugin-jest-hoist: "npm:^29.6.3" + babel-preset-current-node-syntax: "npm:^1.0.0" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/ec5fd0276b5630b05f0c14bb97cc3815c6b31600c683ebb51372e54dcb776cff790bdeeabd5b8d01ede375a040337ccbf6a3ccd68d3a34219125945e167ad943 + languageName: node + linkType: hard + +"bail@npm:^2.0.0": + version: 2.0.2 + resolution: "bail@npm:2.0.2" + checksum: 10c0/25cbea309ef6a1f56214187004e8f34014eb015713ea01fa5b9b7e9e776ca88d0fdffd64143ac42dc91966c915a4b7b683411b56e14929fad16153fc026ffb8b + languageName: node + linkType: hard + +"balanced-match@npm:^1.0.0": + version: 1.0.2 + resolution: "balanced-match@npm:1.0.2" + checksum: 10c0/9308baf0a7e4838a82bbfd11e01b1cb0f0cf2893bc1676c27c2a8c0e70cbae1c59120c3268517a8ae7fb6376b4639ef81ca22582611dbee4ed28df945134aaee + languageName: node + linkType: hard + +"base64-js@npm:^1.0.2, base64-js@npm:^1.3.1, base64-js@npm:^1.5.1": + version: 1.5.1 + resolution: "base64-js@npm:1.5.1" + checksum: 10c0/f23823513b63173a001030fae4f2dabe283b99a9d324ade3ad3d148e218134676f1ee8568c877cd79ec1c53158dcf2d2ba527a97c606618928ba99dd930102bf + languageName: node + linkType: hard + +"baseline-browser-mapping@npm:^2.8.25": + version: 2.8.31 + resolution: "baseline-browser-mapping@npm:2.8.31" + bin: + baseline-browser-mapping: dist/cli.js + checksum: 10c0/e0b2fcb41bf36c5e27e122a4d4cc9e5f6b9747d31cd0bd1f771aee9c490eb1e01cd11a31db32286bd4b9221139ee332b5ab7e3893c18a4dbd0ce8915a9e180ed + languageName: node + linkType: hard + +"big-integer@npm:^1.6.44": + version: 1.6.52 + resolution: "big-integer@npm:1.6.52" + checksum: 10c0/9604224b4c2ab3c43c075d92da15863077a9f59e5d4205f4e7e76acd0cd47e8d469ec5e5dba8d9b32aa233951893b29329ca56ac80c20ce094b4a647a66abae0 + languageName: node + linkType: hard + +"big.js@npm:^5.2.2": + version: 5.2.2 + resolution: "big.js@npm:5.2.2" + checksum: 10c0/230520f1ff920b2d2ce3e372d77a33faa4fa60d802fe01ca4ffbc321ee06023fe9a741ac02793ee778040a16b7e497f7d60c504d1c402b8fdab6f03bb785a25f + languageName: node + linkType: hard + +"binary-extensions@npm:^2.0.0": + version: 2.3.0 + resolution: "binary-extensions@npm:2.3.0" + checksum: 10c0/75a59cafc10fb12a11d510e77110c6c7ae3f4ca22463d52487709ca7f18f69d886aa387557cc9864fbdb10153d0bdb4caacabf11541f55e89ed6e18d12ece2b5 + languageName: node + linkType: hard + +"bluebird-lst@npm:^1.0.9": + version: 1.0.9 + resolution: "bluebird-lst@npm:1.0.9" + dependencies: + bluebird: "npm:^3.5.5" + checksum: 10c0/701eef18f37a53277adeacb21281a70fc4536e521fe0deb665a284f4d8480056c6932988c3dfa6a0c46b4d55f4599f716a15873f30ed5fc2470928093438f87e + languageName: node + linkType: hard + +"bluebird@npm:^3.5.0, bluebird@npm:^3.5.5": + version: 3.7.2 + resolution: "bluebird@npm:3.7.2" + checksum: 10c0/680de03adc54ff925eaa6c7bb9a47a0690e8b5de60f4792604aae8ed618c65e6b63a7893b57ca924beaf53eee69c5af4f8314148c08124c550fe1df1add897d2 + languageName: node + linkType: hard + +"bn.js@npm:^4.0.0, bn.js@npm:^4.1.0, bn.js@npm:^4.11.9": + version: 4.12.2 + resolution: "bn.js@npm:4.12.2" + checksum: 10c0/09a249faa416a9a1ce68b5f5ec8bbca87fe54e5dd4ef8b1cc8a4969147b80035592bddcb1e9cc814c3ba79e573503d5c5178664b722b509fb36d93620dba9b57 + languageName: node + linkType: hard + +"bn.js@npm:^5.2.1, bn.js@npm:^5.2.2": + version: 5.2.2 + resolution: "bn.js@npm:5.2.2" + checksum: 10c0/cb97827d476aab1a0194df33cd84624952480d92da46e6b4a19c32964aa01553a4a613502396712704da2ec8f831cf98d02e74ca03398404bd78a037ba93f2ab + languageName: node + linkType: hard + +"body-parser@npm:1.20.3": + version: 1.20.3 + resolution: "body-parser@npm:1.20.3" + dependencies: + bytes: "npm:3.1.2" + content-type: "npm:~1.0.5" + debug: "npm:2.6.9" + depd: "npm:2.0.0" + destroy: "npm:1.2.0" + http-errors: "npm:2.0.0" + iconv-lite: "npm:0.4.24" + on-finished: "npm:2.4.1" + qs: "npm:6.13.0" + raw-body: "npm:2.5.2" + type-is: "npm:~1.6.18" + unpipe: "npm:1.0.0" + checksum: 10c0/0a9a93b7518f222885498dcecaad528cf010dd109b071bf471c93def4bfe30958b83e03496eb9c1ad4896db543d999bb62be1a3087294162a88cfa1b42c16310 + languageName: node + linkType: hard + +"boolbase@npm:^1.0.0": + version: 1.0.0 + resolution: "boolbase@npm:1.0.0" + checksum: 10c0/e4b53deb4f2b85c52be0e21a273f2045c7b6a6ea002b0e139c744cb6f95e9ec044439a52883b0d74dedd1ff3da55ed140cfdddfed7fb0cccbed373de5dce1bcf + languageName: node + linkType: hard + +"boolean@npm:^3.0.1": + version: 3.2.0 + resolution: "boolean@npm:3.2.0" + checksum: 10c0/6a0dc9668f6f3dda42a53c181fcbdad223169c8d87b6c4011b87a8b14a21770efb2934a778f063d7ece17280f8c06d313c87f7b834bb1dd526a867ffcd00febf + languageName: node + linkType: hard + +"bplist-parser@npm:^0.2.0": + version: 0.2.0 + resolution: "bplist-parser@npm:0.2.0" + dependencies: + big-integer: "npm:^1.6.44" + checksum: 10c0/ce79c69e0f6efe506281e7c84e3712f7d12978991675b6e3a58a295b16f13ca81aa9b845c335614a545e0af728c8311b6aa3142af76ba1cb616af9bbac5c4a9f + languageName: node + linkType: hard + +"brace-expansion@npm:^1.1.7": + version: 1.1.12 + resolution: "brace-expansion@npm:1.1.12" + dependencies: + balanced-match: "npm:^1.0.0" + concat-map: "npm:0.0.1" + checksum: 10c0/975fecac2bb7758c062c20d0b3b6288c7cc895219ee25f0a64a9de662dbac981ff0b6e89909c3897c1f84fa353113a721923afdec5f8b2350255b097f12b1f73 + languageName: node + linkType: hard + +"brace-expansion@npm:^2.0.1": + version: 2.0.2 + resolution: "brace-expansion@npm:2.0.2" + dependencies: + balanced-match: "npm:^1.0.0" + checksum: 10c0/6d117a4c793488af86b83172deb6af143e94c17bc53b0b3cec259733923b4ca84679d506ac261f4ba3c7ed37c46018e2ff442f9ce453af8643ecd64f4a54e6cf + languageName: node + linkType: hard + +"braces@npm:^3.0.3, braces@npm:~3.0.2": + version: 3.0.3 + resolution: "braces@npm:3.0.3" + dependencies: + fill-range: "npm:^7.1.1" + checksum: 10c0/7c6dfd30c338d2997ba77500539227b9d1f85e388a5f43220865201e407e076783d0881f2d297b9f80951b4c957fcf0b51c1d2d24227631643c3f7c284b0aa04 + languageName: node + linkType: hard + +"brorand@npm:^1.0.1, brorand@npm:^1.1.0": + version: 1.1.0 + resolution: "brorand@npm:1.1.0" + checksum: 10c0/6f366d7c4990f82c366e3878492ba9a372a73163c09871e80d82fb4ae0d23f9f8924cb8a662330308206e6b3b76ba1d528b4601c9ef73c2166b440b2ea3b7571 + languageName: node + linkType: hard + +"browserify-aes@npm:^1.0.4, browserify-aes@npm:^1.2.0": + version: 1.2.0 + resolution: "browserify-aes@npm:1.2.0" + dependencies: + buffer-xor: "npm:^1.0.3" + cipher-base: "npm:^1.0.0" + create-hash: "npm:^1.1.0" + evp_bytestokey: "npm:^1.0.3" + inherits: "npm:^2.0.1" + safe-buffer: "npm:^5.0.1" + checksum: 10c0/967f2ae60d610b7b252a4cbb55a7a3331c78293c94b4dd9c264d384ca93354c089b3af9c0dd023534efdc74ffbc82510f7ad4399cf82bc37bc07052eea485f18 + languageName: node + linkType: hard + +"browserify-cipher@npm:^1.0.1": + version: 1.0.1 + resolution: "browserify-cipher@npm:1.0.1" + dependencies: + browserify-aes: "npm:^1.0.4" + browserify-des: "npm:^1.0.0" + evp_bytestokey: "npm:^1.0.0" + checksum: 10c0/aa256dcb42bc53a67168bbc94ab85d243b0a3b56109dee3b51230b7d010d9b78985ffc1fb36e145c6e4db151f888076c1cfc207baf1525d3e375cbe8187fe27d + languageName: node + linkType: hard + +"browserify-des@npm:^1.0.0": + version: 1.0.2 + resolution: "browserify-des@npm:1.0.2" + dependencies: + cipher-base: "npm:^1.0.1" + des.js: "npm:^1.0.0" + inherits: "npm:^2.0.1" + safe-buffer: "npm:^5.1.2" + checksum: 10c0/943eb5d4045eff80a6cde5be4e5fbb1f2d5002126b5a4789c3c1aae3cdddb1eb92b00fb92277f512288e5c6af330730b1dbabcf7ce0923e749e151fcee5a074d + languageName: node + linkType: hard + +"browserify-rsa@npm:^4.0.0, browserify-rsa@npm:^4.1.1": + version: 4.1.1 + resolution: "browserify-rsa@npm:4.1.1" + dependencies: + bn.js: "npm:^5.2.1" + randombytes: "npm:^2.1.0" + safe-buffer: "npm:^5.2.1" + checksum: 10c0/b650ee1192e3d7f3d779edc06dd96ed8720362e72ac310c367b9d7fe35f7e8dbb983c1829142b2b3215458be8bf17c38adc7224920843024ed8cf39e19c513c0 + languageName: node + linkType: hard + +"browserify-sign@npm:^4.2.3": + version: 4.2.5 + resolution: "browserify-sign@npm:4.2.5" + dependencies: + bn.js: "npm:^5.2.2" + browserify-rsa: "npm:^4.1.1" + create-hash: "npm:^1.2.0" + create-hmac: "npm:^1.1.7" + elliptic: "npm:^6.6.1" + inherits: "npm:^2.0.4" + parse-asn1: "npm:^5.1.9" + readable-stream: "npm:^2.3.8" + safe-buffer: "npm:^5.2.1" + checksum: 10c0/6192f9696934bbba58932d098face34c2ab9cac09feed826618b86b8c00a897dab7324cd9aa7d6cb1597064f197264ad72fa5418d4d52bf3c8f9b9e0e124655e + languageName: node + linkType: hard + +"browserify-zlib@npm:^0.2.0": + version: 0.2.0 + resolution: "browserify-zlib@npm:0.2.0" + dependencies: + pako: "npm:~1.0.5" + checksum: 10c0/9ab10b6dc732c6c5ec8ebcbe5cb7fe1467f97402c9b2140113f47b5f187b9438f93a8e065d8baf8b929323c18324fbf1105af479ee86d9d36cab7d7ef3424ad9 + languageName: node + linkType: hard + +"browserslist@npm:^4.20.3, browserslist@npm:^4.24.0, browserslist@npm:^4.27.0": + version: 4.28.0 + resolution: "browserslist@npm:4.28.0" + dependencies: + baseline-browser-mapping: "npm:^2.8.25" + caniuse-lite: "npm:^1.0.30001754" + electron-to-chromium: "npm:^1.5.249" + node-releases: "npm:^2.0.27" + update-browserslist-db: "npm:^1.1.4" + bin: + browserslist: cli.js + checksum: 10c0/4284fd568f7d40a496963083860d488cb2a89fb055b6affd316bebc59441fec938e090b3e62c0ee065eb0bc88cd1bc145f4300a16c75f3f565621c5823715ae1 + languageName: node + linkType: hard + +"bser@npm:2.1.1": + version: 2.1.1 + resolution: "bser@npm:2.1.1" + dependencies: + node-int64: "npm:^0.4.0" + checksum: 10c0/24d8dfb7b6d457d73f32744e678a60cc553e4ec0e9e1a01cf614b44d85c3c87e188d3cc78ef0442ce5032ee6818de20a0162ba1074725c0d08908f62ea979227 + languageName: node + linkType: hard + +"buffer-alloc-unsafe@npm:^1.1.0": + version: 1.1.0 + resolution: "buffer-alloc-unsafe@npm:1.1.0" + checksum: 10c0/06b9298c9369621a830227c3797ceb3ff5535e323946d7b39a7398fed8b3243798259b3c85e287608c5aad35ccc551cec1a0a5190cc8f39652e8eee25697fc9c + languageName: node + linkType: hard + +"buffer-alloc@npm:^1.2.0": + version: 1.2.0 + resolution: "buffer-alloc@npm:1.2.0" + dependencies: + buffer-alloc-unsafe: "npm:^1.1.0" + buffer-fill: "npm:^1.0.0" + checksum: 10c0/09d87dd53996342ccfbeb2871257d8cdb25ce9ee2259adc95c6490200cd6e528c5fbae8f30bcc323fe8d8efb0fe541e4ac3bbe9ee3f81c6b7c4b27434cc02ab4 + languageName: node + linkType: hard + +"buffer-crc32@npm:~0.2.3": + version: 0.2.13 + resolution: "buffer-crc32@npm:0.2.13" + checksum: 10c0/cb0a8ddf5cf4f766466db63279e47761eb825693eeba6a5a95ee4ec8cb8f81ede70aa7f9d8aeec083e781d47154290eb5d4d26b3f7a465ec57fb9e7d59c47150 + languageName: node + linkType: hard + +"buffer-equal@npm:1.0.0": + version: 1.0.0 + resolution: "buffer-equal@npm:1.0.0" + checksum: 10c0/2459f0b6a50dec18571c56dc2a2a0603d2078e79cb0cad2a6eabd093c9fc6e40e7c0a42170fa982324e8defb5d7fe7318e3a91458bc26a00ada1adef87849aaf + languageName: node + linkType: hard + +"buffer-fill@npm:^1.0.0": + version: 1.0.0 + resolution: "buffer-fill@npm:1.0.0" + checksum: 10c0/55b5654fbbf2d7ceb4991bb537f5e5b5b5b9debca583fee416a74fcec47c16d9e7a90c15acd27577da7bd750b7fa6396e77e7c221e7af138b6d26242381c6e4d + languageName: node + linkType: hard + +"buffer-from@npm:^1.0.0": + version: 1.1.2 + resolution: "buffer-from@npm:1.1.2" + checksum: 10c0/124fff9d66d691a86d3b062eff4663fe437a9d9ee4b47b1b9e97f5a5d14f6d5399345db80f796827be7c95e70a8e765dd404b7c3ff3b3324f98e9b0c8826cc34 + languageName: node + linkType: hard + +"buffer-okam@npm:^4.3.0": + version: 4.9.2 + resolution: "buffer-okam@npm:4.9.2" + dependencies: + base64-js: "npm:^1.0.2" + ieee754: "npm:^1.1.4" + isarray: "npm:^1.0.0" + checksum: 10c0/ea1b6e45ac891374ba561022a0a5edf2596018358b71ff93327fe78c5aebac2ffa8dc945d81e9a9ed0efa71b8841cec8a9002ec9a0aad301acd97cd17711b287 + languageName: node + linkType: hard + +"buffer-xor@npm:^1.0.3": + version: 1.0.3 + resolution: "buffer-xor@npm:1.0.3" + checksum: 10c0/fd269d0e0bf71ecac3146187cfc79edc9dbb054e2ee69b4d97dfb857c6d997c33de391696d04bdd669272751fa48e7872a22f3a6c7b07d6c0bc31dbe02a4075c + languageName: node + linkType: hard + +"buffer@npm:^4.3.0": + version: 4.9.2 + resolution: "buffer@npm:4.9.2" + dependencies: + base64-js: "npm:^1.0.2" + ieee754: "npm:^1.1.4" + isarray: "npm:^1.0.0" + checksum: 10c0/dc443d7e7caab23816b58aacdde710b72f525ad6eecd7d738fcaa29f6d6c12e8d9c13fed7219fd502be51ecf0615f5c077d4bdc6f9308dde2e53f8e5393c5b21 + languageName: node + linkType: hard + +"buffer@npm:^5.1.0": + version: 5.7.1 + resolution: "buffer@npm:5.7.1" + dependencies: + base64-js: "npm:^1.3.1" + ieee754: "npm:^1.1.13" + checksum: 10c0/27cac81cff434ed2876058d72e7c4789d11ff1120ef32c9de48f59eab58179b66710c488987d295ae89a228f835fc66d088652dffeb8e3ba8659f80eb091d55e + languageName: node + linkType: hard + +"builder-util-runtime@npm:9.1.1": + version: 9.1.1 + resolution: "builder-util-runtime@npm:9.1.1" + dependencies: + debug: "npm:^4.3.4" + sax: "npm:^1.2.4" + checksum: 10c0/6f0eadd6c600db982bb00a9e9b58f00e1c3b67c5dd6bbb940c0caab46b680cf666983a1469efb09861084d85a6a3779887990189f436387fb3057706c063165e + languageName: node + linkType: hard + +"builder-util@npm:23.6.0": + version: 23.6.0 + resolution: "builder-util@npm:23.6.0" + dependencies: + 7zip-bin: "npm:~5.1.1" + "@types/debug": "npm:^4.1.6" + "@types/fs-extra": "npm:^9.0.11" + app-builder-bin: "npm:4.0.0" + bluebird-lst: "npm:^1.0.9" + builder-util-runtime: "npm:9.1.1" + chalk: "npm:^4.1.1" + cross-spawn: "npm:^7.0.3" + debug: "npm:^4.3.4" + fs-extra: "npm:^10.0.0" + http-proxy-agent: "npm:^5.0.0" + https-proxy-agent: "npm:^5.0.0" + is-ci: "npm:^3.0.0" + js-yaml: "npm:^4.1.0" + source-map-support: "npm:^0.5.19" + stat-mode: "npm:^1.0.0" + temp-file: "npm:^3.4.0" + checksum: 10c0/1e8b5c865813c9fcd4d0a209be6f86493c650883dab5788be8cf58f45004fba029061a42e4f15d307d312fc08936281de538f0688692dfb037eb2bbdc3d77052 + languageName: node + linkType: hard + +"builtin-status-codes@npm:^3.0.0": + version: 3.0.0 + resolution: "builtin-status-codes@npm:3.0.0" + checksum: 10c0/c37bbba11a34c4431e56bd681b175512e99147defbe2358318d8152b3a01df7bf25e0305873947e5b350073d5ef41a364a22b37e48f1fb6d2fe6d5286a0f348c + languageName: node + linkType: hard + +"bundle-name@npm:^3.0.0": + version: 3.0.0 + resolution: "bundle-name@npm:3.0.0" + dependencies: + run-applescript: "npm:^5.0.0" + checksum: 10c0/57bc7f8b025d83961b04db2f1eff6a87f2363c2891f3542a4b82471ff8ebb5d484af48e9784fcdb28ef1d48bb01f03d891966dc3ef58758e46ea32d750ce40f8 + languageName: node + linkType: hard + +"bytes@npm:3.1.2, bytes@npm:~3.1.2": + version: 3.1.2 + resolution: "bytes@npm:3.1.2" + checksum: 10c0/76d1c43cbd602794ad8ad2ae94095cddeb1de78c5dddaa7005c51af10b0176c69971a6d88e805a90c2b6550d76636e43c40d8427a808b8645ede885de4a0358e + languageName: node + linkType: hard + +"cacache@npm:^20.0.1": + version: 20.0.3 + resolution: "cacache@npm:20.0.3" + dependencies: + "@npmcli/fs": "npm:^5.0.0" + fs-minipass: "npm:^3.0.0" + glob: "npm:^13.0.0" + lru-cache: "npm:^11.1.0" + minipass: "npm:^7.0.3" + minipass-collect: "npm:^2.0.1" + minipass-flush: "npm:^1.0.5" + minipass-pipeline: "npm:^1.2.4" + p-map: "npm:^7.0.2" + ssri: "npm:^13.0.0" + unique-filename: "npm:^5.0.0" + checksum: 10c0/c7da1ca694d20e8f8aedabd21dc11518f809a7d2b59aa76a1fc655db5a9e62379e465c157ddd2afe34b19230808882288effa6911b2de26a088a6d5645123462 + languageName: node + linkType: hard + +"cacheable-lookup@npm:^5.0.3": + version: 5.0.4 + resolution: "cacheable-lookup@npm:5.0.4" + checksum: 10c0/a6547fb4954b318aa831cbdd2f7b376824bc784fb1fa67610e4147099e3074726072d9af89f12efb69121415a0e1f2918a8ddd4aafcbcf4e91fbeef4a59cd42c + languageName: node + linkType: hard + +"cacheable-request@npm:^7.0.2": + version: 7.0.4 + resolution: "cacheable-request@npm:7.0.4" + dependencies: + clone-response: "npm:^1.0.2" + get-stream: "npm:^5.1.0" + http-cache-semantics: "npm:^4.0.0" + keyv: "npm:^4.0.0" + lowercase-keys: "npm:^2.0.0" + normalize-url: "npm:^6.0.1" + responselike: "npm:^2.0.0" + checksum: 10c0/0834a7d17ae71a177bc34eab06de112a43f9b5ad05ebe929bec983d890a7d9f2bc5f1aa8bb67ea2b65e07a3bc74bea35fa62dd36dbac52876afe36fdcf83da41 + languageName: node + linkType: hard + +"cacheable@npm:^2.3.4": + version: 2.3.4 + resolution: "cacheable@npm:2.3.4" + dependencies: + "@cacheable/memory": "npm:^2.0.8" + "@cacheable/utils": "npm:^2.4.0" + hookified: "npm:^1.15.0" + keyv: "npm:^5.6.0" + qified: "npm:^0.9.0" + checksum: 10c0/47139d83bed1a74f4e0bd5c102a0865146149fd192d572e61f141947ac6d7d8fa334794a25ac47d8df3758522b5c53a740ccfd31cc611fa098ea598ab08b7e20 + languageName: node + linkType: hard + +"call-bind-apply-helpers@npm:^1.0.0, call-bind-apply-helpers@npm:^1.0.1, call-bind-apply-helpers@npm:^1.0.2": + version: 1.0.2 + resolution: "call-bind-apply-helpers@npm:1.0.2" + dependencies: + es-errors: "npm:^1.3.0" + function-bind: "npm:^1.1.2" + checksum: 10c0/47bd9901d57b857590431243fea704ff18078b16890a6b3e021e12d279bbf211d039155e27d7566b374d49ee1f8189344bac9833dec7a20cdec370506361c938 + languageName: node + linkType: hard + +"call-bind@npm:^1.0.2, call-bind@npm:^1.0.7, call-bind@npm:^1.0.8": + version: 1.0.8 + resolution: "call-bind@npm:1.0.8" + dependencies: + call-bind-apply-helpers: "npm:^1.0.0" + es-define-property: "npm:^1.0.0" + get-intrinsic: "npm:^1.2.4" + set-function-length: "npm:^1.2.2" + checksum: 10c0/a13819be0681d915144467741b69875ae5f4eba8961eb0bf322aab63ec87f8250eb6d6b0dcbb2e1349876412a56129ca338592b3829ef4343527f5f18a0752d4 + languageName: node + linkType: hard + +"call-bound@npm:^1.0.2, call-bound@npm:^1.0.3, call-bound@npm:^1.0.4": + version: 1.0.4 + resolution: "call-bound@npm:1.0.4" + dependencies: + call-bind-apply-helpers: "npm:^1.0.2" + get-intrinsic: "npm:^1.3.0" + checksum: 10c0/f4796a6a0941e71c766aea672f63b72bc61234c4f4964dc6d7606e3664c307e7d77845328a8f3359ce39ddb377fed67318f9ee203dea1d47e46165dcf2917644 + languageName: node + linkType: hard + +"callsites@npm:^3.0.0": + version: 3.1.0 + resolution: "callsites@npm:3.1.0" + checksum: 10c0/fff92277400eb06c3079f9e74f3af120db9f8ea03bad0e84d9aede54bbe2d44a56cccb5f6cf12211f93f52306df87077ecec5b712794c5a9b5dac6d615a3f301 + languageName: node + linkType: hard + +"camel-case@npm:^4.1.2": + version: 4.1.2 + resolution: "camel-case@npm:4.1.2" + dependencies: + pascal-case: "npm:^3.1.2" + tslib: "npm:^2.0.3" + checksum: 10c0/bf9eefaee1f20edbed2e9a442a226793bc72336e2b99e5e48c6b7252b6f70b080fc46d8246ab91939e2af91c36cdd422e0af35161e58dd089590f302f8f64c8a + languageName: node + linkType: hard + +"camelcase-css@npm:^2.0.1": + version: 2.0.1 + resolution: "camelcase-css@npm:2.0.1" + checksum: 10c0/1a1a3137e8a781e6cbeaeab75634c60ffd8e27850de410c162cce222ea331cd1ba5364e8fb21c95e5ca76f52ac34b81a090925ca00a87221355746d049c6e273 + languageName: node + linkType: hard + +"camelcase@npm:^5.3.1": + version: 5.3.1 + resolution: "camelcase@npm:5.3.1" + checksum: 10c0/92ff9b443bfe8abb15f2b1513ca182d16126359ad4f955ebc83dc4ddcc4ef3fdd2c078bc223f2673dc223488e75c99b16cc4d056624374b799e6a1555cf61b23 + languageName: node + linkType: hard + +"camelcase@npm:^6.2.0": + version: 6.3.0 + resolution: "camelcase@npm:6.3.0" + checksum: 10c0/0d701658219bd3116d12da3eab31acddb3f9440790c0792e0d398f0a520a6a4058018e546862b6fba89d7ae990efaeb97da71e1913e9ebf5a8b5621a3d55c710 + languageName: node + linkType: hard + +"camelize@npm:^1.0.0": + version: 1.0.1 + resolution: "camelize@npm:1.0.1" + checksum: 10c0/4c9ac55efd356d37ac483bad3093758236ab686192751d1c9daa43188cc5a07b09bd431eb7458a4efd9ca22424bba23253e7b353feb35d7c749ba040de2385fb + languageName: node + linkType: hard + +"caniuse-lite@npm:^1.0.30001754": + version: 1.0.30001757 + resolution: "caniuse-lite@npm:1.0.30001757" + checksum: 10c0/3ccb71fa2bf1f8c96ff1bf9b918b08806fed33307e20a3ce3259155fda131eaf96cfcd88d3d309c8fd7f8285cc71d89a3b93648a1c04814da31c301f98508d42 + languageName: node + linkType: hard + +"ccount@npm:^2.0.0": + version: 2.0.1 + resolution: "ccount@npm:2.0.1" + checksum: 10c0/3939b1664390174484322bc3f45b798462e6c07ee6384cb3d645e0aa2f318502d174845198c1561930e1d431087f74cf1fe291ae9a4722821a9f4ba67e574350 + languageName: node + linkType: hard + +"chalk@npm:4, chalk@npm:^4.0.0, chalk@npm:^4.1.1, chalk@npm:^4.1.2": + version: 4.1.2 + resolution: "chalk@npm:4.1.2" + dependencies: + ansi-styles: "npm:^4.1.0" + supports-color: "npm:^7.1.0" + checksum: 10c0/4a3fef5cc34975c898ffe77141450f679721df9dde00f6c304353fa9c8b571929123b26a0e4617bde5018977eb655b31970c297b91b63ee83bb82aeb04666880 + languageName: node + linkType: hard + +"chalk@npm:^2.4.2": + version: 2.4.2 + resolution: "chalk@npm:2.4.2" + dependencies: + ansi-styles: "npm:^3.2.1" + escape-string-regexp: "npm:^1.0.5" + supports-color: "npm:^5.3.0" + checksum: 10c0/e6543f02ec877732e3a2d1c3c3323ddb4d39fbab687c23f526e25bd4c6a9bf3b83a696e8c769d078e04e5754921648f7821b2a2acfd16c550435fd630026e073 + languageName: node + linkType: hard + +"character-entities@npm:^2.0.0": + version: 2.0.2 + resolution: "character-entities@npm:2.0.2" + checksum: 10c0/b0c645a45bcc90ff24f0e0140f4875a8436b8ef13b6bcd31ec02cfb2ca502b680362aa95386f7815bdc04b6464d48cf191210b3840d7c04241a149ede591a308 + languageName: node + linkType: hard + +"chat2db@workspace:.": + version: 0.0.0-use.local + resolution: "chat2db@workspace:." + dependencies: + "@dagrejs/dagre": "npm:^3.0.0" + "@dnd-kit/modifiers": "npm:^6.0.1" + "@types/event-source-polyfill": "npm:^1.0.1" + "@types/lodash": "npm:^4.14.195" + "@types/react": "npm:^18.0.33" + "@types/react-dom": "npm:^18.0.11" + "@types/uuid": "npm:^9.0.1" + "@typescript-eslint/eslint-plugin": "npm:^6.7.2" + "@typescript-eslint/parser": "npm:^6.7.2" + "@umijs/plugins": "npm:^4.0.55" + "@xyflow/react": "npm:^12.10.2" + ahooks: "npm:^3.7.8" + ali-react-table: "npm:^2.6.1" + antd: "npm:^5.12.1" + concurrently: "npm:^8.1.0" + copy-to-clipboard: "npm:^3.3.3" + cross-env: "npm:^7.0.3" + echarts: "npm:^5.4.2" + echarts-for-react: "npm:^3.0.2" + electron: "npm:^22.3.0" + electron-builder: "npm:^23.6.0" + electron-debug: "npm:^3.2.0" + eslint: "npm:^8.49.0" + eslint-config-airbnb-base: "npm:^15.0.0" + eslint-config-prettier: "npm:^9.0.0" + eslint-import-resolver-webpack: "npm:^0.13.7" + eslint-plugin-babel: "npm:^5.3.1" + eslint-plugin-import: "npm:^2.28.1" + eslint-plugin-prettier: "npm:^5.0.0" + eslint-plugin-react: "npm:^7.33.2" + eslint-plugin-react-hooks: "npm:^4.6.0" + event-source-polyfill: "npm:^1.0.31" + highlight.js: "npm:^11.9.0" + html-to-image: "npm:^1.11.13" + is-electron: "npm:^2.2.2" + lodash: "npm:^4.17.21" + markdown-it-link-attributes: "npm:^4.0.1" + monaco-editor: "npm:^0.44.0" + monaco-editor-esm-webpack-plugin: "npm:^2.1.0" + monaco-editor-webpack-plugin: "npm:^7.0.1" + prettier: "npm:^2" + prettier-plugin-organize-imports: "npm:^2" + prettier-plugin-packagejson: "npm:^2" + react-markdown: "npm:^8.0.7" + react-monaco-editor: "npm:^0.54.0" + react-sortablejs: "npm:^6.1.4" + remark-gfm: "npm:3" + sql-formatter: "npm:^13.0.4" + styled-components: "npm:^6.0.1" + stylelint: "npm:^17.8.0" + stylelint-config-standard: "npm:^40.0.0" + tailwindcss: "npm:^3" + typescript: "npm:^5.0.3" + umi: "npm:^4.0.87" + umi-request: "npm:^1.4.0" + uuid: "npm:^9.0.0" + zustand: "npm:^4.4.4" + peerDependencies: + react: ^16.8.0 + react-dom: ^16.8.0 + languageName: unknown + linkType: soft + +"chokidar@npm:3.5.3": + version: 3.5.3 + resolution: "chokidar@npm:3.5.3" + dependencies: + anymatch: "npm:~3.1.2" + braces: "npm:~3.0.2" + fsevents: "npm:~2.3.2" + glob-parent: "npm:~5.1.2" + is-binary-path: "npm:~2.1.0" + is-glob: "npm:~4.0.1" + normalize-path: "npm:~3.0.0" + readdirp: "npm:~3.6.0" + dependenciesMeta: + fsevents: + optional: true + checksum: 10c0/1076953093e0707c882a92c66c0f56ba6187831aa51bb4de878c1fec59ae611a3bf02898f190efec8e77a086b8df61c2b2a3ea324642a0558bdf8ee6c5dc9ca1 + languageName: node + linkType: hard + +"chokidar@npm:>=3.0.0 <4.0.0, chokidar@npm:^3.5.3, chokidar@npm:^3.6.0": + version: 3.6.0 + resolution: "chokidar@npm:3.6.0" + dependencies: + anymatch: "npm:~3.1.2" + braces: "npm:~3.0.2" + fsevents: "npm:~2.3.2" + glob-parent: "npm:~5.1.2" + is-binary-path: "npm:~2.1.0" + is-glob: "npm:~4.0.1" + normalize-path: "npm:~3.0.0" + readdirp: "npm:~3.6.0" + dependenciesMeta: + fsevents: + optional: true + checksum: 10c0/8361dcd013f2ddbe260eacb1f3cb2f2c6f2b0ad118708a343a5ed8158941a39cb8fb1d272e0f389712e74ee90ce8ba864eece9e0e62b9705cb468a2f6d917462 + languageName: node + linkType: hard + +"chownr@npm:^2.0.0": + version: 2.0.0 + resolution: "chownr@npm:2.0.0" + checksum: 10c0/594754e1303672171cc04e50f6c398ae16128eb134a88f801bf5354fd96f205320f23536a045d9abd8b51024a149696e51231565891d4efdab8846021ecf88e6 + languageName: node + linkType: hard + +"chownr@npm:^3.0.0": + version: 3.0.0 + resolution: "chownr@npm:3.0.0" + checksum: 10c0/43925b87700f7e3893296c8e9c56cc58f926411cce3a6e5898136daaf08f08b9a8eb76d37d3267e707d0dcc17aed2e2ebdf5848c0c3ce95cf910a919935c1b10 + languageName: node + linkType: hard + +"chromium-pickle-js@npm:^0.2.0": + version: 0.2.0 + resolution: "chromium-pickle-js@npm:0.2.0" + checksum: 10c0/0a95bd280acdf05b0e08fa1a0e1db58c815dd24e92d639add8f494d23a8a49c26e4829721224d68f2f0e57a69047714db29bcff6deb5d029332321223416cb29 + languageName: node + linkType: hard + +"ci-info@npm:^3.2.0": + version: 3.9.0 + resolution: "ci-info@npm:3.9.0" + checksum: 10c0/6f0109e36e111684291d46123d491bc4e7b7a1934c3a20dea28cba89f1d4a03acd892f5f6a81ed3855c38647e285a150e3c9ba062e38943bef57fee6c1554c3a + languageName: node + linkType: hard + +"cipher-base@npm:^1.0.0, cipher-base@npm:^1.0.1, cipher-base@npm:^1.0.3": + version: 1.0.7 + resolution: "cipher-base@npm:1.0.7" + dependencies: + inherits: "npm:^2.0.4" + safe-buffer: "npm:^5.2.1" + to-buffer: "npm:^1.2.2" + checksum: 10c0/53c5046a9d9b60c586479b8f13fde263c3f905e13f11e8e04c7a311ce399c91d9c3ec96642332e0de077d356e1014ee12bba96f74fbaad0de750f49122258836 + languageName: node + linkType: hard + +"classcat@npm:^5.0.3": + version: 5.0.5 + resolution: "classcat@npm:5.0.5" + checksum: 10c0/ff8d273055ef9b518529cfe80fd0486f7057a9917373807ff802d75ceb46e8f8e148f41fa094ee7625c8f34642cfaa98395ff182d9519898da7cbf383d4a210d + languageName: node + linkType: hard + +"classnames@npm:*, classnames@npm:2.x, classnames@npm:^2.2.1, classnames@npm:^2.2.3, classnames@npm:^2.2.5, classnames@npm:^2.2.6, classnames@npm:^2.3.1, classnames@npm:^2.3.2, classnames@npm:^2.5.1": + version: 2.5.1 + resolution: "classnames@npm:2.5.1" + checksum: 10c0/afff4f77e62cea2d79c39962980bf316bacb0d7c49e13a21adaadb9221e1c6b9d3cdb829d8bb1b23c406f4e740507f37e1dcf506f7e3b7113d17c5bab787aa69 + languageName: node + linkType: hard + +"classnames@npm:2.3.1": + version: 2.3.1 + resolution: "classnames@npm:2.3.1" + checksum: 10c0/e3b832219042802464e648c41c2e8be96c2c64d2522cfa22fbb5ec088418406c61ab351a682c077c07f691c8b00c9f0ee7939b20fabc6c23da69063252a4ab89 + languageName: node + linkType: hard + +"clean-css@npm:^5.2.2": + version: 5.3.3 + resolution: "clean-css@npm:5.3.3" + dependencies: + source-map: "npm:~0.6.0" + checksum: 10c0/381de7523e23f3762eb180e327dcc0cedafaf8cb1cd8c26b7cc1fc56e0829a92e734729c4f955394d65ed72fb62f82d8baf78af34b33b8a7d41ebad2accdd6fb + languageName: node + linkType: hard + +"cli-truncate@npm:^2.1.0": + version: 2.1.0 + resolution: "cli-truncate@npm:2.1.0" + dependencies: + slice-ansi: "npm:^3.0.0" + string-width: "npm:^4.2.0" + checksum: 10c0/dfaa3df675bcef7a3254773de768712b590250420345a4c7ac151f041a4bacb4c25864b1377bee54a39b5925a030c00eabf014e312e3a4ac130952ed3b3879e9 + languageName: node + linkType: hard + +"click-to-react-component@npm:1.1.0": + version: 1.1.0 + resolution: "click-to-react-component@npm:1.1.0" + dependencies: + "@floating-ui/react-dom-interactions": "npm:^0.3.1" + htm: "npm:^3.1.0" + react-merge-refs: "npm:^1.1.0" + peerDependencies: + react: ">=16.8.0" + checksum: 10c0/292acb90fdadffc70ba4cc087b8265ec9848c619b0e380a68a937f37679b0f94123a5b07b51097378eb7a0dbea1e127c5f3c792884ff5151c2530f7fffb92a07 + languageName: node + linkType: hard + +"cliui@npm:^8.0.1": + version: 8.0.1 + resolution: "cliui@npm:8.0.1" + dependencies: + string-width: "npm:^4.2.0" + strip-ansi: "npm:^6.0.1" + wrap-ansi: "npm:^7.0.0" + checksum: 10c0/4bda0f09c340cbb6dfdc1ed508b3ca080f12992c18d68c6be4d9cf51756033d5266e61ec57529e610dacbf4da1c634423b0c1b11037709cc6b09045cbd815df5 + languageName: node + linkType: hard + +"clone-response@npm:^1.0.2": + version: 1.0.3 + resolution: "clone-response@npm:1.0.3" + dependencies: + mimic-response: "npm:^1.0.0" + checksum: 10c0/06a2b611824efb128810708baee3bd169ec9a1bf5976a5258cd7eb3f7db25f00166c6eee5961f075c7e38e194f373d4fdf86b8166ad5b9c7e82bbd2e333a6087 + languageName: node + linkType: hard + +"color-convert@npm:^1.9.0": + version: 1.9.3 + resolution: "color-convert@npm:1.9.3" + dependencies: + color-name: "npm:1.1.3" + checksum: 10c0/5ad3c534949a8c68fca8fbc6f09068f435f0ad290ab8b2f76841b9e6af7e0bb57b98cb05b0e19fe33f5d91e5a8611ad457e5f69e0a484caad1f7487fd0e8253c + languageName: node + linkType: hard + +"color-convert@npm:^2.0.1": + version: 2.0.1 + resolution: "color-convert@npm:2.0.1" + dependencies: + color-name: "npm:~1.1.4" + checksum: 10c0/37e1150172f2e311fe1b2df62c6293a342ee7380da7b9cfdba67ea539909afbd74da27033208d01d6d5cfc65ee7868a22e18d7e7648e004425441c0f8a15a7d7 + languageName: node + linkType: hard + +"color-name@npm:1.1.3": + version: 1.1.3 + resolution: "color-name@npm:1.1.3" + checksum: 10c0/566a3d42cca25b9b3cd5528cd7754b8e89c0eb646b7f214e8e2eaddb69994ac5f0557d9c175eb5d8f0ad73531140d9c47525085ee752a91a2ab15ab459caf6d6 + languageName: node + linkType: hard + +"color-name@npm:~1.1.4": + version: 1.1.4 + resolution: "color-name@npm:1.1.4" + checksum: 10c0/a1a3f914156960902f46f7f56bc62effc6c94e84b2cae157a526b1c1f74b677a47ec602bf68a61abfa2b42d15b7c5651c6dbe72a43af720bc588dff885b10f95 + languageName: node + linkType: hard + +"colord@npm:^2.9.3": + version: 2.9.3 + resolution: "colord@npm:2.9.3" + checksum: 10c0/9699e956894d8996b28c686afe8988720785f476f59335c80ce852ded76ab3ebe252703aec53d9bef54f6219aea6b960fb3d9a8300058a1d0c0d4026460cd110 + languageName: node + linkType: hard + +"colors@npm:1.0.3": + version: 1.0.3 + resolution: "colors@npm:1.0.3" + checksum: 10c0/f9e40dd8b3e1a65378a7ced3fced15ddfd60aaf38e99a7521a7fdb25056b15e092f651cd0f5aa1e9b04fa8ce3616d094e07fc6c2bb261e24098db1ddd3d09a1d + languageName: node + linkType: hard + +"combined-stream@npm:^1.0.8": + version: 1.0.8 + resolution: "combined-stream@npm:1.0.8" + dependencies: + delayed-stream: "npm:~1.0.0" + checksum: 10c0/0dbb829577e1b1e839fa82b40c07ffaf7de8a09b935cadd355a73652ae70a88b4320db322f6634a4ad93424292fa80973ac6480986247f1734a1137debf271d5 + languageName: node + linkType: hard + +"comma-separated-tokens@npm:^2.0.0": + version: 2.0.3 + resolution: "comma-separated-tokens@npm:2.0.3" + checksum: 10c0/91f90f1aae320f1755d6957ef0b864fe4f54737f3313bd95e0802686ee2ca38bff1dd381964d00ae5db42912dd1f4ae5c2709644e82706ffc6f6842a813cdd67 + languageName: node + linkType: hard + +"commander@npm:2.9.0": + version: 2.9.0 + resolution: "commander@npm:2.9.0" + dependencies: + graceful-readlink: "npm:>= 1.0.0" + checksum: 10c0/56bcda1e47f453016ed25d9f300bed9e622842a5515802658adb62792fa2ff9af6ee3f9ff16e058d7b20aacc78fb3baa3e02f982414bae1fb5f198c7cb41d5ad + languageName: node + linkType: hard + +"commander@npm:^2.19.0, commander@npm:^2.20.0": + version: 2.20.3 + resolution: "commander@npm:2.20.3" + checksum: 10c0/74c781a5248c2402a0a3e966a0a2bba3c054aad144f5c023364be83265e796b20565aa9feff624132ff629aa64e16999fa40a743c10c12f7c61e96a794b99288 + languageName: node + linkType: hard + +"commander@npm:^4.0.0": + version: 4.1.1 + resolution: "commander@npm:4.1.1" + checksum: 10c0/84a76c08fe6cc08c9c93f62ac573d2907d8e79138999312c92d4155bc2325d487d64d13f669b2000c9f8caf70493c1be2dac74fec3c51d5a04f8bc3ae1830bab + languageName: node + linkType: hard + +"commander@npm:^5.0.0": + version: 5.1.0 + resolution: "commander@npm:5.1.0" + checksum: 10c0/da9d71dbe4ce039faf1fe9eac3771dca8c11d66963341f62602f7b66e36d2a3f8883407af4f9a37b1db1a55c59c0c1325f186425764c2e963dc1d67aec2a4b6d + languageName: node + linkType: hard + +"commander@npm:^7.2.0": + version: 7.2.0 + resolution: "commander@npm:7.2.0" + checksum: 10c0/8d690ff13b0356df7e0ebbe6c59b4712f754f4b724d4f473d3cc5b3fdcf978e3a5dc3078717858a2ceb50b0f84d0660a7f22a96cdc50fb877d0c9bb31593d23a + languageName: node + linkType: hard + +"commander@npm:^8.3.0": + version: 8.3.0 + resolution: "commander@npm:8.3.0" + checksum: 10c0/8b043bb8322ea1c39664a1598a95e0495bfe4ca2fad0d84a92d7d1d8d213e2a155b441d2470c8e08de7c4a28cf2bc6e169211c49e1b21d9f7edc6ae4d9356060 + languageName: node + linkType: hard + +"common-path-prefix@npm:^3.0.0": + version: 3.0.0 + resolution: "common-path-prefix@npm:3.0.0" + checksum: 10c0/c4a74294e1b1570f4a8ab435285d185a03976c323caa16359053e749db4fde44e3e6586c29cd051100335e11895767cbbd27ea389108e327d62f38daf4548fdb + languageName: node + linkType: hard + +"compare-version@npm:^0.1.2": + version: 0.1.2 + resolution: "compare-version@npm:0.1.2" + checksum: 10c0/f38b853cf0d244c0af5f444409abde3d2198cd97312efa1dbc4ab41b520009327c2a63db59bbaf2d69288eff6167ef22be9788dc5476157d073ecdff1a8eeb2d + languageName: node + linkType: hard + +"compressible@npm:~2.0.18": + version: 2.0.18 + resolution: "compressible@npm:2.0.18" + dependencies: + mime-db: "npm:>= 1.43.0 < 2" + checksum: 10c0/8a03712bc9f5b9fe530cc5a79e164e665550d5171a64575d7dcf3e0395d7b4afa2d79ab176c61b5b596e28228b350dd07c1a2a6ead12fd81d1b6cd632af2fef7 + languageName: node + linkType: hard + +"compression@npm:^1.7.4": + version: 1.8.1 + resolution: "compression@npm:1.8.1" + dependencies: + bytes: "npm:3.1.2" + compressible: "npm:~2.0.18" + debug: "npm:2.6.9" + negotiator: "npm:~0.6.4" + on-headers: "npm:~1.1.0" + safe-buffer: "npm:5.2.1" + vary: "npm:~1.1.2" + checksum: 10c0/85114b0b91c16594dc8c671cd9b05ef5e465066a60e5a4ed8b4551661303559a896ed17bb72c4234c04064e078f6ca86a34b8690349499a43f6fc4b844475da4 + languageName: node + linkType: hard + +"compute-scroll-into-view@npm:^3.0.2": + version: 3.1.1 + resolution: "compute-scroll-into-view@npm:3.1.1" + checksum: 10c0/59761ed62304a9599b52ad75d0d6fbf0669ee2ab7dd472fdb0ad9da36628414c014dea7b5810046560180ad30ffec52a953d19297f66a1d4f3aa0999b9d2521d + languageName: node + linkType: hard + +"concat-map@npm:0.0.1": + version: 0.0.1 + resolution: "concat-map@npm:0.0.1" + checksum: 10c0/c996b1cfdf95b6c90fee4dae37e332c8b6eb7d106430c17d538034c0ad9a1630cb194d2ab37293b1bdd4d779494beee7786d586a50bd9376fd6f7bcc2bd4c98f + languageName: node + linkType: hard + +"concurrently@npm:^8.1.0": + version: 8.2.2 + resolution: "concurrently@npm:8.2.2" + dependencies: + chalk: "npm:^4.1.2" + date-fns: "npm:^2.30.0" + lodash: "npm:^4.17.21" + rxjs: "npm:^7.8.1" + shell-quote: "npm:^1.8.1" + spawn-command: "npm:0.0.2" + supports-color: "npm:^8.1.1" + tree-kill: "npm:^1.2.2" + yargs: "npm:^17.7.2" + bin: + conc: dist/bin/concurrently.js + concurrently: dist/bin/concurrently.js + checksum: 10c0/0e9683196fe9c071d944345d21d8f34aa6c0cc50c0dd897e95619f2f1c9eb4871dca851b2569da17888235b7335b4c821ca19deed35bebcd9a131ee5d247f34c + languageName: node + linkType: hard + +"confusing-browser-globals@npm:^1.0.10": + version: 1.0.11 + resolution: "confusing-browser-globals@npm:1.0.11" + checksum: 10c0/475d0a284fa964a5182b519af5738b5b64bf7e413cfd703c1b3496bf6f4df9f827893a9b221c0ea5873c1476835beb1e0df569ba643eff0734010c1eb780589e + languageName: node + linkType: hard + +"connect-history-api-fallback@npm:^2.0.0": + version: 2.0.0 + resolution: "connect-history-api-fallback@npm:2.0.0" + checksum: 10c0/90fa8b16ab76e9531646cc70b010b1dbd078153730c510d3142f6cf07479ae8a812c5a3c0e40a28528dd1681a62395d0cfdef67da9e914c4772ac85d69a3ed87 + languageName: node + linkType: hard + +"console-browserify@npm:^1.1.0": + version: 1.2.0 + resolution: "console-browserify@npm:1.2.0" + checksum: 10c0/89b99a53b7d6cee54e1e64fa6b1f7ac24b844b4019c5d39db298637e55c1f4ffa5c165457ad984864de1379df2c8e1886cbbdac85d9dbb6876a9f26c3106f226 + languageName: node + linkType: hard + +"constants-browserify@npm:^1.0.0": + version: 1.0.0 + resolution: "constants-browserify@npm:1.0.0" + checksum: 10c0/ab49b1d59a433ed77c964d90d19e08b2f77213fb823da4729c0baead55e3c597f8f97ebccfdfc47bd896d43854a117d114c849a6f659d9986420e97da0f83ac5 + languageName: node + linkType: hard + +"content-disposition@npm:0.5.4": + version: 0.5.4 + resolution: "content-disposition@npm:0.5.4" + dependencies: + safe-buffer: "npm:5.2.1" + checksum: 10c0/bac0316ebfeacb8f381b38285dc691c9939bf0a78b0b7c2d5758acadad242d04783cee5337ba7d12a565a19075af1b3c11c728e1e4946de73c6ff7ce45f3f1bb + languageName: node + linkType: hard + +"content-type@npm:~1.0.4, content-type@npm:~1.0.5": + version: 1.0.5 + resolution: "content-type@npm:1.0.5" + checksum: 10c0/b76ebed15c000aee4678c3707e0860cb6abd4e680a598c0a26e17f0bfae723ec9cc2802f0ff1bc6e4d80603719010431d2231018373d4dde10f9ccff9dadf5af + languageName: node + linkType: hard + +"convert-source-map@npm:^1.7.0": + version: 1.9.0 + resolution: "convert-source-map@npm:1.9.0" + checksum: 10c0/281da55454bf8126cbc6625385928c43479f2060984180c42f3a86c8b8c12720a24eac260624a7d1e090004028d2dee78602330578ceec1a08e27cb8bb0a8a5b + languageName: node + linkType: hard + +"convert-source-map@npm:^2.0.0": + version: 2.0.0 + resolution: "convert-source-map@npm:2.0.0" + checksum: 10c0/8f2f7a27a1a011cc6cc88cc4da2d7d0cfa5ee0369508baae3d98c260bb3ac520691464e5bbe4ae7cdf09860c1d69ecc6f70c63c6e7c7f7e3f18ec08484dc7d9b + languageName: node + linkType: hard + +"cookie-signature@npm:1.0.6": + version: 1.0.6 + resolution: "cookie-signature@npm:1.0.6" + checksum: 10c0/b36fd0d4e3fef8456915fcf7742e58fbfcc12a17a018e0eb9501c9d5ef6893b596466f03b0564b81af29ff2538fd0aa4b9d54fe5ccbfb4c90ea50ad29fe2d221 + languageName: node + linkType: hard + +"cookie@npm:0.7.1": + version: 0.7.1 + resolution: "cookie@npm:0.7.1" + checksum: 10c0/5de60c67a410e7c8dc8a46a4b72eb0fe925871d057c9a5d2c0e8145c4270a4f81076de83410c4d397179744b478e33cd80ccbcc457abf40a9409ad27dcd21dde + languageName: node + linkType: hard + +"copy-anything@npm:^2.0.1": + version: 2.0.6 + resolution: "copy-anything@npm:2.0.6" + dependencies: + is-what: "npm:^3.14.1" + checksum: 10c0/2702998a8cc015f9917385b7f16b0d85f1f6e5e2fd34d99f14df584838f492f49aa0c390d973684c687e895c5c58d08b308a0400ac3e1e3d6fa1e5884a5402ad + languageName: node + linkType: hard + +"copy-anything@npm:^3.0.2": + version: 3.0.5 + resolution: "copy-anything@npm:3.0.5" + dependencies: + is-what: "npm:^4.1.8" + checksum: 10c0/01eadd500c7e1db71d32d95a3bfaaedcb839ef891c741f6305ab0461398056133de08f2d1bf4c392b364e7bdb7ce498513896e137a7a183ac2516b065c28a4fe + languageName: node + linkType: hard + +"copy-to-clipboard@npm:^3.3.3": + version: 3.3.3 + resolution: "copy-to-clipboard@npm:3.3.3" + dependencies: + toggle-selection: "npm:^1.0.6" + checksum: 10c0/3ebf5e8ee00601f8c440b83ec08d838e8eabb068c1fae94a9cda6b42f288f7e1b552f3463635f419af44bf7675afc8d0390d30876cf5c2d5d35f86d9c56a3e5f + languageName: node + linkType: hard + +"core-js-pure@npm:^3.23.3": + version: 3.47.0 + resolution: "core-js-pure@npm:3.47.0" + checksum: 10c0/7eb5f897e532b33e6ea85ec2c60073fc2fe943e4543ec9903340450fc0f3b46b5b118d57d332e9f2c3d681a8b7b219a4cc64ccf548d933f6b79f754b682696dd + languageName: node + linkType: hard + +"core-js@npm:3.34.0": + version: 3.34.0 + resolution: "core-js@npm:3.34.0" + checksum: 10c0/408a77898abe03bf3e5dec2a451c36f4745081cca9022f8bdf9b817d57bb6d3a534d555f47a4b95e1daa5e21dbc79122eac2402e25720d425f5925127e55dcd8 + languageName: node + linkType: hard + +"core-util-is@npm:1.0.2": + version: 1.0.2 + resolution: "core-util-is@npm:1.0.2" + checksum: 10c0/980a37a93956d0de8a828ce508f9b9e3317039d68922ca79995421944146700e4aaf490a6dbfebcb1c5292a7184600c7710b957d724be1e37b8254c6bc0fe246 + languageName: node + linkType: hard + +"core-util-is@npm:~1.0.0": + version: 1.0.3 + resolution: "core-util-is@npm:1.0.3" + checksum: 10c0/90a0e40abbddfd7618f8ccd63a74d88deea94e77d0e8dbbea059fa7ebebb8fbb4e2909667fe26f3a467073de1a542ebe6ae4c73a73745ac5833786759cd906c9 + languageName: node + linkType: hard + +"cors@npm:^2.8.5": + version: 2.8.5 + resolution: "cors@npm:2.8.5" + dependencies: + object-assign: "npm:^4" + vary: "npm:^1" + checksum: 10c0/373702b7999409922da80de4a61938aabba6929aea5b6fd9096fefb9e8342f626c0ebd7507b0e8b0b311380744cc985f27edebc0a26e0ddb784b54e1085de761 + languageName: node + linkType: hard + +"cosmiconfig@npm:^7.0.1": + version: 7.1.0 + resolution: "cosmiconfig@npm:7.1.0" + dependencies: + "@types/parse-json": "npm:^4.0.0" + import-fresh: "npm:^3.2.1" + parse-json: "npm:^5.0.0" + path-type: "npm:^4.0.0" + yaml: "npm:^1.10.0" + checksum: 10c0/b923ff6af581638128e5f074a5450ba12c0300b71302398ea38dbeabd33bbcaa0245ca9adbedfcf284a07da50f99ede5658c80bb3e39e2ce770a99d28a21ef03 + languageName: node + linkType: hard + +"cosmiconfig@npm:^9.0.0": + version: 9.0.0 + resolution: "cosmiconfig@npm:9.0.0" + dependencies: + env-paths: "npm:^2.2.1" + import-fresh: "npm:^3.3.0" + js-yaml: "npm:^4.1.0" + parse-json: "npm:^5.2.0" + peerDependencies: + typescript: ">=4.9.5" + peerDependenciesMeta: + typescript: + optional: true + checksum: 10c0/1c1703be4f02a250b1d6ca3267e408ce16abfe8364193891afc94c2d5c060b69611fdc8d97af74b7e6d5d1aac0ab2fb94d6b079573146bc2d756c2484ce5f0ee + languageName: node + linkType: hard + +"cosmiconfig@npm:^9.0.1": + version: 9.0.1 + resolution: "cosmiconfig@npm:9.0.1" + dependencies: + env-paths: "npm:^2.2.1" + import-fresh: "npm:^3.3.0" + js-yaml: "npm:^4.1.0" + parse-json: "npm:^5.2.0" + peerDependencies: + typescript: ">=4.9.5" + peerDependenciesMeta: + typescript: + optional: true + checksum: 10c0/a5d4d95599687532ee072bca60170133c24d4e08cd795529e0f22c6ce5fde9409eaf4f26e36e3d671f43270ef858fc68f3c7b0ec28e58fac7ddebda5b7725306 + languageName: node + linkType: hard + +"crc@npm:^3.8.0": + version: 3.8.0 + resolution: "crc@npm:3.8.0" + dependencies: + buffer: "npm:^5.1.0" + checksum: 10c0/1a0da36e5f95b19cd2a7b2eab5306a08f1c47bdd22da6f761ab764e2222e8e90a877398907cea94108bd5e41a6d311ea84d7914eaca67da2baa4050bd6384b3d + languageName: node + linkType: hard + +"create-ecdh@npm:^4.0.4": + version: 4.0.4 + resolution: "create-ecdh@npm:4.0.4" + dependencies: + bn.js: "npm:^4.1.0" + elliptic: "npm:^6.5.3" + checksum: 10c0/77b11a51360fec9c3bce7a76288fc0deba4b9c838d5fb354b3e40c59194d23d66efe6355fd4b81df7580da0661e1334a235a2a5c040b7569ba97db428d466e7f + languageName: node + linkType: hard + +"create-hash@npm:^1.1.0, create-hash@npm:^1.2.0": + version: 1.2.0 + resolution: "create-hash@npm:1.2.0" + dependencies: + cipher-base: "npm:^1.0.1" + inherits: "npm:^2.0.1" + md5.js: "npm:^1.3.4" + ripemd160: "npm:^2.0.1" + sha.js: "npm:^2.4.0" + checksum: 10c0/d402e60e65e70e5083cb57af96d89567954d0669e90550d7cec58b56d49c4b193d35c43cec8338bc72358198b8cbf2f0cac14775b651e99238e1cf411490f915 + languageName: node + linkType: hard + +"create-hmac@npm:^1.1.7": + version: 1.1.7 + resolution: "create-hmac@npm:1.1.7" + dependencies: + cipher-base: "npm:^1.0.3" + create-hash: "npm:^1.1.0" + inherits: "npm:^2.0.1" + ripemd160: "npm:^2.0.0" + safe-buffer: "npm:^5.0.1" + sha.js: "npm:^2.4.8" + checksum: 10c0/24332bab51011652a9a0a6d160eed1e8caa091b802335324ae056b0dcb5acbc9fcf173cf10d128eba8548c3ce98dfa4eadaa01bd02f44a34414baee26b651835 + languageName: node + linkType: hard + +"cross-env@npm:^7.0.3": + version: 7.0.3 + resolution: "cross-env@npm:7.0.3" + dependencies: + cross-spawn: "npm:^7.0.1" + bin: + cross-env: src/bin/cross-env.js + cross-env-shell: src/bin/cross-env-shell.js + checksum: 10c0/f3765c25746c69fcca369655c442c6c886e54ccf3ab8c16847d5ad0e91e2f337d36eedc6599c1227904bf2a228d721e690324446876115bc8e7b32a866735ecf + languageName: node + linkType: hard + +"cross-spawn@npm:^7.0.1, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.6": + version: 7.0.6 + resolution: "cross-spawn@npm:7.0.6" + dependencies: + path-key: "npm:^3.1.0" + shebang-command: "npm:^2.0.0" + which: "npm:^2.0.1" + checksum: 10c0/053ea8b2135caff68a9e81470e845613e374e7309a47731e81639de3eaeb90c3d01af0e0b44d2ab9d50b43467223b88567dfeb3262db942dc063b9976718ffc1 + languageName: node + linkType: hard + +"crypto-browserify@npm:^3.11.0": + version: 3.12.1 + resolution: "crypto-browserify@npm:3.12.1" + dependencies: + browserify-cipher: "npm:^1.0.1" + browserify-sign: "npm:^4.2.3" + create-ecdh: "npm:^4.0.4" + create-hash: "npm:^1.2.0" + create-hmac: "npm:^1.1.7" + diffie-hellman: "npm:^5.0.3" + hash-base: "npm:~3.0.4" + inherits: "npm:^2.0.4" + pbkdf2: "npm:^3.1.2" + public-encrypt: "npm:^4.0.3" + randombytes: "npm:^2.1.0" + randomfill: "npm:^1.0.4" + checksum: 10c0/184a2def7b16628e79841243232ab5497f18d8e158ac21b7ce90ab172427d0a892a561280adc08f9d4d517bce8db2a5b335dc21abb970f787f8e874bd7b9db7d + languageName: node + linkType: hard + +"css-blank-pseudo@npm:^3.0.3": + version: 3.0.3 + resolution: "css-blank-pseudo@npm:3.0.3" + dependencies: + postcss-selector-parser: "npm:^6.0.9" + peerDependencies: + postcss: ^8.4 + bin: + css-blank-pseudo: dist/cli.cjs + checksum: 10c0/889b0c4e47f5172cbc1a036ed31c1b25b13e6331bd85f91c910ce29ba4a1bad33d8d7bd0d48343bc5d9bf30750b4626fe55fe9fd1042e09eda72f4a72c1d779c + languageName: node + linkType: hard + +"css-color-keywords@npm:^1.0.0": + version: 1.0.0 + resolution: "css-color-keywords@npm:1.0.0" + checksum: 10c0/af205a86c68e0051846ed91eb3e30b4517e1904aac040013ff1d742019b3f9369ba5658ba40901dbbc121186fc4bf0e75a814321cc3e3182fbb2feb81c6d9cb7 + languageName: node + linkType: hard + +"css-functions-list@npm:^3.3.3": + version: 3.3.3 + resolution: "css-functions-list@npm:3.3.3" + checksum: 10c0/7b9e5dd94e0178b2edb0f3263de5ae7942e56ab0b73420d4adb8fea003367e1dbc94fe8ea300bf732d1423f7eafb523e695136f0a4e6ae4f0abec66848219ee6 + languageName: node + linkType: hard + +"css-has-pseudo@npm:^3.0.4": + version: 3.0.4 + resolution: "css-has-pseudo@npm:3.0.4" + dependencies: + postcss-selector-parser: "npm:^6.0.9" + peerDependencies: + postcss: ^8.4 + bin: + css-has-pseudo: dist/cli.cjs + checksum: 10c0/da950bd66a73b7e02b428c95eba98fe664583ea059200dc4ddac2dfa3e316b637c538b69a1a8ffe52c4f739818bf55a264d652f15b18b78a6332e73ae08f03ed + languageName: node + linkType: hard + +"css-loader@npm:6.7.1": + version: 6.7.1 + resolution: "css-loader@npm:6.7.1" + dependencies: + icss-utils: "npm:^5.1.0" + postcss: "npm:^8.4.7" + postcss-modules-extract-imports: "npm:^3.0.0" + postcss-modules-local-by-default: "npm:^4.0.0" + postcss-modules-scope: "npm:^3.0.0" + postcss-modules-values: "npm:^4.0.0" + postcss-value-parser: "npm:^4.2.0" + semver: "npm:^7.3.5" + peerDependencies: + webpack: ^5.0.0 + checksum: 10c0/c9e900e2a6012a988ab36cf87598fa1e74cd570ab25dbcc8a5d7f10a91a0f9549ff3656b9bbb2bf26b9f5a39f76b9b4b148513c4085c23b73c9c1d5cc2f7de12 + languageName: node + linkType: hard + +"css-prefers-color-scheme@npm:^6.0.3": + version: 6.0.3 + resolution: "css-prefers-color-scheme@npm:6.0.3" + peerDependencies: + postcss: ^8.4 + bin: + css-prefers-color-scheme: dist/cli.cjs + checksum: 10c0/b0f1efba0384f52506a5ab54179a2b56a4a2b693c81e2d533529c6eae7ddb9ca4b1be3a6bc9d2d44f7c4b3750bb4eda7ae9d7254fe91379b25e0cc3b301fbdd8 + languageName: node + linkType: hard + +"css-select@npm:^4.1.3": + version: 4.3.0 + resolution: "css-select@npm:4.3.0" + dependencies: + boolbase: "npm:^1.0.0" + css-what: "npm:^6.0.1" + domhandler: "npm:^4.3.1" + domutils: "npm:^2.8.0" + nth-check: "npm:^2.0.1" + checksum: 10c0/a489d8e5628e61063d5a8fe0fa1cc7ae2478cb334a388a354e91cf2908154be97eac9fa7ed4dffe87a3e06cf6fcaa6016553115335c4fd3377e13dac7bd5a8e1 + languageName: node + linkType: hard + +"css-to-react-native@npm:3.2.0, css-to-react-native@npm:^3.0.0, css-to-react-native@npm:^3.2.0": + version: 3.2.0 + resolution: "css-to-react-native@npm:3.2.0" + dependencies: + camelize: "npm:^1.0.0" + css-color-keywords: "npm:^1.0.0" + postcss-value-parser: "npm:^4.0.2" + checksum: 10c0/fde850a511d5d3d7c55a1e9b8ed26b69a8ad4868b3487e36ebfbfc0b96fc34bc977d9cd1d61a289d0c74d3f9a662d8cee297da53d4433bf2e27d6acdff8e1003 + languageName: node + linkType: hard + +"css-tree@npm:^1.1.2, css-tree@npm:^1.1.3": + version: 1.1.3 + resolution: "css-tree@npm:1.1.3" + dependencies: + mdn-data: "npm:2.0.14" + source-map: "npm:^0.6.1" + checksum: 10c0/499a507bfa39b8b2128f49736882c0dd636b0cd3370f2c69f4558ec86d269113286b7df469afc955de6a68b0dba00bc533e40022a73698081d600072d5d83c1c + languageName: node + linkType: hard + +"css-tree@npm:^3.2.1": + version: 3.2.1 + resolution: "css-tree@npm:3.2.1" + dependencies: + mdn-data: "npm:2.27.1" + source-map-js: "npm:^1.2.1" + checksum: 10c0/1f65e9ccaa56112a4706d6f003dd43d777f0dbcf848e66fd320f823192533581f8dd58daa906cb80622658332d50284d6be13b87a6ab4556cbbfe9ef535bbf7e + languageName: node + linkType: hard + +"css-what@npm:^6.0.1": + version: 6.2.2 + resolution: "css-what@npm:6.2.2" + checksum: 10c0/91e24c26fb977b4ccef30d7007d2668c1c10ac0154cc3f42f7304410e9594fb772aea4f30c832d2993b132ca8d99338050866476210316345ec2e7d47b248a56 + languageName: node + linkType: hard + +"cssdb@npm:^6.6.1": + version: 6.6.3 + resolution: "cssdb@npm:6.6.3" + checksum: 10c0/a8bd55c609f1c08c2d69c11e846d054f700557bbfcf6a4dc5676a7ff4d7f32c719aa3b6197533ba3af47168109d4de95619299655a0565cc3b439d1bfb770949 + languageName: node + linkType: hard + +"cssesc@npm:^3.0.0": + version: 3.0.0 + resolution: "cssesc@npm:3.0.0" + bin: + cssesc: bin/cssesc + checksum: 10c0/6bcfd898662671be15ae7827120472c5667afb3d7429f1f917737f3bf84c4176003228131b643ae74543f17a394446247df090c597bb9a728cce298606ed0aa7 + languageName: node + linkType: hard + +"csso@npm:^4.2.0": + version: 4.2.0 + resolution: "csso@npm:4.2.0" + dependencies: + css-tree: "npm:^1.1.2" + checksum: 10c0/f8c6b1300efaa0f8855a7905ae3794a29c6496e7f16a71dec31eb6ca7cfb1f058a4b03fd39b66c4deac6cb06bf6b4ba86da7b67d7320389cb9994d52b924b903 + languageName: node + linkType: hard + +"csstype@npm:3.1.3": + version: 3.1.3 + resolution: "csstype@npm:3.1.3" + checksum: 10c0/80c089d6f7e0c5b2bd83cf0539ab41474198579584fa10d86d0cafe0642202343cbc119e076a0b1aece191989477081415d66c9fefbf3c957fc2fc4b7009f248 + languageName: node + linkType: hard + +"csstype@npm:^3.1.2, csstype@npm:^3.1.3, csstype@npm:^3.2.2": + version: 3.2.3 + resolution: "csstype@npm:3.2.3" + checksum: 10c0/cd29c51e70fa822f1cecd8641a1445bed7063697469d35633b516e60fe8c1bde04b08f6c5b6022136bb669b64c63d4173af54864510fbb4ee23281801841a3ce + languageName: node + linkType: hard + +"current-script-polyfill@npm:1.0.0": + version: 1.0.0 + resolution: "current-script-polyfill@npm:1.0.0" + checksum: 10c0/a06daa43cbce46e0c9bf37411b2a56830053fa21892d3a18cdd5f32c0916b51b2cafcad4032585ea40ce5d36c0e27b5818f91310b968c95967a0cca787f5cebf + languageName: node + linkType: hard + +"d3-color@npm:1 - 3": + version: 3.1.0 + resolution: "d3-color@npm:3.1.0" + checksum: 10c0/a4e20e1115fa696fce041fbe13fbc80dc4c19150fa72027a7c128ade980bc0eeeba4bcf28c9e21f0bce0e0dbfe7ca5869ef67746541dcfda053e4802ad19783c + languageName: node + linkType: hard + +"d3-dispatch@npm:1 - 3": + version: 3.0.1 + resolution: "d3-dispatch@npm:3.0.1" + checksum: 10c0/6eca77008ce2dc33380e45d4410c67d150941df7ab45b91d116dbe6d0a3092c0f6ac184dd4602c796dc9e790222bad3ff7142025f5fd22694efe088d1d941753 + languageName: node + linkType: hard + +"d3-drag@npm:2 - 3, d3-drag@npm:^3.0.0": + version: 3.0.0 + resolution: "d3-drag@npm:3.0.0" + dependencies: + d3-dispatch: "npm:1 - 3" + d3-selection: "npm:3" + checksum: 10c0/d2556e8dc720741a443b595a30af403dd60642dfd938d44d6e9bfc4c71a962142f9a028c56b61f8b4790b65a34acad177d1263d66f103c3c527767b0926ef5aa + languageName: node + linkType: hard + +"d3-ease@npm:1 - 3": + version: 3.0.1 + resolution: "d3-ease@npm:3.0.1" + checksum: 10c0/fec8ef826c0cc35cda3092c6841e07672868b1839fcaf556e19266a3a37e6bc7977d8298c0fcb9885e7799bfdcef7db1baaba9cd4dcf4bc5e952cf78574a88b0 + languageName: node + linkType: hard + +"d3-interpolate@npm:1 - 3, d3-interpolate@npm:^3.0.1": + version: 3.0.1 + resolution: "d3-interpolate@npm:3.0.1" + dependencies: + d3-color: "npm:1 - 3" + checksum: 10c0/19f4b4daa8d733906671afff7767c19488f51a43d251f8b7f484d5d3cfc36c663f0a66c38fe91eee30f40327443d799be17169f55a293a3ba949e84e57a33e6a + languageName: node + linkType: hard + +"d3-selection@npm:2 - 3, d3-selection@npm:3, d3-selection@npm:^3.0.0": + version: 3.0.0 + resolution: "d3-selection@npm:3.0.0" + checksum: 10c0/e59096bbe8f0cb0daa1001d9bdd6dbc93a688019abc97d1d8b37f85cd3c286a6875b22adea0931b0c88410d025563e1643019161a883c516acf50c190a11b56b + languageName: node + linkType: hard + +"d3-timer@npm:1 - 3": + version: 3.0.1 + resolution: "d3-timer@npm:3.0.1" + checksum: 10c0/d4c63cb4bb5461d7038aac561b097cd1c5673969b27cbdd0e87fa48d9300a538b9e6f39b4a7f0e3592ef4f963d858c8a9f0e92754db73116770856f2fc04561a + languageName: node + linkType: hard + +"d3-transition@npm:2 - 3": + version: 3.0.1 + resolution: "d3-transition@npm:3.0.1" + dependencies: + d3-color: "npm:1 - 3" + d3-dispatch: "npm:1 - 3" + d3-ease: "npm:1 - 3" + d3-interpolate: "npm:1 - 3" + d3-timer: "npm:1 - 3" + peerDependencies: + d3-selection: 2 - 3 + checksum: 10c0/4e74535dda7024aa43e141635b7522bb70cf9d3dfefed975eb643b36b864762eca67f88fafc2ca798174f83ca7c8a65e892624f824b3f65b8145c6a1a88dbbad + languageName: node + linkType: hard + +"d3-zoom@npm:^3.0.0": + version: 3.0.0 + resolution: "d3-zoom@npm:3.0.0" + dependencies: + d3-dispatch: "npm:1 - 3" + d3-drag: "npm:2 - 3" + d3-interpolate: "npm:1 - 3" + d3-selection: "npm:2 - 3" + d3-transition: "npm:2 - 3" + checksum: 10c0/ee2036479049e70d8c783d594c444fe00e398246048e3f11a59755cd0e21de62ece3126181b0d7a31bf37bcf32fd726f83ae7dea4495ff86ec7736ce5ad36fd3 + languageName: node + linkType: hard + +"d@npm:1, d@npm:^1.0.1, d@npm:^1.0.2": + version: 1.0.2 + resolution: "d@npm:1.0.2" + dependencies: + es5-ext: "npm:^0.10.64" + type: "npm:^2.7.2" + checksum: 10c0/3e6ede10cd3b77586c47da48423b62bed161bf1a48bdbcc94d87263522e22f5dfb0e678a6dba5323fdc14c5d8612b7f7eb9e7d9e37b2e2d67a7bf9f116dabe5a + languageName: node + linkType: hard + +"data-uri-to-buffer@npm:^4.0.0": + version: 4.0.1 + resolution: "data-uri-to-buffer@npm:4.0.1" + checksum: 10c0/20a6b93107597530d71d4cb285acee17f66bcdfc03fd81040921a81252f19db27588d87fc8fc69e1950c55cfb0bf8ae40d0e5e21d907230813eb5d5a7f9eb45b + languageName: node + linkType: hard + +"data-view-buffer@npm:^1.0.2": + version: 1.0.2 + resolution: "data-view-buffer@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.3" + es-errors: "npm:^1.3.0" + is-data-view: "npm:^1.0.2" + checksum: 10c0/7986d40fc7979e9e6241f85db8d17060dd9a71bd53c894fa29d126061715e322a4cd47a00b0b8c710394854183d4120462b980b8554012acc1c0fa49df7ad38c + languageName: node + linkType: hard + +"data-view-byte-length@npm:^1.0.2": + version: 1.0.2 + resolution: "data-view-byte-length@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.3" + es-errors: "npm:^1.3.0" + is-data-view: "npm:^1.0.2" + checksum: 10c0/f8a4534b5c69384d95ac18137d381f18a5cfae1f0fc1df0ef6feef51ef0d568606d970b69e02ea186c6c0f0eac77fe4e6ad96fec2569cc86c3afcc7475068c55 + languageName: node + linkType: hard + +"data-view-byte-offset@npm:^1.0.1": + version: 1.0.1 + resolution: "data-view-byte-offset@npm:1.0.1" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + is-data-view: "npm:^1.0.1" + checksum: 10c0/fa7aa40078025b7810dcffc16df02c480573b7b53ef1205aa6a61533011005c1890e5ba17018c692ce7c900212b547262d33279fde801ad9843edc0863bf78c4 + languageName: node + linkType: hard + +"date-fns@npm:^2.30.0": + version: 2.30.0 + resolution: "date-fns@npm:2.30.0" + dependencies: + "@babel/runtime": "npm:^7.21.0" + checksum: 10c0/e4b521fbf22bc8c3db332bbfb7b094fd3e7627de0259a9d17c7551e2d2702608a7307a449206065916538e384f37b181565447ce2637ae09828427aed9cb5581 + languageName: node + linkType: hard + +"dayjs@npm:^1.11.10, dayjs@npm:^1.11.11, dayjs@npm:^1.11.7, dayjs@npm:^1.9.1": + version: 1.11.19 + resolution: "dayjs@npm:1.11.19" + checksum: 10c0/7d8a6074a343f821f81ea284d700bd34ea6c7abbe8d93bce7aba818948957c1b7f56131702e5e890a5622cdfc05dcebe8aed0b8313bdc6838a594d7846b0b000 + languageName: node + linkType: hard + +"debug@npm:2.6.9, debug@npm:^2.6.8": + version: 2.6.9 + resolution: "debug@npm:2.6.9" + dependencies: + ms: "npm:2.0.0" + checksum: 10c0/121908fb839f7801180b69a7e218a40b5a0b718813b886b7d6bdb82001b931c938e2941d1e4450f33a1b1df1da653f5f7a0440c197f29fbf8a6e9d45ff6ef589 + languageName: node + linkType: hard + +"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.0.1, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.4.3": + version: 4.4.3 + resolution: "debug@npm:4.4.3" + dependencies: + ms: "npm:^2.1.3" + peerDependenciesMeta: + supports-color: + optional: true + checksum: 10c0/d79136ec6c83ecbefd0f6a5593da6a9c91ec4d7ddc4b54c883d6e71ec9accb5f67a1a5e96d00a328196b5b5c86d365e98d8a3a70856aaf16b4e7b1985e67f5a6 + languageName: node + linkType: hard + +"debug@npm:^3.0.1, debug@npm:^3.2.7": + version: 3.2.7 + resolution: "debug@npm:3.2.7" + dependencies: + ms: "npm:^2.1.1" + checksum: 10c0/37d96ae42cbc71c14844d2ae3ba55adf462ec89fd3a999459dec3833944cd999af6007ff29c780f1c61153bcaaf2c842d1e4ce1ec621e4fc4923244942e4a02a + languageName: node + linkType: hard + +"decode-named-character-reference@npm:^1.0.0": + version: 1.2.0 + resolution: "decode-named-character-reference@npm:1.2.0" + dependencies: + character-entities: "npm:^2.0.0" + checksum: 10c0/761a89de6b0e0a2d4b21ae99074e4cc3344dd11eb29f112e23cc5909f2e9f33c5ed20cd6b146b27fb78170bce0f3f9b3362a84b75638676a05c938c24a60f5d7 + languageName: node + linkType: hard + +"decode-uri-component@npm:^0.2.0": + version: 0.2.2 + resolution: "decode-uri-component@npm:0.2.2" + checksum: 10c0/1f4fa54eb740414a816b3f6c24818fbfcabd74ac478391e9f4e2282c994127db02010ce804f3d08e38255493cfe68608b3f5c8e09fd6efc4ae46c807691f7a31 + languageName: node + linkType: hard + +"decompress-response@npm:^6.0.0": + version: 6.0.0 + resolution: "decompress-response@npm:6.0.0" + dependencies: + mimic-response: "npm:^3.1.0" + checksum: 10c0/bd89d23141b96d80577e70c54fb226b2f40e74a6817652b80a116d7befb8758261ad073a8895648a29cc0a5947021ab66705cb542fa9c143c82022b27c5b175e + languageName: node + linkType: hard + +"deep-is@npm:^0.1.3": + version: 0.1.4 + resolution: "deep-is@npm:0.1.4" + checksum: 10c0/7f0ee496e0dff14a573dc6127f14c95061b448b87b995fc96c017ce0a1e66af1675e73f1d6064407975bc4ea6ab679497a29fff7b5b9c4e99cb10797c1ad0b4c + languageName: node + linkType: hard + +"deepmerge@npm:^1.5.2": + version: 1.5.2 + resolution: "deepmerge@npm:1.5.2" + checksum: 10c0/5e676957f523c73a69633d236227513310fea934af02839bd6908cf569503f8988e76512fab6d9dde700e72642f22f331455d6b12e2826e4854a8e8233d0789d + languageName: node + linkType: hard + +"deepmerge@npm:^4.2.2": + version: 4.3.1 + resolution: "deepmerge@npm:4.3.1" + checksum: 10c0/e53481aaf1aa2c4082b5342be6b6d8ad9dfe387bc92ce197a66dea08bd4265904a087e75e464f14d1347cf2ac8afe1e4c16b266e0561cc5df29382d3c5f80044 + languageName: node + linkType: hard + +"default-browser-id@npm:^3.0.0": + version: 3.0.0 + resolution: "default-browser-id@npm:3.0.0" + dependencies: + bplist-parser: "npm:^0.2.0" + untildify: "npm:^4.0.0" + checksum: 10c0/8db3ab882eb3e1e8b59d84c8641320e6c66d8eeb17eb4bb848b7dd549b1e6fd313988e4a13542e95fbaeff03f6e9dedc5ad191ad4df7996187753eb0d45c00b7 + languageName: node + linkType: hard + +"default-browser@npm:^4.0.0": + version: 4.0.0 + resolution: "default-browser@npm:4.0.0" + dependencies: + bundle-name: "npm:^3.0.0" + default-browser-id: "npm:^3.0.0" + execa: "npm:^7.1.1" + titleize: "npm:^3.0.0" + checksum: 10c0/7c8848badc139ecf9d878e562bc4e7ab4301e51ba120b24d8dcb14739c30152115cc612065ac3ab73c02aace4afa29db5a044257b2f0cf234f16e3a58f6c925e + languageName: node + linkType: hard + +"defer-to-connect@npm:^2.0.0": + version: 2.0.1 + resolution: "defer-to-connect@npm:2.0.1" + checksum: 10c0/625ce28e1b5ad10cf77057b9a6a727bf84780c17660f6644dab61dd34c23de3001f03cedc401f7d30a4ed9965c2e8a7336e220a329146f2cf85d4eddea429782 + languageName: node + linkType: hard + +"define-data-property@npm:^1.0.1, define-data-property@npm:^1.1.4": + version: 1.1.4 + resolution: "define-data-property@npm:1.1.4" + dependencies: + es-define-property: "npm:^1.0.0" + es-errors: "npm:^1.3.0" + gopd: "npm:^1.0.1" + checksum: 10c0/dea0606d1483eb9db8d930d4eac62ca0fa16738b0b3e07046cddfacf7d8c868bbe13fa0cb263eb91c7d0d527960dc3f2f2471a69ed7816210307f6744fe62e37 + languageName: node + linkType: hard + +"define-lazy-prop@npm:^2.0.0": + version: 2.0.0 + resolution: "define-lazy-prop@npm:2.0.0" + checksum: 10c0/db6c63864a9d3b7dc9def55d52764968a5af296de87c1b2cc71d8be8142e445208071953649e0386a8cc37cfcf9a2067a47207f1eb9ff250c2a269658fdae422 + languageName: node + linkType: hard + +"define-lazy-prop@npm:^3.0.0": + version: 3.0.0 + resolution: "define-lazy-prop@npm:3.0.0" + checksum: 10c0/5ab0b2bf3fa58b3a443140bbd4cd3db1f91b985cc8a246d330b9ac3fc0b6a325a6d82bddc0b055123d745b3f9931afeea74a5ec545439a1630b9c8512b0eeb49 + languageName: node + linkType: hard + +"define-properties@npm:^1.1.3, define-properties@npm:^1.2.1": + version: 1.2.1 + resolution: "define-properties@npm:1.2.1" + dependencies: + define-data-property: "npm:^1.0.1" + has-property-descriptors: "npm:^1.0.0" + object-keys: "npm:^1.1.1" + checksum: 10c0/88a152319ffe1396ccc6ded510a3896e77efac7a1bfbaa174a7b00414a1747377e0bb525d303794a47cf30e805c2ec84e575758512c6e44a993076d29fd4e6c3 + languageName: node + linkType: hard + +"delayed-stream@npm:~1.0.0": + version: 1.0.0 + resolution: "delayed-stream@npm:1.0.0" + checksum: 10c0/d758899da03392e6712f042bec80aa293bbe9e9ff1b2634baae6a360113e708b91326594c8a486d475c69d6259afb7efacdc3537bfcda1c6c648e390ce601b19 + languageName: node + linkType: hard + +"depd@npm:2.0.0, depd@npm:~2.0.0": + version: 2.0.0 + resolution: "depd@npm:2.0.0" + checksum: 10c0/58bd06ec20e19529b06f7ad07ddab60e504d9e0faca4bd23079fac2d279c3594334d736508dc350e06e510aba5e22e4594483b3a6562ce7c17dd797f4cc4ad2c + languageName: node + linkType: hard + +"depd@npm:~1.1.2": + version: 1.1.2 + resolution: "depd@npm:1.1.2" + checksum: 10c0/acb24aaf936ef9a227b6be6d495f0d2eb20108a9a6ad40585c5bda1a897031512fef6484e4fdbb80bd249fdaa82841fa1039f416ece03188e677ba11bcfda249 + languageName: node + linkType: hard + +"dequal@npm:^2.0.0, dequal@npm:^2.0.3": + version: 2.0.3 + resolution: "dequal@npm:2.0.3" + checksum: 10c0/f98860cdf58b64991ae10205137c0e97d384c3a4edc7f807603887b7c4b850af1224a33d88012009f150861cbee4fa2d322c4cc04b9313bee312e47f6ecaa888 + languageName: node + linkType: hard + +"des.js@npm:^1.0.0": + version: 1.1.0 + resolution: "des.js@npm:1.1.0" + dependencies: + inherits: "npm:^2.0.1" + minimalistic-assert: "npm:^1.0.0" + checksum: 10c0/671354943ad67493e49eb4c555480ab153edd7cee3a51c658082fcde539d2690ed2a4a0b5d1f401f9cde822edf3939a6afb2585f32c091f2d3a1b1665cd45236 + languageName: node + linkType: hard + +"destroy@npm:1.2.0": + version: 1.2.0 + resolution: "destroy@npm:1.2.0" + checksum: 10c0/bd7633942f57418f5a3b80d5cb53898127bcf53e24cdf5d5f4396be471417671f0fee48a4ebe9a1e9defbde2a31280011af58a57e090ff822f589b443ed4e643 + languageName: node + linkType: hard + +"destroy@npm:~1.0.4": + version: 1.0.4 + resolution: "destroy@npm:1.0.4" + checksum: 10c0/eab493808ba17a1fa22c71ef1a4e68d2c4c5222a38040606c966d2ab09117f3a7f3e05c39bffbe41a697f9de552039e43c30e46f0c3eab3faa9f82e800e172a0 + languageName: node + linkType: hard + +"detect-indent@npm:^7.0.1": + version: 7.0.2 + resolution: "detect-indent@npm:7.0.2" + checksum: 10c0/adb1334ca3fe516dc6817aff0a777540b88643ab92fe13a72d0f5d12721ca796ffdd0e5fedb7b45e6e82657156c6ad44f5d5758157f0439532ae7d07b595146b + languageName: node + linkType: hard + +"detect-libc@npm:^1.0.3": + version: 1.0.3 + resolution: "detect-libc@npm:1.0.3" + bin: + detect-libc: ./bin/detect-libc.js + checksum: 10c0/4da0deae9f69e13bc37a0902d78bf7169480004b1fed3c19722d56cff578d16f0e11633b7fbf5fb6249181236c72e90024cbd68f0b9558ae06e281f47326d50d + languageName: node + linkType: hard + +"detect-newline@npm:^4.0.0, detect-newline@npm:^4.0.1": + version: 4.0.1 + resolution: "detect-newline@npm:4.0.1" + checksum: 10c0/1cc1082e88ad477f30703ae9f23bd3e33816ea2db6a35333057e087d72d466f5a777809b71f560118ecff935d2c712f5b59e1008a8b56a900909d8fd4621c603 + languageName: node + linkType: hard + +"detect-node@npm:^2.0.4": + version: 2.1.0 + resolution: "detect-node@npm:2.1.0" + checksum: 10c0/f039f601790f2e9d4654e499913259a798b1f5246ae24f86ab5e8bd4aaf3bce50484234c494f11fb00aecb0c6e2733aa7b1cf3f530865640b65fbbd65b2c4e09 + languageName: node + linkType: hard + +"didyoumean@npm:^1.2.2": + version: 1.2.2 + resolution: "didyoumean@npm:1.2.2" + checksum: 10c0/95d0b53d23b851aacff56dfadb7ecfedce49da4232233baecfeecb7710248c4aa03f0aa8995062f0acafaf925adf8536bd7044a2e68316fd7d411477599bc27b + languageName: node + linkType: hard + +"diff@npm:^5.0.0": + version: 5.2.0 + resolution: "diff@npm:5.2.0" + checksum: 10c0/aed0941f206fe261ecb258dc8d0ceea8abbde3ace5827518ff8d302f0fc9cc81ce116c4d8f379151171336caf0516b79e01abdc1ed1201b6440d895a66689eb4 + languageName: node + linkType: hard + +"diffie-hellman@npm:^5.0.3": + version: 5.0.3 + resolution: "diffie-hellman@npm:5.0.3" + dependencies: + bn.js: "npm:^4.1.0" + miller-rabin: "npm:^4.0.0" + randombytes: "npm:^2.0.0" + checksum: 10c0/ce53ccafa9ca544b7fc29b08a626e23a9b6562efc2a98559a0c97b4718937cebaa9b5d7d0a05032cc9c1435e9b3c1532b9e9bf2e0ede868525922807ad6e1ecf + languageName: node + linkType: hard + +"dir-compare@npm:^2.4.0": + version: 2.4.0 + resolution: "dir-compare@npm:2.4.0" + dependencies: + buffer-equal: "npm:1.0.0" + colors: "npm:1.0.3" + commander: "npm:2.9.0" + minimatch: "npm:3.0.4" + bin: + dircompare: src/cli/dircompare.js + checksum: 10c0/f1bf30faeeb2829f5d209ed72d94b3c4973c02765d2defc206e84963b3ce31fd0f5b0e86e9cf075dff917402846e7bb6efffd7836ea35c0ee46adcaad8ca345f + languageName: node + linkType: hard + +"dir-glob@npm:^3.0.1": + version: 3.0.1 + resolution: "dir-glob@npm:3.0.1" + dependencies: + path-type: "npm:^4.0.0" + checksum: 10c0/dcac00920a4d503e38bb64001acb19df4efc14536ada475725e12f52c16777afdee4db827f55f13a908ee7efc0cb282e2e3dbaeeb98c0993dd93d1802d3bf00c + languageName: node + linkType: hard + +"discontinuous-range@npm:1.0.0": + version: 1.0.0 + resolution: "discontinuous-range@npm:1.0.0" + checksum: 10c0/487b105f83c1cc528e25e65d3c4b73958ec79769b7bd0e264414702a23a7e2b282c72982b4bef4af29fcab53f47816c3f0a5c40d85a99a490f4bc35b83dc00f8 + languageName: node + linkType: hard + +"dlv@npm:^1.1.3": + version: 1.1.3 + resolution: "dlv@npm:1.1.3" + checksum: 10c0/03eb4e769f19a027fd5b43b59e8a05e3fd2100ac239ebb0bf9a745de35d449e2f25cfaf3aa3934664551d72856f4ae8b7822016ce5c42c2d27c18ae79429ec42 + languageName: node + linkType: hard + +"dmg-builder@npm:23.6.0": + version: 23.6.0 + resolution: "dmg-builder@npm:23.6.0" + dependencies: + app-builder-lib: "npm:23.6.0" + builder-util: "npm:23.6.0" + builder-util-runtime: "npm:9.1.1" + dmg-license: "npm:^1.0.11" + fs-extra: "npm:^10.0.0" + iconv-lite: "npm:^0.6.2" + js-yaml: "npm:^4.1.0" + dependenciesMeta: + dmg-license: + optional: true + checksum: 10c0/3710e00e0b92305f3f744c8cf8dfcf54439785d724831b28e9e6944f9c63b510059d341212e059e0934bdc5243b30a6801153b490a9ad2bc1b9a1f07f690fa02 + languageName: node + linkType: hard + +"dmg-license@npm:^1.0.11": + version: 1.0.11 + resolution: "dmg-license@npm:1.0.11" + dependencies: + "@types/plist": "npm:^3.0.1" + "@types/verror": "npm:^1.10.3" + ajv: "npm:^6.10.0" + crc: "npm:^3.8.0" + iconv-corefoundation: "npm:^1.1.7" + plist: "npm:^3.0.4" + smart-buffer: "npm:^4.0.2" + verror: "npm:^1.10.0" + bin: + dmg-license: bin/dmg-license.js + conditions: os=darwin + languageName: node + linkType: hard + +"doctrine@npm:^2.1.0": + version: 2.1.0 + resolution: "doctrine@npm:2.1.0" + dependencies: + esutils: "npm:^2.0.2" + checksum: 10c0/b6416aaff1f380bf56c3b552f31fdf7a69b45689368deca72d28636f41c16bb28ec3ebc40ace97db4c1afc0ceeb8120e8492fe0046841c94c2933b2e30a7d5ac + languageName: node + linkType: hard + +"doctrine@npm:^3.0.0": + version: 3.0.0 + resolution: "doctrine@npm:3.0.0" + dependencies: + esutils: "npm:^2.0.2" + checksum: 10c0/c96bdccabe9d62ab6fea9399fdff04a66e6563c1d6fb3a3a063e8d53c3bb136ba63e84250bbf63d00086a769ad53aef92d2bd483f03f837fc97b71cbee6b2520 + languageName: node + linkType: hard + +"dom-converter@npm:^0.2.0": + version: 0.2.0 + resolution: "dom-converter@npm:0.2.0" + dependencies: + utila: "npm:~0.4" + checksum: 10c0/e96aa63bd8c6ee3cd9ce19c3aecfc2c42e50a460e8087114794d4f5ecf3a4f052b34ea3bf2d73b5d80b4da619073b49905e6d7d788ceb7814ca4c29be5354a11 + languageName: node + linkType: hard + +"dom-serializer@npm:^1.0.1": + version: 1.4.1 + resolution: "dom-serializer@npm:1.4.1" + dependencies: + domelementtype: "npm:^2.0.1" + domhandler: "npm:^4.2.0" + entities: "npm:^2.0.0" + checksum: 10c0/67d775fa1ea3de52035c98168ddcd59418356943b5eccb80e3c8b3da53adb8e37edb2cc2f885802b7b1765bf5022aec21dfc32910d7f9e6de4c3148f095ab5e0 + languageName: node + linkType: hard + +"dom-walk@npm:^0.1.0": + version: 0.1.2 + resolution: "dom-walk@npm:0.1.2" + checksum: 10c0/4d2ad9062a9423d890f8577aa202b597a6b85f9489bdde656b9443901b8b322b289655c3affefc58ec2e41931e0828dfee0a1d2db6829a607d76def5901fc5a9 + languageName: node + linkType: hard + +"domain-browser@npm:^1.1.1": + version: 1.2.0 + resolution: "domain-browser@npm:1.2.0" + checksum: 10c0/a955f482f4b4710fbd77c12a33e77548d63603c30c80f61a80519f27e3db1ba8530b914584cc9e9365d2038753d6b5bd1f4e6c81e432b007b0ec95b8b5e69b1b + languageName: node + linkType: hard + +"domelementtype@npm:^2.0.1, domelementtype@npm:^2.2.0": + version: 2.3.0 + resolution: "domelementtype@npm:2.3.0" + checksum: 10c0/686f5a9ef0fff078c1412c05db73a0dce096190036f33e400a07e2a4518e9f56b1e324f5c576a0a747ef0e75b5d985c040b0d51945ce780c0dd3c625a18cd8c9 + languageName: node + linkType: hard + +"domhandler@npm:^4.0.0, domhandler@npm:^4.2.0, domhandler@npm:^4.3.1": + version: 4.3.1 + resolution: "domhandler@npm:4.3.1" + dependencies: + domelementtype: "npm:^2.2.0" + checksum: 10c0/5c199c7468cb052a8b5ab80b13528f0db3d794c64fc050ba793b574e158e67c93f8336e87fd81e9d5ee43b0e04aea4d8b93ed7be4899cb726a1601b3ba18538b + languageName: node + linkType: hard + +"domutils@npm:^2.5.2, domutils@npm:^2.8.0": + version: 2.8.0 + resolution: "domutils@npm:2.8.0" + dependencies: + dom-serializer: "npm:^1.0.1" + domelementtype: "npm:^2.2.0" + domhandler: "npm:^4.2.0" + checksum: 10c0/d58e2ae01922f0dd55894e61d18119924d88091837887bf1438f2327f32c65eb76426bd9384f81e7d6dcfb048e0f83c19b222ad7101176ad68cdc9c695b563db + languageName: node + linkType: hard + +"dot-case@npm:^3.0.4": + version: 3.0.4 + resolution: "dot-case@npm:3.0.4" + dependencies: + no-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + checksum: 10c0/5b859ea65097a7ea870e2c91b5768b72ddf7fa947223fd29e167bcdff58fe731d941c48e47a38ec8aa8e43044c8fbd15cd8fa21689a526bc34b6548197cd5b05 + languageName: node + linkType: hard + +"dotenv-expand@npm:^5.1.0": + version: 5.1.0 + resolution: "dotenv-expand@npm:5.1.0" + checksum: 10c0/24ac633de853ef474d0421cc639328b7134109c8dc2baaa5e3afb7495af5e9237136d7e6971e55668e4dce915487eb140967cdd2b3e99aa439e0f6bf8b56faeb + languageName: node + linkType: hard + +"dotenv@npm:^9.0.2": + version: 9.0.2 + resolution: "dotenv@npm:9.0.2" + checksum: 10c0/535f04d59e0bf58fe0c7966886eff42fb5e0227e2f7bfa38d37439bbf6b3c25d1b085bd235c9b98e7e9a032b1cd310904366e5588b320c29335d359660fab0d4 + languageName: node + linkType: hard + +"dunder-proto@npm:^1.0.0, dunder-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "dunder-proto@npm:1.0.1" + dependencies: + call-bind-apply-helpers: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + gopd: "npm:^1.2.0" + checksum: 10c0/199f2a0c1c16593ca0a145dbf76a962f8033ce3129f01284d48c45ed4e14fea9bbacd7b3610b6cdc33486cef20385ac054948fefc6272fcce645c09468f93031 + languageName: node + linkType: hard + +"duplexify@npm:^4.1.2": + version: 4.1.3 + resolution: "duplexify@npm:4.1.3" + dependencies: + end-of-stream: "npm:^1.4.1" + inherits: "npm:^2.0.3" + readable-stream: "npm:^3.1.1" + stream-shift: "npm:^1.0.2" + checksum: 10c0/8a7621ae95c89f3937f982fe36d72ea997836a708471a75bb2a0eecde3330311b1e128a6dad510e0fd64ace0c56bff3484ed2e82af0e465600c82117eadfbda5 + languageName: node + linkType: hard + +"dva-core@npm:^2.0.4": + version: 2.0.4 + resolution: "dva-core@npm:2.0.4" + dependencies: + "@babel/runtime": "npm:^7.0.0" + flatten: "npm:^1.0.2" + global: "npm:^4.3.2" + invariant: "npm:^2.2.1" + is-plain-object: "npm:^2.0.3" + redux-saga: "npm:^0.16.0" + warning: "npm:^3.0.0" + peerDependencies: + redux: 4.x + checksum: 10c0/17290ddd31575c429089bb5dd725c4cb50628afe673ce679c4ce2fb8c723edde2ff9eb1730c1c0098a757586e638b2fd15117b56442439ac508ec3ef1b0b5906 + languageName: node + linkType: hard + +"dva-immer@npm:^1.0.0": + version: 1.0.2 + resolution: "dva-immer@npm:1.0.2" + dependencies: + "@babel/runtime": "npm:^7.0.0" + immer: "npm:^8.0.4" + peerDependencies: + dva: ^2.5.0-0 + checksum: 10c0/ab428476a92b97f044003e1e0578bf68daad808046eae7d0bb4658b5543813d9b243e915d6d415b60119c295eb644150f1d79141d2d53cfb3038d73c22468d59 + languageName: node + linkType: hard + +"dva-loading@npm:^3.0.22": + version: 3.0.25 + resolution: "dva-loading@npm:3.0.25" + dependencies: + "@babel/runtime": "npm:^7.0.0" + peerDependencies: + dva-core: ^1.1.0 || ^1.5.0-0 || ^1.6.0-0 + checksum: 10c0/2cf26c3ac9e61e5be60bb3d0a67f4827f636529c516429fe18adbc14e9cf3d6eff7dade6ea07757d5066620dae8eb2e94fdf6d8274dcaed0fa60bb75ec211e29 + languageName: node + linkType: hard + +"eastasianwidth@npm:^0.2.0": + version: 0.2.0 + resolution: "eastasianwidth@npm:0.2.0" + checksum: 10c0/26f364ebcdb6395f95124fda411f63137a4bfb5d3a06453f7f23dfe52502905bd84e0488172e0f9ec295fdc45f05c23d5d91baf16bd26f0fe9acd777a188dc39 + languageName: node + linkType: hard + +"echarts-for-react@npm:^3.0.2": + version: 3.0.5 + resolution: "echarts-for-react@npm:3.0.5" + dependencies: + fast-deep-equal: "npm:^3.1.3" + size-sensor: "npm:^1.0.1" + peerDependencies: + echarts: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 + react: ^15.0.0 || >=16.0.0 + checksum: 10c0/ca4a70fbcee0dbfde7410278167b82f8900163aa40ebc0a504c002c7555ce6cc3ffbfdc6b938cd66088c54b097ff2a37e9b4776f0e62fb08bb6136d3ccd12826 + languageName: node + linkType: hard + +"echarts@npm:^5.4.2": + version: 5.6.0 + resolution: "echarts@npm:5.6.0" + dependencies: + tslib: "npm:2.3.0" + zrender: "npm:5.6.1" + checksum: 10c0/6d6a2ee88534d1ff0433e935c542237b9896de1c94959f47ebc7e0e9da26f59bf11c91ed6fc135b62ad2786c779ee12bc536fa481e60532dad5b6a2f5167e9ea + languageName: node + linkType: hard + +"ee-first@npm:1.1.1": + version: 1.1.1 + resolution: "ee-first@npm:1.1.1" + checksum: 10c0/b5bb125ee93161bc16bfe6e56c6b04de5ad2aa44234d8f644813cc95d861a6910903132b05093706de2b706599367c4130eb6d170f6b46895686b95f87d017b7 + languageName: node + linkType: hard + +"ejs@npm:^3.1.7": + version: 3.1.10 + resolution: "ejs@npm:3.1.10" + dependencies: + jake: "npm:^10.8.5" + bin: + ejs: bin/cli.js + checksum: 10c0/52eade9e68416ed04f7f92c492183340582a36482836b11eab97b159fcdcfdedc62233a1bf0bf5e5e1851c501f2dca0e2e9afd111db2599e4e7f53ee29429ae1 + languageName: node + linkType: hard + +"electron-builder@npm:^23.6.0": + version: 23.6.0 + resolution: "electron-builder@npm:23.6.0" + dependencies: + "@types/yargs": "npm:^17.0.1" + app-builder-lib: "npm:23.6.0" + builder-util: "npm:23.6.0" + builder-util-runtime: "npm:9.1.1" + chalk: "npm:^4.1.1" + dmg-builder: "npm:23.6.0" + fs-extra: "npm:^10.0.0" + is-ci: "npm:^3.0.0" + lazy-val: "npm:^1.0.5" + read-config-file: "npm:6.2.0" + simple-update-notifier: "npm:^1.0.7" + yargs: "npm:^17.5.1" + bin: + electron-builder: cli.js + install-app-deps: install-app-deps.js + checksum: 10c0/85e6472c05ee1e5977f5198a2cc4e744f0aed7a5eb1511adfa8bbf36aa241fe21471d90613b212020cb1288751672568a41854363456d2b9ffa4b4ba28db918b + languageName: node + linkType: hard + +"electron-debug@npm:^3.2.0": + version: 3.2.0 + resolution: "electron-debug@npm:3.2.0" + dependencies: + electron-is-dev: "npm:^1.1.0" + electron-localshortcut: "npm:^3.1.0" + checksum: 10c0/ee3e19c328f4a569b36c336b56a77833472b2e9942d08b1f2e567c3b81614e51bcdc0cd32ea1bdba0f75d2ee2e85024874a1ec6d3f123a7629de7817f79e8c95 + languageName: node + linkType: hard + +"electron-is-accelerator@npm:^0.1.0": + version: 0.1.2 + resolution: "electron-is-accelerator@npm:0.1.2" + checksum: 10c0/120da55c3b581cbca5eccdd80c9099574a7aa0a8ea8b9fd4e5dcd906dcf83308c94587ad771b066cdd073e45e68dbe4c06256602998f16ccafbcfe1cab968718 + languageName: node + linkType: hard + +"electron-is-dev@npm:^1.1.0": + version: 1.2.0 + resolution: "electron-is-dev@npm:1.2.0" + checksum: 10c0/56a4c8e9b9eb7d43da86ee63093b80b32e3fb92cc5e4a60a6b7cacafcdf456ec2c6102d90fa5919fb8b03c61848d4bfa2db820349c5abd9f19e73f7afd2dc22d + languageName: node + linkType: hard + +"electron-localshortcut@npm:^3.1.0": + version: 3.2.1 + resolution: "electron-localshortcut@npm:3.2.1" + dependencies: + debug: "npm:^4.0.1" + electron-is-accelerator: "npm:^0.1.0" + keyboardevent-from-electron-accelerator: "npm:^2.0.0" + keyboardevents-areequal: "npm:^0.2.1" + checksum: 10c0/6490a1dd0155926d5664500d45a5acb4771ba96e3ad4a878367e24ab96096c8824197cf3e2890a4f51b0307665afe680fe5f22dae2e4ba781cf32ddab9dcc335 + languageName: node + linkType: hard + +"electron-osx-sign@npm:^0.6.0": + version: 0.6.0 + resolution: "electron-osx-sign@npm:0.6.0" + dependencies: + bluebird: "npm:^3.5.0" + compare-version: "npm:^0.1.2" + debug: "npm:^2.6.8" + isbinaryfile: "npm:^3.0.2" + minimist: "npm:^1.2.0" + plist: "npm:^3.0.1" + bin: + electron-osx-flat: bin/electron-osx-flat.js + electron-osx-sign: bin/electron-osx-sign.js + checksum: 10c0/57ff8a90b59ad976de7c1cfc9c3813c24b1d2f56fd117a753f717b6157f244184e5696cb6e024b4491f0c9b2e0856b82cc983bf66fc2b21735175246d37aab6b + languageName: node + linkType: hard + +"electron-publish@npm:23.6.0": + version: 23.6.0 + resolution: "electron-publish@npm:23.6.0" + dependencies: + "@types/fs-extra": "npm:^9.0.11" + builder-util: "npm:23.6.0" + builder-util-runtime: "npm:9.1.1" + chalk: "npm:^4.1.1" + fs-extra: "npm:^10.0.0" + lazy-val: "npm:^1.0.5" + mime: "npm:^2.5.2" + checksum: 10c0/89e9378894ef4582cbfc56a173cefb301b39a59e8fe8a8ac9e9f54d416acff809c1187bd9ee933ac7629c37a66d2768402f55fcd3bc5119da95665bfd788ce87 + languageName: node + linkType: hard + +"electron-to-chromium@npm:^1.5.249": + version: 1.5.260 + resolution: "electron-to-chromium@npm:1.5.260" + checksum: 10c0/5be308adbe7f9b370f628eb3ae35528bccc8e8592ee4848f9dfa308af658deaa87e915dd6929b6993e712929e7e6828f40434814506476ae11051381ee423fdf + languageName: node + linkType: hard + +"electron@npm:^22.3.0": + version: 22.3.27 + resolution: "electron@npm:22.3.27" + dependencies: + "@electron/get": "npm:^2.0.0" + "@types/node": "npm:^16.11.26" + extract-zip: "npm:^2.0.1" + bin: + electron: cli.js + checksum: 10c0/4ec1bbdc04686a2c778151ecc989cbe19f26d776c3ac7efc1aebb08c91cc2a4d32ed80f8d1cdea3e9acf22bdbd62ea473dc685201212328491cad6147b7aa3d3 + languageName: node + linkType: hard + +"elliptic@npm:^6.5.3, elliptic@npm:^6.6.1": + version: 6.6.1 + resolution: "elliptic@npm:6.6.1" + dependencies: + bn.js: "npm:^4.11.9" + brorand: "npm:^1.1.0" + hash.js: "npm:^1.0.0" + hmac-drbg: "npm:^1.0.1" + inherits: "npm:^2.0.4" + minimalistic-assert: "npm:^1.0.1" + minimalistic-crypto-utils: "npm:^1.0.1" + checksum: 10c0/8b24ef782eec8b472053793ea1e91ae6bee41afffdfcb78a81c0a53b191e715cbe1292aa07165958a9bbe675bd0955142560b1a007ffce7d6c765bcaf951a867 + languageName: node + linkType: hard + +"emoji-regex@npm:^8.0.0": + version: 8.0.0 + resolution: "emoji-regex@npm:8.0.0" + checksum: 10c0/b6053ad39951c4cf338f9092d7bfba448cdfd46fe6a2a034700b149ac9ffbc137e361cbd3c442297f86bed2e5f7576c1b54cc0a6bf8ef5106cc62f496af35010 + languageName: node + linkType: hard + +"emoji-regex@npm:^9.2.2": + version: 9.2.2 + resolution: "emoji-regex@npm:9.2.2" + checksum: 10c0/af014e759a72064cf66e6e694a7fc6b0ed3d8db680427b021a89727689671cefe9d04151b2cad51dbaf85d5ba790d061cd167f1cf32eb7b281f6368b3c181639 + languageName: node + linkType: hard + +"emojis-list@npm:^3.0.0": + version: 3.0.0 + resolution: "emojis-list@npm:3.0.0" + checksum: 10c0/7dc4394b7b910444910ad64b812392159a21e1a7ecc637c775a440227dcb4f80eff7fe61f4453a7d7603fa23d23d30cc93fe9e4b5ed985b88d6441cd4a35117b + languageName: node + linkType: hard + +"encodeurl@npm:~1.0.2": + version: 1.0.2 + resolution: "encodeurl@npm:1.0.2" + checksum: 10c0/f6c2387379a9e7c1156c1c3d4f9cb7bb11cf16dd4c1682e1f6746512564b053df5781029b6061296832b59fb22f459dbe250386d217c2f6e203601abb2ee0bec + languageName: node + linkType: hard + +"encodeurl@npm:~2.0.0": + version: 2.0.0 + resolution: "encodeurl@npm:2.0.0" + checksum: 10c0/5d317306acb13e6590e28e27924c754163946a2480de11865c991a3a7eed4315cd3fba378b543ca145829569eefe9b899f3d84bb09870f675ae60bc924b01ceb + languageName: node + linkType: hard + +"encoding@npm:^0.1.11, encoding@npm:^0.1.13": + version: 0.1.13 + resolution: "encoding@npm:0.1.13" + dependencies: + iconv-lite: "npm:^0.6.2" + checksum: 10c0/36d938712ff00fe1f4bac88b43bcffb5930c1efa57bbcdca9d67e1d9d6c57cfb1200fb01efe0f3109b2ce99b231f90779532814a81370a1bd3274a0f58585039 + languageName: node + linkType: hard + +"end-of-stream@npm:^1.1.0, end-of-stream@npm:^1.4.1": + version: 1.4.5 + resolution: "end-of-stream@npm:1.4.5" + dependencies: + once: "npm:^1.4.0" + checksum: 10c0/b0701c92a10b89afb1cb45bf54a5292c6f008d744eb4382fa559d54775ff31617d1d7bc3ef617575f552e24fad2c7c1a1835948c66b3f3a4be0a6c1f35c883d8 + languageName: node + linkType: hard + +"enhanced-resolve@npm:5.9.3": + version: 5.9.3 + resolution: "enhanced-resolve@npm:5.9.3" + dependencies: + graceful-fs: "npm:^4.2.4" + tapable: "npm:^2.2.0" + checksum: 10c0/743428030e1d627835bfd05b7a9570ee163e728ab1d55fcd69de9c4ff3e84356c0c9c9deaa8164afb3c648699681a403801ac85b94d6a343838d38f36109643c + languageName: node + linkType: hard + +"enhanced-resolve@npm:^0.9.1": + version: 0.9.1 + resolution: "enhanced-resolve@npm:0.9.1" + dependencies: + graceful-fs: "npm:^4.1.2" + memory-fs: "npm:^0.2.0" + tapable: "npm:^0.1.8" + checksum: 10c0/8b0ab20b7fc925a88d437bea124d112a19bd06c5186fb3592d2119b56af37731f55eb6e0567023b1263ee5ac35ef7a09a84f02cd3da26cdf01d500a2762ac3dd + languageName: node + linkType: hard + +"enhanced-resolve@npm:^5.15.0, enhanced-resolve@npm:^5.18.1": + version: 5.18.3 + resolution: "enhanced-resolve@npm:5.18.3" + dependencies: + graceful-fs: "npm:^4.2.4" + tapable: "npm:^2.2.0" + checksum: 10c0/d413c23c2d494e4c1c9c9ac7d60b812083dc6d446699ed495e69c920988af0a3c66bf3f8d0e7a45cb1686c2d4c1df9f4e7352d973f5b56fe63d8d711dd0ccc54 + languageName: node + linkType: hard + +"entities@npm:^2.0.0": + version: 2.2.0 + resolution: "entities@npm:2.2.0" + checksum: 10c0/7fba6af1f116300d2ba1c5673fc218af1961b20908638391b4e1e6d5850314ee2ac3ec22d741b3a8060479911c99305164aed19b6254bde75e7e6b1b2c3f3aa3 + languageName: node + linkType: hard + +"entities@npm:^4.4.0": + version: 4.5.0 + resolution: "entities@npm:4.5.0" + checksum: 10c0/5b039739f7621f5d1ad996715e53d964035f75ad3b9a4d38c6b3804bb226e282ffeae2443624d8fdd9c47d8e926ae9ac009c54671243f0c3294c26af7cc85250 + languageName: node + linkType: hard + +"env-paths@npm:^2.2.0, env-paths@npm:^2.2.1": + version: 2.2.1 + resolution: "env-paths@npm:2.2.1" + checksum: 10c0/285325677bf00e30845e330eec32894f5105529db97496ee3f598478e50f008c5352a41a30e5e72ec9de8a542b5a570b85699cd63bd2bc646dbcb9f311d83bc4 + languageName: node + linkType: hard + +"err-code@npm:^2.0.2": + version: 2.0.3 + resolution: "err-code@npm:2.0.3" + checksum: 10c0/b642f7b4dd4a376e954947550a3065a9ece6733ab8e51ad80db727aaae0817c2e99b02a97a3d6cecc648a97848305e728289cf312d09af395403a90c9d4d8a66 + languageName: node + linkType: hard + +"errno@npm:^0.1.1": + version: 0.1.8 + resolution: "errno@npm:0.1.8" + dependencies: + prr: "npm:~1.0.1" + bin: + errno: cli.js + checksum: 10c0/83758951967ec57bf00b5f5b7dc797e6d65a6171e57ea57adcf1bd1a0b477fd9b5b35fae5be1ff18f4090ed156bce1db749fe7e317aac19d485a5d150f6a4936 + languageName: node + linkType: hard + +"error-ex@npm:^1.3.1": + version: 1.3.4 + resolution: "error-ex@npm:1.3.4" + dependencies: + is-arrayish: "npm:^0.2.1" + checksum: 10c0/b9e34ff4778b8f3b31a8377e1c654456f4c41aeaa3d10a1138c3b7635d8b7b2e03eb2475d46d8ae055c1f180a1063e100bffabf64ea7e7388b37735df5328664 + languageName: node + linkType: hard + +"error-stack-parser@npm:^2.0.6": + version: 2.1.4 + resolution: "error-stack-parser@npm:2.1.4" + dependencies: + stackframe: "npm:^1.3.4" + checksum: 10c0/7679b780043c98b01fc546725484e0cfd3071bf5c906bbe358722972f04abf4fc3f0a77988017665bab367f6ef3fc2d0185f7528f45966b83e7c99c02d5509b9 + languageName: node + linkType: hard + +"es-abstract@npm:^1.17.5, es-abstract@npm:^1.23.2, es-abstract@npm:^1.23.3, es-abstract@npm:^1.23.5, es-abstract@npm:^1.23.6, es-abstract@npm:^1.23.9, es-abstract@npm:^1.24.0": + version: 1.24.0 + resolution: "es-abstract@npm:1.24.0" + dependencies: + array-buffer-byte-length: "npm:^1.0.2" + arraybuffer.prototype.slice: "npm:^1.0.4" + available-typed-arrays: "npm:^1.0.7" + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" + data-view-buffer: "npm:^1.0.2" + data-view-byte-length: "npm:^1.0.2" + data-view-byte-offset: "npm:^1.0.1" + es-define-property: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.1.1" + es-set-tostringtag: "npm:^2.1.0" + es-to-primitive: "npm:^1.3.0" + function.prototype.name: "npm:^1.1.8" + get-intrinsic: "npm:^1.3.0" + get-proto: "npm:^1.0.1" + get-symbol-description: "npm:^1.1.0" + globalthis: "npm:^1.0.4" + gopd: "npm:^1.2.0" + has-property-descriptors: "npm:^1.0.2" + has-proto: "npm:^1.2.0" + has-symbols: "npm:^1.1.0" + hasown: "npm:^2.0.2" + internal-slot: "npm:^1.1.0" + is-array-buffer: "npm:^3.0.5" + is-callable: "npm:^1.2.7" + is-data-view: "npm:^1.0.2" + is-negative-zero: "npm:^2.0.3" + is-regex: "npm:^1.2.1" + is-set: "npm:^2.0.3" + is-shared-array-buffer: "npm:^1.0.4" + is-string: "npm:^1.1.1" + is-typed-array: "npm:^1.1.15" + is-weakref: "npm:^1.1.1" + math-intrinsics: "npm:^1.1.0" + object-inspect: "npm:^1.13.4" + object-keys: "npm:^1.1.1" + object.assign: "npm:^4.1.7" + own-keys: "npm:^1.0.1" + regexp.prototype.flags: "npm:^1.5.4" + safe-array-concat: "npm:^1.1.3" + safe-push-apply: "npm:^1.0.0" + safe-regex-test: "npm:^1.1.0" + set-proto: "npm:^1.0.0" + stop-iteration-iterator: "npm:^1.1.0" + string.prototype.trim: "npm:^1.2.10" + string.prototype.trimend: "npm:^1.0.9" + string.prototype.trimstart: "npm:^1.0.8" + typed-array-buffer: "npm:^1.0.3" + typed-array-byte-length: "npm:^1.0.3" + typed-array-byte-offset: "npm:^1.0.4" + typed-array-length: "npm:^1.0.7" + unbox-primitive: "npm:^1.1.0" + which-typed-array: "npm:^1.1.19" + checksum: 10c0/b256e897be32df5d382786ce8cce29a1dd8c97efbab77a26609bd70f2ed29fbcfc7a31758cb07488d532e7ccccdfca76c1118f2afe5a424cdc05ca007867c318 + languageName: node + linkType: hard + +"es-define-property@npm:^1.0.0, es-define-property@npm:^1.0.1": + version: 1.0.1 + resolution: "es-define-property@npm:1.0.1" + checksum: 10c0/3f54eb49c16c18707949ff25a1456728c883e81259f045003499efba399c08bad00deebf65cccde8c0e07908c1a225c9d472b7107e558f2a48e28d530e34527c + languageName: node + linkType: hard + +"es-errors@npm:^1.3.0": + version: 1.3.0 + resolution: "es-errors@npm:1.3.0" + checksum: 10c0/0a61325670072f98d8ae3b914edab3559b6caa980f08054a3b872052640d91da01d38df55df797fcc916389d77fc92b8d5906cf028f4db46d7e3003abecbca85 + languageName: node + linkType: hard + +"es-get-iterator@npm:^1.1.3": + version: 1.1.3 + resolution: "es-get-iterator@npm:1.1.3" + dependencies: + call-bind: "npm:^1.0.2" + get-intrinsic: "npm:^1.1.3" + has-symbols: "npm:^1.0.3" + is-arguments: "npm:^1.1.1" + is-map: "npm:^2.0.2" + is-set: "npm:^2.0.2" + is-string: "npm:^1.0.7" + isarray: "npm:^2.0.5" + stop-iteration-iterator: "npm:^1.0.0" + checksum: 10c0/ebd11effa79851ea75d7f079405f9d0dc185559fd65d986c6afea59a0ff2d46c2ed8675f19f03dce7429d7f6c14ff9aede8d121fbab78d75cfda6a263030bac0 + languageName: node + linkType: hard + +"es-iterator-helpers@npm:^1.0.12, es-iterator-helpers@npm:^1.2.1": + version: 1.2.1 + resolution: "es-iterator-helpers@npm:1.2.1" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.3" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.6" + es-errors: "npm:^1.3.0" + es-set-tostringtag: "npm:^2.0.3" + function-bind: "npm:^1.1.2" + get-intrinsic: "npm:^1.2.6" + globalthis: "npm:^1.0.4" + gopd: "npm:^1.2.0" + has-property-descriptors: "npm:^1.0.2" + has-proto: "npm:^1.2.0" + has-symbols: "npm:^1.1.0" + internal-slot: "npm:^1.1.0" + iterator.prototype: "npm:^1.1.4" + safe-array-concat: "npm:^1.1.3" + checksum: 10c0/97e3125ca472d82d8aceea11b790397648b52c26d8768ea1c1ee6309ef45a8755bb63225a43f3150c7591cffc17caf5752459f1e70d583b4184370a8f04ebd2f + languageName: node + linkType: hard + +"es-object-atoms@npm:^1.0.0, es-object-atoms@npm:^1.1.1": + version: 1.1.1 + resolution: "es-object-atoms@npm:1.1.1" + dependencies: + es-errors: "npm:^1.3.0" + checksum: 10c0/65364812ca4daf48eb76e2a3b7a89b3f6a2e62a1c420766ce9f692665a29d94fe41fe88b65f24106f449859549711e4b40d9fb8002d862dfd7eb1c512d10be0c + languageName: node + linkType: hard + +"es-set-tostringtag@npm:^2.0.3, es-set-tostringtag@npm:^2.1.0": + version: 2.1.0 + resolution: "es-set-tostringtag@npm:2.1.0" + dependencies: + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.6" + has-tostringtag: "npm:^1.0.2" + hasown: "npm:^2.0.2" + checksum: 10c0/ef2ca9ce49afe3931cb32e35da4dcb6d86ab02592cfc2ce3e49ced199d9d0bb5085fc7e73e06312213765f5efa47cc1df553a6a5154584b21448e9fb8355b1af + languageName: node + linkType: hard + +"es-shim-unscopables@npm:^1.0.2, es-shim-unscopables@npm:^1.1.0": + version: 1.1.0 + resolution: "es-shim-unscopables@npm:1.1.0" + dependencies: + hasown: "npm:^2.0.2" + checksum: 10c0/1b9702c8a1823fc3ef39035a4e958802cf294dd21e917397c561d0b3e195f383b978359816b1732d02b255ccf63e1e4815da0065b95db8d7c992037be3bbbcdb + languageName: node + linkType: hard + +"es-to-primitive@npm:^1.2.1, es-to-primitive@npm:^1.3.0": + version: 1.3.0 + resolution: "es-to-primitive@npm:1.3.0" + dependencies: + is-callable: "npm:^1.2.7" + is-date-object: "npm:^1.0.5" + is-symbol: "npm:^1.0.4" + checksum: 10c0/c7e87467abb0b438639baa8139f701a06537d2b9bc758f23e8622c3b42fd0fdb5bde0f535686119e446dd9d5e4c0f238af4e14960f4771877cf818d023f6730b + languageName: node + linkType: hard + +"es5-ext@npm:^0.10.35, es5-ext@npm:^0.10.62, es5-ext@npm:^0.10.64, es5-ext@npm:~0.10.14": + version: 0.10.64 + resolution: "es5-ext@npm:0.10.64" + dependencies: + es6-iterator: "npm:^2.0.3" + es6-symbol: "npm:^3.1.3" + esniff: "npm:^2.0.1" + next-tick: "npm:^1.1.0" + checksum: 10c0/4459b6ae216f3c615db086e02437bdfde851515a101577fd61b19f9b3c1ad924bab4d197981eb7f0ccb915f643f2fc10ff76b97a680e96cbb572d15a27acd9a3 + languageName: node + linkType: hard + +"es5-imcompatible-versions@npm:^0.1.78": + version: 0.1.90 + resolution: "es5-imcompatible-versions@npm:0.1.90" + checksum: 10c0/a05672554d4b9488af00c56b8adea3c4b6df4601d9bbd24b665f5b2ea5e5f0171f0843632fa78302057bd384e4dc5de8a2042270c84655294d2793db2849e17f + languageName: node + linkType: hard + +"es6-error@npm:^4.1.1": + version: 4.1.1 + resolution: "es6-error@npm:4.1.1" + checksum: 10c0/357663fb1e845c047d548c3d30f86e005db71e122678f4184ced0693f634688c3f3ef2d7de7d4af732f734de01f528b05954e270f06aa7d133679fb9fe6600ef + languageName: node + linkType: hard + +"es6-iterator@npm:^2.0.3": + version: 2.0.3 + resolution: "es6-iterator@npm:2.0.3" + dependencies: + d: "npm:1" + es5-ext: "npm:^0.10.35" + es6-symbol: "npm:^3.1.1" + checksum: 10c0/91f20b799dba28fb05bf623c31857fc1524a0f1c444903beccaf8929ad196c8c9ded233e5ac7214fc63a92b3f25b64b7f2737fcca8b1f92d2d96cf3ac902f5d8 + languageName: node + linkType: hard + +"es6-promise@npm:^4.1.1": + version: 4.2.8 + resolution: "es6-promise@npm:4.2.8" + checksum: 10c0/2373d9c5e9a93bdd9f9ed32ff5cb6dd3dd785368d1c21e9bbbfd07d16345b3774ae260f2bd24c8f836a6903f432b4151e7816a7fa8891ccb4e1a55a028ec42c3 + languageName: node + linkType: hard + +"es6-symbol@npm:^3.1.1, es6-symbol@npm:^3.1.3": + version: 3.1.4 + resolution: "es6-symbol@npm:3.1.4" + dependencies: + d: "npm:^1.0.2" + ext: "npm:^1.7.0" + checksum: 10c0/777bf3388db5d7919e09a0fd175aa5b8a62385b17cb2227b7a137680cba62b4d9f6193319a102642aa23d5840d38a62e4784f19cfa5be4a2210a3f0e9b23d15d + languageName: node + linkType: hard + +"esbuild@npm:0.21.4": + version: 0.21.4 + resolution: "esbuild@npm:0.21.4" + dependencies: + "@esbuild/aix-ppc64": "npm:0.21.4" + "@esbuild/android-arm": "npm:0.21.4" + "@esbuild/android-arm64": "npm:0.21.4" + "@esbuild/android-x64": "npm:0.21.4" + "@esbuild/darwin-arm64": "npm:0.21.4" + "@esbuild/darwin-x64": "npm:0.21.4" + "@esbuild/freebsd-arm64": "npm:0.21.4" + "@esbuild/freebsd-x64": "npm:0.21.4" + "@esbuild/linux-arm": "npm:0.21.4" + "@esbuild/linux-arm64": "npm:0.21.4" + "@esbuild/linux-ia32": "npm:0.21.4" + "@esbuild/linux-loong64": "npm:0.21.4" + "@esbuild/linux-mips64el": "npm:0.21.4" + "@esbuild/linux-ppc64": "npm:0.21.4" + "@esbuild/linux-riscv64": "npm:0.21.4" + "@esbuild/linux-s390x": "npm:0.21.4" + "@esbuild/linux-x64": "npm:0.21.4" + "@esbuild/netbsd-x64": "npm:0.21.4" + "@esbuild/openbsd-x64": "npm:0.21.4" + "@esbuild/sunos-x64": "npm:0.21.4" + "@esbuild/win32-arm64": "npm:0.21.4" + "@esbuild/win32-ia32": "npm:0.21.4" + "@esbuild/win32-x64": "npm:0.21.4" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10c0/83276c7b82bc3415199da91a84a01cf287d4912f2c02fead9c0542d6bda463d6d152cb7fb86f680dae72dc701c864a8963069ddb9e2b344948595cc87f81c4f1 + languageName: node + linkType: hard + +"esbuild@npm:^0.18.10, esbuild@npm:~0.18.20": + version: 0.18.20 + resolution: "esbuild@npm:0.18.20" + dependencies: + "@esbuild/android-arm": "npm:0.18.20" + "@esbuild/android-arm64": "npm:0.18.20" + "@esbuild/android-x64": "npm:0.18.20" + "@esbuild/darwin-arm64": "npm:0.18.20" + "@esbuild/darwin-x64": "npm:0.18.20" + "@esbuild/freebsd-arm64": "npm:0.18.20" + "@esbuild/freebsd-x64": "npm:0.18.20" + "@esbuild/linux-arm": "npm:0.18.20" + "@esbuild/linux-arm64": "npm:0.18.20" + "@esbuild/linux-ia32": "npm:0.18.20" + "@esbuild/linux-loong64": "npm:0.18.20" + "@esbuild/linux-mips64el": "npm:0.18.20" + "@esbuild/linux-ppc64": "npm:0.18.20" + "@esbuild/linux-riscv64": "npm:0.18.20" + "@esbuild/linux-s390x": "npm:0.18.20" + "@esbuild/linux-x64": "npm:0.18.20" + "@esbuild/netbsd-x64": "npm:0.18.20" + "@esbuild/openbsd-x64": "npm:0.18.20" + "@esbuild/sunos-x64": "npm:0.18.20" + "@esbuild/win32-arm64": "npm:0.18.20" + "@esbuild/win32-ia32": "npm:0.18.20" + "@esbuild/win32-x64": "npm:0.18.20" + dependenciesMeta: + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10c0/473b1d92842f50a303cf948a11ebd5f69581cd254d599dd9d62f9989858e0533f64e83b723b5e1398a5b488c0f5fd088795b4235f65ecaf4f007d4b79f04bc88 + languageName: node + linkType: hard + +"escalade@npm:^3.1.1, escalade@npm:^3.2.0": + version: 3.2.0 + resolution: "escalade@npm:3.2.0" + checksum: 10c0/ced4dd3a78e15897ed3be74e635110bbf3b08877b0a41be50dcb325ee0e0b5f65fc2d50e9845194d7c4633f327e2e1c6cce00a71b617c5673df0374201d67f65 + languageName: node + linkType: hard + +"escape-html@npm:~1.0.3": + version: 1.0.3 + resolution: "escape-html@npm:1.0.3" + checksum: 10c0/524c739d776b36c3d29fa08a22e03e8824e3b2fd57500e5e44ecf3cc4707c34c60f9ca0781c0e33d191f2991161504c295e98f68c78fe7baa6e57081ec6ac0a3 + languageName: node + linkType: hard + +"escape-string-regexp@npm:^1.0.5": + version: 1.0.5 + resolution: "escape-string-regexp@npm:1.0.5" + checksum: 10c0/a968ad453dd0c2724e14a4f20e177aaf32bb384ab41b674a8454afe9a41c5e6fe8903323e0a1052f56289d04bd600f81278edf140b0fcc02f5cac98d0f5b5371 + languageName: node + linkType: hard + +"escape-string-regexp@npm:^4.0.0": + version: 4.0.0 + resolution: "escape-string-regexp@npm:4.0.0" + checksum: 10c0/9497d4dd307d845bd7f75180d8188bb17ea8c151c1edbf6b6717c100e104d629dc2dfb687686181b0f4b7d732c7dfdc4d5e7a8ff72de1b0ca283a75bbb3a9cd9 + languageName: node + linkType: hard + +"escape-string-regexp@npm:^5.0.0": + version: 5.0.0 + resolution: "escape-string-regexp@npm:5.0.0" + checksum: 10c0/6366f474c6f37a802800a435232395e04e9885919873e382b157ab7e8f0feb8fed71497f84a6f6a81a49aab41815522f5839112bd38026d203aea0c91622df95 + languageName: node + linkType: hard + +"eslint-config-airbnb-base@npm:^15.0.0": + version: 15.0.0 + resolution: "eslint-config-airbnb-base@npm:15.0.0" + dependencies: + confusing-browser-globals: "npm:^1.0.10" + object.assign: "npm:^4.1.2" + object.entries: "npm:^1.1.5" + semver: "npm:^6.3.0" + peerDependencies: + eslint: ^7.32.0 || ^8.2.0 + eslint-plugin-import: ^2.25.2 + checksum: 10c0/93639d991654414756f82ad7860aac30b0dc6797277b7904ddb53ed88a32c470598696bbc6c503e066414024d305221974d3769e6642de65043bedf29cbbd30f + languageName: node + linkType: hard + +"eslint-config-prettier@npm:^9.0.0": + version: 9.1.2 + resolution: "eslint-config-prettier@npm:9.1.2" + peerDependencies: + eslint: ">=7.0.0" + bin: + eslint-config-prettier: bin/cli.js + checksum: 10c0/d2e9dc913b1677764a4732433d83d258f40820458c65d0274cb9e3eaf6559b39f2136446f310c05abed065a4b3c2e901807ccf583dff76c6227eaebf4132c39a + languageName: node + linkType: hard + +"eslint-import-resolver-node@npm:^0.3.9": + version: 0.3.9 + resolution: "eslint-import-resolver-node@npm:0.3.9" + dependencies: + debug: "npm:^3.2.7" + is-core-module: "npm:^2.13.0" + resolve: "npm:^1.22.4" + checksum: 10c0/0ea8a24a72328a51fd95aa8f660dcca74c1429806737cf10261ab90cfcaaf62fd1eff664b76a44270868e0a932711a81b250053942595bcd00a93b1c1575dd61 + languageName: node + linkType: hard + +"eslint-import-resolver-webpack@npm:^0.13.7": + version: 0.13.10 + resolution: "eslint-import-resolver-webpack@npm:0.13.10" + dependencies: + debug: "npm:^3.2.7" + enhanced-resolve: "npm:^0.9.1" + find-root: "npm:^1.1.0" + hasown: "npm:^2.0.2" + interpret: "npm:^1.4.0" + is-core-module: "npm:^2.15.1" + is-regex: "npm:^1.2.0" + lodash: "npm:^4.17.21" + resolve: "npm:^2.0.0-next.5" + semver: "npm:^5.7.2" + peerDependencies: + eslint-plugin-import: ">=1.4.0" + webpack: ">=1.11.0" + checksum: 10c0/781cc6934771e6ba73e8ed29098d07b52d26e1de3aabfe13b44f658360e951bd451b6bd40f8c9a1567b471d6f80ae944948411b4358de975eb60611dc31babf4 + languageName: node + linkType: hard + +"eslint-module-utils@npm:^2.12.1": + version: 2.12.1 + resolution: "eslint-module-utils@npm:2.12.1" + dependencies: + debug: "npm:^3.2.7" + peerDependenciesMeta: + eslint: + optional: true + checksum: 10c0/6f4efbe7a91ae49bf67b4ab3644cb60bc5bd7db4cb5521de1b65be0847ffd3fb6bce0dd68f0995e1b312d137f768e2a1f842ee26fe73621afa05f850628fdc40 + languageName: node + linkType: hard + +"eslint-plugin-babel@npm:^5.3.1": + version: 5.3.1 + resolution: "eslint-plugin-babel@npm:5.3.1" + dependencies: + eslint-rule-composer: "npm:^0.3.0" + peerDependencies: + eslint: ">=4.0.0" + checksum: 10c0/c73e054c3cf3c5392e8ea7e56f41db3859b9d7c0dd347c28a5f08ae87889cc4879fcddfe227ee1ec075a9ab62e34e245d7e6e723180dfa36d07397c2cbb2c1a1 + languageName: node + linkType: hard + +"eslint-plugin-import@npm:^2.28.1": + version: 2.32.0 + resolution: "eslint-plugin-import@npm:2.32.0" + dependencies: + "@rtsao/scc": "npm:^1.1.0" + array-includes: "npm:^3.1.9" + array.prototype.findlastindex: "npm:^1.2.6" + array.prototype.flat: "npm:^1.3.3" + array.prototype.flatmap: "npm:^1.3.3" + debug: "npm:^3.2.7" + doctrine: "npm:^2.1.0" + eslint-import-resolver-node: "npm:^0.3.9" + eslint-module-utils: "npm:^2.12.1" + hasown: "npm:^2.0.2" + is-core-module: "npm:^2.16.1" + is-glob: "npm:^4.0.3" + minimatch: "npm:^3.1.2" + object.fromentries: "npm:^2.0.8" + object.groupby: "npm:^1.0.3" + object.values: "npm:^1.2.1" + semver: "npm:^6.3.1" + string.prototype.trimend: "npm:^1.0.9" + tsconfig-paths: "npm:^3.15.0" + peerDependencies: + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 + checksum: 10c0/bfb1b8fc8800398e62ddfefbf3638d185286edfed26dfe00875cc2846d954491b4f5112457831588b757fa789384e1ae585f812614c4797f0499fa234fd4a48b + languageName: node + linkType: hard + +"eslint-plugin-jest@npm:27.2.3": + version: 27.2.3 + resolution: "eslint-plugin-jest@npm:27.2.3" + dependencies: + "@typescript-eslint/utils": "npm:^5.10.0" + peerDependencies: + "@typescript-eslint/eslint-plugin": ^5.0.0 || ^6.0.0 + eslint: ^7.0.0 || ^8.0.0 + jest: "*" + peerDependenciesMeta: + "@typescript-eslint/eslint-plugin": + optional: true + jest: + optional: true + checksum: 10c0/e9e5b4372ef9fbb4fb781c335dadd9b45b4607db92f9b9f63c9c0fd777ef1a7487aa7ba459fb68eb8320d7684457d0d574fd6170f36f0d7aaa350de6dc9fa333 + languageName: node + linkType: hard + +"eslint-plugin-prettier@npm:^5.0.0": + version: 5.5.4 + resolution: "eslint-plugin-prettier@npm:5.5.4" + dependencies: + prettier-linter-helpers: "npm:^1.0.0" + synckit: "npm:^0.11.7" + peerDependencies: + "@types/eslint": ">=8.0.0" + eslint: ">=8.0.0" + eslint-config-prettier: ">= 7.0.0 <10.0.0 || >=10.1.0" + prettier: ">=3.0.0" + peerDependenciesMeta: + "@types/eslint": + optional: true + eslint-config-prettier: + optional: true + checksum: 10c0/5cc780e0ab002f838ad8057409e86de4ff8281aa2704a50fa8511abff87028060c2e45741bc9cbcbd498712e8d189de8026e70aed9e20e50fe5ba534ee5a8442 + languageName: node + linkType: hard + +"eslint-plugin-react-hooks@npm:4.6.0": + version: 4.6.0 + resolution: "eslint-plugin-react-hooks@npm:4.6.0" + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 + checksum: 10c0/58c7e10ea5792c33346fcf5cb4024e14837035ce412ff99c2dcb7c4f903dc9b17939078f80bfef826301ce326582c396c00e8e0ac9d10ac2cde2b42d33763c65 + languageName: node + linkType: hard + +"eslint-plugin-react-hooks@npm:^4.6.0": + version: 4.6.2 + resolution: "eslint-plugin-react-hooks@npm:4.6.2" + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 + checksum: 10c0/4844e58c929bc05157fb70ba1e462e34f1f4abcbc8dd5bbe5b04513d33e2699effb8bca668297976ceea8e7ebee4e8fc29b9af9d131bcef52886feaa2308b2cc + languageName: node + linkType: hard + +"eslint-plugin-react@npm:7.33.2": + version: 7.33.2 + resolution: "eslint-plugin-react@npm:7.33.2" + dependencies: + array-includes: "npm:^3.1.6" + array.prototype.flatmap: "npm:^1.3.1" + array.prototype.tosorted: "npm:^1.1.1" + doctrine: "npm:^2.1.0" + es-iterator-helpers: "npm:^1.0.12" + estraverse: "npm:^5.3.0" + jsx-ast-utils: "npm:^2.4.1 || ^3.0.0" + minimatch: "npm:^3.1.2" + object.entries: "npm:^1.1.6" + object.fromentries: "npm:^2.0.6" + object.hasown: "npm:^1.1.2" + object.values: "npm:^1.1.6" + prop-types: "npm:^15.8.1" + resolve: "npm:^2.0.0-next.4" + semver: "npm:^6.3.1" + string.prototype.matchall: "npm:^4.0.8" + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 + checksum: 10c0/f9b247861024bafc396c4bd3c9ac946604b3b23077251c98f23602aa22027a0c33a69157fd49564e4ff7f17b3678e5dc366a46c7ec42a09454d7cbce786d5001 + languageName: node + linkType: hard + +"eslint-plugin-react@npm:^7.33.2": + version: 7.37.5 + resolution: "eslint-plugin-react@npm:7.37.5" + dependencies: + array-includes: "npm:^3.1.8" + array.prototype.findlast: "npm:^1.2.5" + array.prototype.flatmap: "npm:^1.3.3" + array.prototype.tosorted: "npm:^1.1.4" + doctrine: "npm:^2.1.0" + es-iterator-helpers: "npm:^1.2.1" + estraverse: "npm:^5.3.0" + hasown: "npm:^2.0.2" + jsx-ast-utils: "npm:^2.4.1 || ^3.0.0" + minimatch: "npm:^3.1.2" + object.entries: "npm:^1.1.9" + object.fromentries: "npm:^2.0.8" + object.values: "npm:^1.2.1" + prop-types: "npm:^15.8.1" + resolve: "npm:^2.0.0-next.5" + semver: "npm:^6.3.1" + string.prototype.matchall: "npm:^4.0.12" + string.prototype.repeat: "npm:^1.0.0" + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 + checksum: 10c0/c850bfd556291d4d9234f5ca38db1436924a1013627c8ab1853f77cac73ec19b020e861e6c7b783436a48b6ffcdfba4547598235a37ad4611b6739f65fd8ad57 + languageName: node + linkType: hard + +"eslint-rule-composer@npm:^0.3.0": + version: 0.3.0 + resolution: "eslint-rule-composer@npm:0.3.0" + checksum: 10c0/1f0c40d209e1503a955101a0dbba37e7fc67c8aaa47a5b9ae0b0fcbae7022c86e52b3df2b1b9ffd658e16cd80f31fff92e7222460a44d8251e61d49e0af79a07 + languageName: node + linkType: hard + +"eslint-scope@npm:5.1.1, eslint-scope@npm:^5.1.1": + version: 5.1.1 + resolution: "eslint-scope@npm:5.1.1" + dependencies: + esrecurse: "npm:^4.3.0" + estraverse: "npm:^4.1.1" + checksum: 10c0/d30ef9dc1c1cbdece34db1539a4933fe3f9b14e1ffb27ecc85987902ee663ad7c9473bbd49a9a03195a373741e62e2f807c4938992e019b511993d163450e70a + languageName: node + linkType: hard + +"eslint-scope@npm:^7.2.2": + version: 7.2.2 + resolution: "eslint-scope@npm:7.2.2" + dependencies: + esrecurse: "npm:^4.3.0" + estraverse: "npm:^5.2.0" + checksum: 10c0/613c267aea34b5a6d6c00514e8545ef1f1433108097e857225fed40d397dd6b1809dffd11c2fde23b37ca53d7bf935fe04d2a18e6fc932b31837b6ad67e1c116 + languageName: node + linkType: hard + +"eslint-visitor-keys@npm:^2.1.0": + version: 2.1.0 + resolution: "eslint-visitor-keys@npm:2.1.0" + checksum: 10c0/9f0e3a2db751d84067d15977ac4b4472efd6b303e369e6ff241a99feac04da758f46d5add022c33d06b53596038dbae4b4aceb27c7e68b8dfc1055b35e495787 + languageName: node + linkType: hard + +"eslint-visitor-keys@npm:^3.3.0, eslint-visitor-keys@npm:^3.4.1, eslint-visitor-keys@npm:^3.4.3": + version: 3.4.3 + resolution: "eslint-visitor-keys@npm:3.4.3" + checksum: 10c0/92708e882c0a5ffd88c23c0b404ac1628cf20104a108c745f240a13c332a11aac54f49a22d5762efbffc18ecbc9a580d1b7ad034bf5f3cc3307e5cbff2ec9820 + languageName: node + linkType: hard + +"eslint@npm:^8.49.0": + version: 8.57.1 + resolution: "eslint@npm:8.57.1" + dependencies: + "@eslint-community/eslint-utils": "npm:^4.2.0" + "@eslint-community/regexpp": "npm:^4.6.1" + "@eslint/eslintrc": "npm:^2.1.4" + "@eslint/js": "npm:8.57.1" + "@humanwhocodes/config-array": "npm:^0.13.0" + "@humanwhocodes/module-importer": "npm:^1.0.1" + "@nodelib/fs.walk": "npm:^1.2.8" + "@ungap/structured-clone": "npm:^1.2.0" + ajv: "npm:^6.12.4" + chalk: "npm:^4.0.0" + cross-spawn: "npm:^7.0.2" + debug: "npm:^4.3.2" + doctrine: "npm:^3.0.0" + escape-string-regexp: "npm:^4.0.0" + eslint-scope: "npm:^7.2.2" + eslint-visitor-keys: "npm:^3.4.3" + espree: "npm:^9.6.1" + esquery: "npm:^1.4.2" + esutils: "npm:^2.0.2" + fast-deep-equal: "npm:^3.1.3" + file-entry-cache: "npm:^6.0.1" + find-up: "npm:^5.0.0" + glob-parent: "npm:^6.0.2" + globals: "npm:^13.19.0" + graphemer: "npm:^1.4.0" + ignore: "npm:^5.2.0" + imurmurhash: "npm:^0.1.4" + is-glob: "npm:^4.0.0" + is-path-inside: "npm:^3.0.3" + js-yaml: "npm:^4.1.0" + json-stable-stringify-without-jsonify: "npm:^1.0.1" + levn: "npm:^0.4.1" + lodash.merge: "npm:^4.6.2" + minimatch: "npm:^3.1.2" + natural-compare: "npm:^1.4.0" + optionator: "npm:^0.9.3" + strip-ansi: "npm:^6.0.1" + text-table: "npm:^0.2.0" + bin: + eslint: bin/eslint.js + checksum: 10c0/1fd31533086c1b72f86770a4d9d7058ee8b4643fd1cfd10c7aac1ecb8725698e88352a87805cf4b2ce890aa35947df4b4da9655fb7fdfa60dbb448a43f6ebcf1 + languageName: node + linkType: hard + +"esniff@npm:^2.0.1": + version: 2.0.1 + resolution: "esniff@npm:2.0.1" + dependencies: + d: "npm:^1.0.1" + es5-ext: "npm:^0.10.62" + event-emitter: "npm:^0.3.5" + type: "npm:^2.7.2" + checksum: 10c0/7efd8d44ac20e5db8cb0ca77eb65eca60628b2d0f3a1030bcb05e71cc40e6e2935c47b87dba3c733db12925aa5b897f8e0e7a567a2c274206f184da676ea2e65 + languageName: node + linkType: hard + +"espree@npm:^9.6.0, espree@npm:^9.6.1": + version: 9.6.1 + resolution: "espree@npm:9.6.1" + dependencies: + acorn: "npm:^8.9.0" + acorn-jsx: "npm:^5.3.2" + eslint-visitor-keys: "npm:^3.4.1" + checksum: 10c0/1a2e9b4699b715347f62330bcc76aee224390c28bb02b31a3752e9d07549c473f5f986720483c6469cf3cfb3c9d05df612ffc69eb1ee94b54b739e67de9bb460 + languageName: node + linkType: hard + +"esprima@npm:^4.0.0": + version: 4.0.1 + resolution: "esprima@npm:4.0.1" + bin: + esparse: ./bin/esparse.js + esvalidate: ./bin/esvalidate.js + checksum: 10c0/ad4bab9ead0808cf56501750fd9d3fb276f6b105f987707d059005d57e182d18a7c9ec7f3a01794ebddcca676773e42ca48a32d67a250c9d35e009ca613caba3 + languageName: node + linkType: hard + +"esquery@npm:^1.4.2": + version: 1.6.0 + resolution: "esquery@npm:1.6.0" + dependencies: + estraverse: "npm:^5.1.0" + checksum: 10c0/cb9065ec605f9da7a76ca6dadb0619dfb611e37a81e318732977d90fab50a256b95fee2d925fba7c2f3f0523aa16f91587246693bc09bc34d5a59575fe6e93d2 + languageName: node + linkType: hard + +"esrecurse@npm:^4.3.0": + version: 4.3.0 + resolution: "esrecurse@npm:4.3.0" + dependencies: + estraverse: "npm:^5.2.0" + checksum: 10c0/81a37116d1408ded88ada45b9fb16dbd26fba3aadc369ce50fcaf82a0bac12772ebd7b24cd7b91fc66786bf2c1ac7b5f196bc990a473efff972f5cb338877cf5 + languageName: node + linkType: hard + +"estraverse@npm:^4.1.1": + version: 4.3.0 + resolution: "estraverse@npm:4.3.0" + checksum: 10c0/9cb46463ef8a8a4905d3708a652d60122a0c20bb58dec7e0e12ab0e7235123d74214fc0141d743c381813e1b992767e2708194f6f6e0f9fd00c1b4e0887b8b6d + languageName: node + linkType: hard + +"estraverse@npm:^5.1.0, estraverse@npm:^5.2.0, estraverse@npm:^5.3.0": + version: 5.3.0 + resolution: "estraverse@npm:5.3.0" + checksum: 10c0/1ff9447b96263dec95d6d67431c5e0771eb9776427421260a3e2f0fdd5d6bd4f8e37a7338f5ad2880c9f143450c9b1e4fc2069060724570a49cf9cf0312bd107 + languageName: node + linkType: hard + +"esutils@npm:^2.0.2": + version: 2.0.3 + resolution: "esutils@npm:2.0.3" + checksum: 10c0/9a2fe69a41bfdade834ba7c42de4723c97ec776e40656919c62cbd13607c45e127a003f05f724a1ea55e5029a4cf2de444b13009f2af71271e42d93a637137c7 + languageName: node + linkType: hard + +"etag@npm:~1.8.1": + version: 1.8.1 + resolution: "etag@npm:1.8.1" + checksum: 10c0/12be11ef62fb9817314d790089a0a49fae4e1b50594135dcb8076312b7d7e470884b5100d249b28c18581b7fd52f8b485689ffae22a11ed9ec17377a33a08f84 + languageName: node + linkType: hard + +"event-emitter@npm:^0.3.5, event-emitter@npm:~0.3.5": + version: 0.3.5 + resolution: "event-emitter@npm:0.3.5" + dependencies: + d: "npm:1" + es5-ext: "npm:~0.10.14" + checksum: 10c0/75082fa8ffb3929766d0f0a063bfd6046bd2a80bea2666ebaa0cfd6f4a9116be6647c15667bea77222afc12f5b4071b68d393cf39fdaa0e8e81eda006160aff0 + languageName: node + linkType: hard + +"event-source-polyfill@npm:^1.0.31": + version: 1.0.31 + resolution: "event-source-polyfill@npm:1.0.31" + checksum: 10c0/79966f5084796e14f9a9dec315a2ccc220dedc51ff5f2b198dc80e3cb2ae01428d39d9bf66ed679f1944be086b9f6e84ea3dc933b81b0411c07f99672135679b + languageName: node + linkType: hard + +"events-okam@npm:^3.0.0": + version: 3.3.0 + resolution: "events-okam@npm:3.3.0" + checksum: 10c0/9ae077524c8cbf8192fe0e47b8aaa1696dd13d5eb2f47fa844d0fc6d2600c5d573e0a79e8c426a0a4b5422db07189b637d50f166af0cf7a37f9c28d2a6607298 + languageName: node + linkType: hard + +"events@npm:^3.0.0": + version: 3.3.0 + resolution: "events@npm:3.3.0" + checksum: 10c0/d6b6f2adbccbcda74ddbab52ed07db727ef52e31a61ed26db9feb7dc62af7fc8e060defa65e5f8af9449b86b52cc1a1f6a79f2eafcf4e62add2b7a1fa4a432f6 + languageName: node + linkType: hard + +"evp_bytestokey@npm:^1.0.0, evp_bytestokey@npm:^1.0.3": + version: 1.0.3 + resolution: "evp_bytestokey@npm:1.0.3" + dependencies: + md5.js: "npm:^1.3.4" + node-gyp: "npm:latest" + safe-buffer: "npm:^5.1.1" + checksum: 10c0/77fbe2d94a902a80e9b8f5a73dcd695d9c14899c5e82967a61b1fc6cbbb28c46552d9b127cff47c45fcf684748bdbcfa0a50410349109de87ceb4b199ef6ee99 + languageName: node + linkType: hard + +"execa@npm:^5.0.0, execa@npm:^5.1.1": + version: 5.1.1 + resolution: "execa@npm:5.1.1" + dependencies: + cross-spawn: "npm:^7.0.3" + get-stream: "npm:^6.0.0" + human-signals: "npm:^2.1.0" + is-stream: "npm:^2.0.0" + merge-stream: "npm:^2.0.0" + npm-run-path: "npm:^4.0.1" + onetime: "npm:^5.1.2" + signal-exit: "npm:^3.0.3" + strip-final-newline: "npm:^2.0.0" + checksum: 10c0/c8e615235e8de4c5addf2fa4c3da3e3aa59ce975a3e83533b4f6a71750fb816a2e79610dc5f1799b6e28976c9ae86747a36a606655bf8cb414a74d8d507b304f + languageName: node + linkType: hard + +"execa@npm:^7.1.1": + version: 7.2.0 + resolution: "execa@npm:7.2.0" + dependencies: + cross-spawn: "npm:^7.0.3" + get-stream: "npm:^6.0.1" + human-signals: "npm:^4.3.0" + is-stream: "npm:^3.0.0" + merge-stream: "npm:^2.0.0" + npm-run-path: "npm:^5.1.0" + onetime: "npm:^6.0.0" + signal-exit: "npm:^3.0.7" + strip-final-newline: "npm:^3.0.0" + checksum: 10c0/098cd6a1bc26d509e5402c43f4971736450b84d058391820c6f237aeec6436963e006fd8423c9722f148c53da86aa50045929c7278b5522197dff802d10f9885 + languageName: node + linkType: hard + +"exponential-backoff@npm:^3.1.1": + version: 3.1.3 + resolution: "exponential-backoff@npm:3.1.3" + checksum: 10c0/77e3ae682b7b1f4972f563c6dbcd2b0d54ac679e62d5d32f3e5085feba20483cf28bd505543f520e287a56d4d55a28d7874299941faf637e779a1aa5994d1267 + languageName: node + linkType: hard + +"express-http-proxy@npm:^2.1.1": + version: 2.1.2 + resolution: "express-http-proxy@npm:2.1.2" + dependencies: + debug: "npm:^3.0.1" + es6-promise: "npm:^4.1.1" + raw-body: "npm:^2.3.0" + checksum: 10c0/e40df492eff2cf3a7b0341dabe3096c799d0a73a1a7c309709bfba8c5928bd80f417f3831a7092b94043c7d9543af7917259e124c78f5222e3f9b01e6a08a3e9 + languageName: node + linkType: hard + +"express@npm:^4.18.2": + version: 4.21.2 + resolution: "express@npm:4.21.2" + dependencies: + accepts: "npm:~1.3.8" + array-flatten: "npm:1.1.1" + body-parser: "npm:1.20.3" + content-disposition: "npm:0.5.4" + content-type: "npm:~1.0.4" + cookie: "npm:0.7.1" + cookie-signature: "npm:1.0.6" + debug: "npm:2.6.9" + depd: "npm:2.0.0" + encodeurl: "npm:~2.0.0" + escape-html: "npm:~1.0.3" + etag: "npm:~1.8.1" + finalhandler: "npm:1.3.1" + fresh: "npm:0.5.2" + http-errors: "npm:2.0.0" + merge-descriptors: "npm:1.0.3" + methods: "npm:~1.1.2" + on-finished: "npm:2.4.1" + parseurl: "npm:~1.3.3" + path-to-regexp: "npm:0.1.12" + proxy-addr: "npm:~2.0.7" + qs: "npm:6.13.0" + range-parser: "npm:~1.2.1" + safe-buffer: "npm:5.2.1" + send: "npm:0.19.0" + serve-static: "npm:1.16.2" + setprototypeof: "npm:1.2.0" + statuses: "npm:2.0.1" + type-is: "npm:~1.6.18" + utils-merge: "npm:1.0.1" + vary: "npm:~1.1.2" + checksum: 10c0/38168fd0a32756600b56e6214afecf4fc79ec28eca7f7a91c2ab8d50df4f47562ca3f9dee412da7f5cea6b1a1544b33b40f9f8586dbacfbdada0fe90dbb10a1f + languageName: node + linkType: hard + +"ext@npm:^1.7.0": + version: 1.7.0 + resolution: "ext@npm:1.7.0" + dependencies: + type: "npm:^2.7.2" + checksum: 10c0/a8e5f34e12214e9eee3a4af3b5c9d05ba048f28996450975b369fc86e5d0ef13b6df0615f892f5396a9c65d616213c25ec5b0ad17ef42eac4a500512a19da6c7 + languageName: node + linkType: hard + +"extend@npm:^3.0.0": + version: 3.0.2 + resolution: "extend@npm:3.0.2" + checksum: 10c0/73bf6e27406e80aa3e85b0d1c4fd987261e628064e170ca781125c0b635a3dabad5e05adbf07595ea0cf1e6c5396cacb214af933da7cbaf24fe75ff14818e8f9 + languageName: node + linkType: hard + +"extract-zip@npm:^2.0.1": + version: 2.0.1 + resolution: "extract-zip@npm:2.0.1" + dependencies: + "@types/yauzl": "npm:^2.9.1" + debug: "npm:^4.1.1" + get-stream: "npm:^5.1.0" + yauzl: "npm:^2.10.0" + dependenciesMeta: + "@types/yauzl": + optional: true + bin: + extract-zip: cli.js + checksum: 10c0/9afbd46854aa15a857ae0341a63a92743a7b89c8779102c3b4ffc207516b2019337353962309f85c66ee3d9092202a83cdc26dbf449a11981272038443974aee + languageName: node + linkType: hard + +"extsprintf@npm:^1.2.0": + version: 1.4.1 + resolution: "extsprintf@npm:1.4.1" + checksum: 10c0/e10e2769985d0e9b6c7199b053a9957589d02e84de42832c295798cb422a025e6d4a92e0259c1fb4d07090f5bfde6b55fd9f880ac5855bd61d775f8ab75a7ab0 + languageName: node + linkType: hard + +"fast-deep-equal@npm:3.1.3, fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": + version: 3.1.3 + resolution: "fast-deep-equal@npm:3.1.3" + checksum: 10c0/40dedc862eb8992c54579c66d914635afbec43350afbbe991235fdcb4e3a8d5af1b23ae7e79bef7d4882d0ecee06c3197488026998fb19f72dc95acff1d1b1d0 + languageName: node + linkType: hard + +"fast-diff@npm:^1.1.2": + version: 1.3.0 + resolution: "fast-diff@npm:1.3.0" + checksum: 10c0/5c19af237edb5d5effda008c891a18a585f74bf12953be57923f17a3a4d0979565fc64dbc73b9e20926b9d895f5b690c618cbb969af0cf022e3222471220ad29 + languageName: node + linkType: hard + +"fast-glob@npm:3.2.12": + version: 3.2.12 + resolution: "fast-glob@npm:3.2.12" + dependencies: + "@nodelib/fs.stat": "npm:^2.0.2" + "@nodelib/fs.walk": "npm:^1.2.3" + glob-parent: "npm:^5.1.2" + merge2: "npm:^1.3.0" + micromatch: "npm:^4.0.4" + checksum: 10c0/08604fb8ef6442ce74068bef3c3104382bb1f5ab28cf75e4ee904662778b60ad620e1405e692b7edea598ef445f5d387827a965ba034e1892bf54b1dfde97f26 + languageName: node + linkType: hard + +"fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.0, fast-glob@npm:^3.3.2, fast-glob@npm:^3.3.3": + version: 3.3.3 + resolution: "fast-glob@npm:3.3.3" + dependencies: + "@nodelib/fs.stat": "npm:^2.0.2" + "@nodelib/fs.walk": "npm:^1.2.3" + glob-parent: "npm:^5.1.2" + merge2: "npm:^1.3.0" + micromatch: "npm:^4.0.8" + checksum: 10c0/f6aaa141d0d3384cf73cbcdfc52f475ed293f6d5b65bfc5def368b09163a9f7e5ec2b3014d80f733c405f58e470ee0cc451c2937685045cddcdeaa24199c43fe + languageName: node + linkType: hard + +"fast-json-stable-stringify@npm:^2.0.0, fast-json-stable-stringify@npm:^2.1.0": + version: 2.1.0 + resolution: "fast-json-stable-stringify@npm:2.1.0" + checksum: 10c0/7f081eb0b8a64e0057b3bb03f974b3ef00135fbf36c1c710895cd9300f13c94ba809bb3a81cf4e1b03f6e5285610a61abbd7602d0652de423144dfee5a389c9b + languageName: node + linkType: hard + +"fast-levenshtein@npm:^2.0.6": + version: 2.0.6 + resolution: "fast-levenshtein@npm:2.0.6" + checksum: 10c0/111972b37338bcb88f7d9e2c5907862c280ebf4234433b95bc611e518d192ccb2d38119c4ac86e26b668d75f7f3894f4ff5c4982899afced7ca78633b08287c4 + languageName: node + linkType: hard + +"fast-redact@npm:^3.0.0": + version: 3.5.0 + resolution: "fast-redact@npm:3.5.0" + checksum: 10c0/7e2ce4aad6e7535e0775bf12bd3e4f2e53d8051d8b630e0fa9e67f68cb0b0e6070d2f7a94b1d0522ef07e32f7c7cda5755e2b677a6538f1e9070ca053c42343a + languageName: node + linkType: hard + +"fast-uri@npm:^3.0.1": + version: 3.1.0 + resolution: "fast-uri@npm:3.1.0" + checksum: 10c0/44364adca566f70f40d1e9b772c923138d47efeac2ae9732a872baafd77061f26b097ba2f68f0892885ad177becd065520412b8ffeec34b16c99433c5b9e2de7 + languageName: node + linkType: hard + +"fastest-levenshtein@npm:^1.0.16": + version: 1.0.16 + resolution: "fastest-levenshtein@npm:1.0.16" + checksum: 10c0/7e3d8ae812a7f4fdf8cad18e9cde436a39addf266a5986f653ea0d81e0de0900f50c0f27c6d5aff3f686bcb48acbd45be115ae2216f36a6a13a7dbbf5cad878b + languageName: node + linkType: hard + +"fastq@npm:^1.6.0": + version: 1.19.1 + resolution: "fastq@npm:1.19.1" + dependencies: + reusify: "npm:^1.0.4" + checksum: 10c0/ebc6e50ac7048daaeb8e64522a1ea7a26e92b3cee5cd1c7f2316cdca81ba543aa40a136b53891446ea5c3a67ec215fbaca87ad405f102dd97012f62916905630 + languageName: node + linkType: hard + +"fb-watchman@npm:^2.0.0": + version: 2.0.2 + resolution: "fb-watchman@npm:2.0.2" + dependencies: + bser: "npm:2.1.1" + checksum: 10c0/feae89ac148adb8f6ae8ccd87632e62b13563e6fb114cacb5265c51f585b17e2e268084519fb2edd133872f1d47a18e6bfd7e5e08625c0d41b93149694187581 + languageName: node + linkType: hard + +"fd-slicer@npm:~1.1.0": + version: 1.1.0 + resolution: "fd-slicer@npm:1.1.0" + dependencies: + pend: "npm:~1.2.0" + checksum: 10c0/304dd70270298e3ffe3bcc05e6f7ade2511acc278bc52d025f8918b48b6aa3b77f10361bddfadfe2a28163f7af7adbdce96f4d22c31b2f648ba2901f0c5fc20e + languageName: node + linkType: hard + +"fdir@npm:^6.5.0": + version: 6.5.0 + resolution: "fdir@npm:6.5.0" + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + checksum: 10c0/e345083c4306b3aed6cb8ec551e26c36bab5c511e99ea4576a16750ddc8d3240e63826cc624f5ae17ad4dc82e68a253213b60d556c11bfad064b7607847ed07f + languageName: node + linkType: hard + +"fetch-blob@npm:^3.1.2, fetch-blob@npm:^3.1.4": + version: 3.2.0 + resolution: "fetch-blob@npm:3.2.0" + dependencies: + node-domexception: "npm:^1.0.0" + web-streams-polyfill: "npm:^3.0.3" + checksum: 10c0/60054bf47bfa10fb0ba6cb7742acec2f37c1f56344f79a70bb8b1c48d77675927c720ff3191fa546410a0442c998d27ab05e9144c32d530d8a52fbe68f843b69 + languageName: node + linkType: hard + +"file-entry-cache@npm:^11.1.2": + version: 11.1.2 + resolution: "file-entry-cache@npm:11.1.2" + dependencies: + flat-cache: "npm:^6.1.20" + checksum: 10c0/14a251661750b783236d8e2fdf98da642b0069d6bd2b512caed36ee6a6d719b06493f15fcdda5ec32a61770d5eba6ac885b4ff4a64e57f3cc2a33d99aebabd08 + languageName: node + linkType: hard + +"file-entry-cache@npm:^6.0.1": + version: 6.0.1 + resolution: "file-entry-cache@npm:6.0.1" + dependencies: + flat-cache: "npm:^3.0.4" + checksum: 10c0/58473e8a82794d01b38e5e435f6feaf648e3f36fdb3a56e98f417f4efae71ad1c0d4ebd8a9a7c50c3ad085820a93fc7494ad721e0e4ebc1da3573f4e1c3c7cdd + languageName: node + linkType: hard + +"filelist@npm:^1.0.4": + version: 1.0.4 + resolution: "filelist@npm:1.0.4" + dependencies: + minimatch: "npm:^5.0.1" + checksum: 10c0/426b1de3944a3d153b053f1c0ebfd02dccd0308a4f9e832ad220707a6d1f1b3c9784d6cadf6b2f68f09a57565f63ebc7bcdc913ccf8012d834f472c46e596f41 + languageName: node + linkType: hard + +"fill-range@npm:^7.1.1": + version: 7.1.1 + resolution: "fill-range@npm:7.1.1" + dependencies: + to-regex-range: "npm:^5.0.1" + checksum: 10c0/b75b691bbe065472f38824f694c2f7449d7f5004aa950426a2c28f0306c60db9b880c0b0e4ed819997ffb882d1da02cfcfc819bddc94d71627f5269682edf018 + languageName: node + linkType: hard + +"filter-obj@npm:^1.1.0": + version: 1.1.0 + resolution: "filter-obj@npm:1.1.0" + checksum: 10c0/071e0886b2b50238ca5026c5bbf58c26a7c1a1f720773b8c7813d16ba93d0200de977af14ac143c5ac18f666b2cfc83073f3a5fe6a4e996c49e0863d5500fccf + languageName: node + linkType: hard + +"finalhandler@npm:1.3.1": + version: 1.3.1 + resolution: "finalhandler@npm:1.3.1" + dependencies: + debug: "npm:2.6.9" + encodeurl: "npm:~2.0.0" + escape-html: "npm:~1.0.3" + on-finished: "npm:2.4.1" + parseurl: "npm:~1.3.3" + statuses: "npm:2.0.1" + unpipe: "npm:~1.0.0" + checksum: 10c0/d38035831865a49b5610206a3a9a9aae4e8523cbbcd01175d0480ffbf1278c47f11d89be3ca7f617ae6d94f29cf797546a4619cd84dd109009ef33f12f69019f + languageName: node + linkType: hard + +"find-root@npm:^1.1.0": + version: 1.1.0 + resolution: "find-root@npm:1.1.0" + checksum: 10c0/1abc7f3bf2f8d78ff26d9e00ce9d0f7b32e5ff6d1da2857bcdf4746134c422282b091c672cde0572cac3840713487e0a7a636af9aa1b74cb11894b447a521efa + languageName: node + linkType: hard + +"find-up@npm:4.1.0, find-up@npm:^4.1.0": + version: 4.1.0 + resolution: "find-up@npm:4.1.0" + dependencies: + locate-path: "npm:^5.0.0" + path-exists: "npm:^4.0.0" + checksum: 10c0/0406ee89ebeefa2d507feb07ec366bebd8a6167ae74aa4e34fb4c4abd06cf782a3ce26ae4194d70706f72182841733f00551c209fe575cb00bd92104056e78c1 + languageName: node + linkType: hard + +"find-up@npm:^5.0.0": + version: 5.0.0 + resolution: "find-up@npm:5.0.0" + dependencies: + locate-path: "npm:^6.0.0" + path-exists: "npm:^4.0.0" + checksum: 10c0/062c5a83a9c02f53cdd6d175a37ecf8f87ea5bbff1fdfb828f04bfa021441bc7583e8ebc0872a4c1baab96221fb8a8a275a19809fb93fbc40bd69ec35634069a + languageName: node + linkType: hard + +"flat-cache@npm:^3.0.4": + version: 3.2.0 + resolution: "flat-cache@npm:3.2.0" + dependencies: + flatted: "npm:^3.2.9" + keyv: "npm:^4.5.3" + rimraf: "npm:^3.0.2" + checksum: 10c0/b76f611bd5f5d68f7ae632e3ae503e678d205cf97a17c6ab5b12f6ca61188b5f1f7464503efae6dc18683ed8f0b41460beb48ac4b9ac63fe6201296a91ba2f75 + languageName: node + linkType: hard + +"flat-cache@npm:^6.1.20": + version: 6.1.22 + resolution: "flat-cache@npm:6.1.22" + dependencies: + cacheable: "npm:^2.3.4" + flatted: "npm:^3.4.2" + hookified: "npm:^1.15.0" + checksum: 10c0/ec94fba4ecb10b43567bb815f19e178d4351a66a58117b06a06c81bda6b579c2ed75d8cbd9ea90a2ab9408493b564ffef55386f263f20d1d73bb991fa97de67f + languageName: node + linkType: hard + +"flatted@npm:^3.2.9": + version: 3.3.3 + resolution: "flatted@npm:3.3.3" + checksum: 10c0/e957a1c6b0254aa15b8cce8533e24165abd98fadc98575db082b786b5da1b7d72062b81bfdcd1da2f4d46b6ed93bec2434e62333e9b4261d79ef2e75a10dd538 + languageName: node + linkType: hard + +"flatted@npm:^3.4.2": + version: 3.4.2 + resolution: "flatted@npm:3.4.2" + checksum: 10c0/a65b67aae7172d6cdf63691be7de6c5cd5adbdfdfe2e9da1a09b617c9512ed794037741ee53d93114276bff3f93cd3b0d97d54f9b316e1e4885dde6e9ffdf7ed + languageName: node + linkType: hard + +"flatten@npm:^1.0.2": + version: 1.0.3 + resolution: "flatten@npm:1.0.3" + checksum: 10c0/9f9b1f3dcd05be057bb83ec27f2513da5306e7bfc0cf8bd839ab423eb1b0f99683a25c97b48fafd5959819159659ce9f1397623a46f89a8577ba095fcf5fb753 + languageName: node + linkType: hard + +"follow-redirects@npm:^1.14.9": + version: 1.15.11 + resolution: "follow-redirects@npm:1.15.11" + peerDependenciesMeta: + debug: + optional: true + checksum: 10c0/d301f430542520a54058d4aeeb453233c564aaccac835d29d15e050beb33f339ad67d9bddbce01739c5dc46a6716dbe3d9d0d5134b1ca203effa11a7ef092343 + languageName: node + linkType: hard + +"for-each@npm:^0.3.3, for-each@npm:^0.3.5": + version: 0.3.5 + resolution: "for-each@npm:0.3.5" + dependencies: + is-callable: "npm:^1.2.7" + checksum: 10c0/0e0b50f6a843a282637d43674d1fb278dda1dd85f4f99b640024cfb10b85058aac0cc781bf689d5fe50b4b7f638e91e548560723a4e76e04fe96ae35ef039cee + languageName: node + linkType: hard + +"foreground-child@npm:^3.1.0": + version: 3.3.1 + resolution: "foreground-child@npm:3.3.1" + dependencies: + cross-spawn: "npm:^7.0.6" + signal-exit: "npm:^4.0.1" + checksum: 10c0/8986e4af2430896e65bc2788d6679067294d6aee9545daefc84923a0a4b399ad9c7a3ea7bd8c0b2b80fdf4a92de4c69df3f628233ff3224260e9c1541a9e9ed3 + languageName: node + linkType: hard + +"fork-ts-checker-webpack-plugin@npm:8.0.0": + version: 8.0.0 + resolution: "fork-ts-checker-webpack-plugin@npm:8.0.0" + dependencies: + "@babel/code-frame": "npm:^7.16.7" + chalk: "npm:^4.1.2" + chokidar: "npm:^3.5.3" + cosmiconfig: "npm:^7.0.1" + deepmerge: "npm:^4.2.2" + fs-extra: "npm:^10.0.0" + memfs: "npm:^3.4.1" + minimatch: "npm:^3.0.4" + node-abort-controller: "npm:^3.0.1" + schema-utils: "npm:^3.1.1" + semver: "npm:^7.3.5" + tapable: "npm:^2.2.1" + peerDependencies: + typescript: ">3.6.0" + webpack: ^5.11.0 + checksum: 10c0/1a2bb9bbd3e943e3b3a45d7fa9e8383698f5fea1ba28f7d18c8372c804460c2f13af53f791360b973fddafd3e88de7af59082c3cb3375f4e7c3365cd85accedc + languageName: node + linkType: hard + +"form-data@npm:^4.0.0": + version: 4.0.5 + resolution: "form-data@npm:4.0.5" + dependencies: + asynckit: "npm:^0.4.0" + combined-stream: "npm:^1.0.8" + es-set-tostringtag: "npm:^2.1.0" + hasown: "npm:^2.0.2" + mime-types: "npm:^2.1.12" + checksum: 10c0/dd6b767ee0bbd6d84039db12a0fa5a2028160ffbfaba1800695713b46ae974a5f6e08b3356c3195137f8530dcd9dfcb5d5ae1eeff53d0db1e5aad863b619ce3b + languageName: node + linkType: hard + +"formdata-polyfill@npm:^4.0.10": + version: 4.0.10 + resolution: "formdata-polyfill@npm:4.0.10" + dependencies: + fetch-blob: "npm:^3.1.2" + checksum: 10c0/5392ec484f9ce0d5e0d52fb5a78e7486637d516179b0eb84d81389d7eccf9ca2f663079da56f761355c0a65792810e3b345dc24db9a8bbbcf24ef3c8c88570c6 + languageName: node + linkType: hard + +"forwarded@npm:0.2.0": + version: 0.2.0 + resolution: "forwarded@npm:0.2.0" + checksum: 10c0/9b67c3fac86acdbc9ae47ba1ddd5f2f81526fa4c8226863ede5600a3f7c7416ef451f6f1e240a3cc32d0fd79fcfe6beb08fd0da454f360032bde70bf80afbb33 + languageName: node + linkType: hard + +"fraction.js@npm:^5.3.4": + version: 5.3.4 + resolution: "fraction.js@npm:5.3.4" + checksum: 10c0/f90079fe9bfc665e0a07079938e8ff71115bce9462f17b32fc283f163b0540ec34dc33df8ed41bb56f028316b04361b9a9995b9ee9258617f8338e0b05c5f95a + languageName: node + linkType: hard + +"fresh@npm:0.5.2": + version: 0.5.2 + resolution: "fresh@npm:0.5.2" + checksum: 10c0/c6d27f3ed86cc5b601404822f31c900dd165ba63fff8152a3ef714e2012e7535027063bc67ded4cb5b3a49fa596495d46cacd9f47d6328459cf570f08b7d9e5a + languageName: node + linkType: hard + +"fs-extra@npm:^10.0.0, fs-extra@npm:^10.1.0": + version: 10.1.0 + resolution: "fs-extra@npm:10.1.0" + dependencies: + graceful-fs: "npm:^4.2.0" + jsonfile: "npm:^6.0.1" + universalify: "npm:^2.0.0" + checksum: 10c0/5f579466e7109719d162a9249abbeffe7f426eb133ea486e020b89bc6d67a741134076bf439983f2eb79276ceaf6bd7b7c1e43c3fd67fe889863e69072fb0a5e + languageName: node + linkType: hard + +"fs-extra@npm:^8.1.0": + version: 8.1.0 + resolution: "fs-extra@npm:8.1.0" + dependencies: + graceful-fs: "npm:^4.2.0" + jsonfile: "npm:^4.0.0" + universalify: "npm:^0.1.0" + checksum: 10c0/259f7b814d9e50d686899550c4f9ded85c46c643f7fe19be69504888e007fcbc08f306fae8ec495b8b998635e997c9e3e175ff2eeed230524ef1c1684cc96423 + languageName: node + linkType: hard + +"fs-extra@npm:^9.0.0, fs-extra@npm:^9.0.1": + version: 9.1.0 + resolution: "fs-extra@npm:9.1.0" + dependencies: + at-least-node: "npm:^1.0.0" + graceful-fs: "npm:^4.2.0" + jsonfile: "npm:^6.0.1" + universalify: "npm:^2.0.0" + checksum: 10c0/9b808bd884beff5cb940773018179a6b94a966381d005479f00adda6b44e5e3d4abf765135773d849cc27efe68c349e4a7b86acd7d3306d5932c14f3a4b17a92 + languageName: node + linkType: hard + +"fs-minipass@npm:^2.0.0": + version: 2.1.0 + resolution: "fs-minipass@npm:2.1.0" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/703d16522b8282d7299337539c3ed6edddd1afe82435e4f5b76e34a79cd74e488a8a0e26a636afc2440e1a23b03878e2122e3a2cfe375a5cf63c37d92b86a004 + languageName: node + linkType: hard + +"fs-minipass@npm:^3.0.0": + version: 3.0.3 + resolution: "fs-minipass@npm:3.0.3" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/63e80da2ff9b621e2cb1596abcb9207f1cf82b968b116ccd7b959e3323144cce7fb141462200971c38bbf2ecca51695069db45265705bed09a7cd93ae5b89f94 + languageName: node + linkType: hard + +"fs-monkey@npm:^1.0.4": + version: 1.1.0 + resolution: "fs-monkey@npm:1.1.0" + checksum: 10c0/45596fe14753ae8f3fa180724106383de68c8de2836eb24d1647cacf18a6d05335402f3611d32e00234072a60d2f3371024c00cd295593bfbce35b84ff9f6a34 + languageName: node + linkType: hard + +"fs.realpath@npm:^1.0.0": + version: 1.0.0 + resolution: "fs.realpath@npm:1.0.0" + checksum: 10c0/444cf1291d997165dfd4c0d58b69f0e4782bfd9149fd72faa4fe299e68e0e93d6db941660b37dd29153bf7186672ececa3b50b7e7249477b03fdf850f287c948 + languageName: node + linkType: hard + +"fsevents@npm:^2.3.2, fsevents@npm:~2.3.2": + version: 2.3.3 + resolution: "fsevents@npm:2.3.3" + dependencies: + node-gyp: "npm:latest" + checksum: 10c0/a1f0c44595123ed717febbc478aa952e47adfc28e2092be66b8ab1635147254ca6cfe1df792a8997f22716d4cbafc73309899ff7bfac2ac3ad8cf2e4ecc3ec60 + conditions: os=darwin + languageName: node + linkType: hard + +"fsevents@patch:fsevents@npm%3A^2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin": + version: 2.3.3 + resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" + dependencies: + node-gyp: "npm:latest" + conditions: os=darwin + languageName: node + linkType: hard + +"function-bind@npm:^1.1.2": + version: 1.1.2 + resolution: "function-bind@npm:1.1.2" + checksum: 10c0/d8680ee1e5fcd4c197e4ac33b2b4dce03c71f4d91717292785703db200f5c21f977c568d28061226f9b5900cbcd2c84463646134fd5337e7925e0942bc3f46d5 + languageName: node + linkType: hard + +"function.prototype.name@npm:^1.1.6, function.prototype.name@npm:^1.1.8": + version: 1.1.8 + resolution: "function.prototype.name@npm:1.1.8" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.3" + define-properties: "npm:^1.2.1" + functions-have-names: "npm:^1.2.3" + hasown: "npm:^2.0.2" + is-callable: "npm:^1.2.7" + checksum: 10c0/e920a2ab52663005f3cbe7ee3373e3c71c1fb5558b0b0548648cdf3e51961085032458e26c71ff1a8c8c20e7ee7caeb03d43a5d1fa8610c459333323a2e71253 + languageName: node + linkType: hard + +"functions-have-names@npm:^1.2.3": + version: 1.2.3 + resolution: "functions-have-names@npm:1.2.3" + checksum: 10c0/33e77fd29bddc2d9bb78ab3eb854c165909201f88c75faa8272e35899e2d35a8a642a15e7420ef945e1f64a9670d6aa3ec744106b2aa42be68ca5114025954ca + languageName: node + linkType: hard + +"generator-function@npm:^2.0.0": + version: 2.0.1 + resolution: "generator-function@npm:2.0.1" + checksum: 10c0/8a9f59df0f01cfefafdb3b451b80555e5cf6d76487095db91ac461a0e682e4ff7a9dbce15f4ecec191e53586d59eece01949e05a4b4492879600bbbe8e28d6b8 + languageName: node + linkType: hard + +"gensync@npm:^1.0.0-beta.2": + version: 1.0.0-beta.2 + resolution: "gensync@npm:1.0.0-beta.2" + checksum: 10c0/782aba6cba65b1bb5af3b095d96249d20edbe8df32dbf4696fd49be2583faf676173bf4809386588828e4dd76a3354fcbeb577bab1c833ccd9fc4577f26103f8 + languageName: node + linkType: hard + +"get-caller-file@npm:^2.0.5": + version: 2.0.5 + resolution: "get-caller-file@npm:2.0.5" + checksum: 10c0/c6c7b60271931fa752aeb92f2b47e355eac1af3a2673f47c9589e8f8a41adc74d45551c1bc57b5e66a80609f10ffb72b6f575e4370d61cc3f7f3aaff01757cde + languageName: node + linkType: hard + +"get-east-asian-width@npm:^1.5.0": + version: 1.5.0 + resolution: "get-east-asian-width@npm:1.5.0" + checksum: 10c0/bff8bbc8d81790b9477f7aa55b1806b9f082a8dc1359fff7bd8b96939622c86b729685afc2bfeb22def1fc6ef1e5228e4d87dd4e6da60bc43a5edfb03c4ee167 + languageName: node + linkType: hard + +"get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6, get-intrinsic@npm:^1.2.7, get-intrinsic@npm:^1.3.0": + version: 1.3.1 + resolution: "get-intrinsic@npm:1.3.1" + dependencies: + async-function: "npm:^1.0.0" + async-generator-function: "npm:^1.0.0" + call-bind-apply-helpers: "npm:^1.0.2" + es-define-property: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.1.1" + function-bind: "npm:^1.1.2" + generator-function: "npm:^2.0.0" + get-proto: "npm:^1.0.1" + gopd: "npm:^1.2.0" + has-symbols: "npm:^1.1.0" + hasown: "npm:^2.0.2" + math-intrinsics: "npm:^1.1.0" + checksum: 10c0/9f4ab0cf7efe0fd2c8185f52e6f637e708f3a112610c88869f8f041bb9ecc2ce44bf285dfdbdc6f4f7c277a5b88d8e94a432374d97cca22f3de7fc63795deb5d + languageName: node + linkType: hard + +"get-package-type@npm:^0.1.0": + version: 0.1.0 + resolution: "get-package-type@npm:0.1.0" + checksum: 10c0/e34cdf447fdf1902a1f6d5af737eaadf606d2ee3518287abde8910e04159368c268568174b2e71102b87b26c2020486f126bfca9c4fb1ceb986ff99b52ecd1be + languageName: node + linkType: hard + +"get-proto@npm:^1.0.0, get-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "get-proto@npm:1.0.1" + dependencies: + dunder-proto: "npm:^1.0.1" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/9224acb44603c5526955e83510b9da41baf6ae73f7398875fba50edc5e944223a89c4a72b070fcd78beb5f7bdda58ecb6294adc28f7acfc0da05f76a2399643c + languageName: node + linkType: hard + +"get-stdin@npm:=8.0.0": + version: 8.0.0 + resolution: "get-stdin@npm:8.0.0" + checksum: 10c0/b71b72b83928221052f713b3b6247ebf1ceaeb4ef76937778557537fd51ad3f586c9e6a7476865022d9394b39b74eed1dc7514052fa74d80625276253571b76f + languageName: node + linkType: hard + +"get-stream@npm:^5.1.0": + version: 5.2.0 + resolution: "get-stream@npm:5.2.0" + dependencies: + pump: "npm:^3.0.0" + checksum: 10c0/43797ffd815fbb26685bf188c8cfebecb8af87b3925091dd7b9a9c915993293d78e3c9e1bce125928ff92f2d0796f3889b92b5ec6d58d1041b574682132e0a80 + languageName: node + linkType: hard + +"get-stream@npm:^6.0.0, get-stream@npm:^6.0.1": + version: 6.0.1 + resolution: "get-stream@npm:6.0.1" + checksum: 10c0/49825d57d3fd6964228e6200a58169464b8e8970489b3acdc24906c782fb7f01f9f56f8e6653c4a50713771d6658f7cfe051e5eb8c12e334138c9c918b296341 + languageName: node + linkType: hard + +"get-symbol-description@npm:^1.1.0": + version: 1.1.0 + resolution: "get-symbol-description@npm:1.1.0" + dependencies: + call-bound: "npm:^1.0.3" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.6" + checksum: 10c0/d6a7d6afca375779a4b307738c9e80dbf7afc0bdbe5948768d54ab9653c865523d8920e670991a925936eb524b7cb6a6361d199a760b21d0ca7620194455aa4b + languageName: node + linkType: hard + +"get-tsconfig@npm:4.7.5": + version: 4.7.5 + resolution: "get-tsconfig@npm:4.7.5" + dependencies: + resolve-pkg-maps: "npm:^1.0.0" + checksum: 10c0/a917dff2ba9ee187c41945736bf9bbab65de31ce5bc1effd76267be483a7340915cff232199406379f26517d2d0a4edcdbcda8cca599c2480a0f2cf1e1de3efa + languageName: node + linkType: hard + +"get-tsconfig@npm:^4.7.0": + version: 4.13.0 + resolution: "get-tsconfig@npm:4.13.0" + dependencies: + resolve-pkg-maps: "npm:^1.0.0" + checksum: 10c0/2c49ef8d3907047a107f229fd610386fe3b7fe9e42dfd6b42e7406499493cdda8c62e83e57e8d7a98125610774b9f604d3a0ff308d7f9de5c7ac6d1b07cb6036 + languageName: node + linkType: hard + +"git-hooks-list@npm:^3.0.0": + version: 3.2.0 + resolution: "git-hooks-list@npm:3.2.0" + checksum: 10c0/6fdbc727da8e5a6fd9be47b40dd896db3a5c38196a3a52d2f0ed66fe28a6e0df50128b6e674d52b04fa5932a395b693441da9c0cfa7df16f1eff83aee042b127 + languageName: node + linkType: hard + +"git-hooks-list@npm:^4.0.0": + version: 4.1.1 + resolution: "git-hooks-list@npm:4.1.1" + checksum: 10c0/74d87b1ed457214599566032e3bb79d75ec1605729e83fa6182b889900dd94fc14aafe7b8c66b40562ab9fdeea0e0d8035c23a64d8eb9d3917d1f1d6c06c8e4d + languageName: node + linkType: hard + +"glob-parent@npm:^5.1.2, glob-parent@npm:~5.1.2": + version: 5.1.2 + resolution: "glob-parent@npm:5.1.2" + dependencies: + is-glob: "npm:^4.0.1" + checksum: 10c0/cab87638e2112bee3f839ef5f6e0765057163d39c66be8ec1602f3823da4692297ad4e972de876ea17c44d652978638d2fd583c6713d0eb6591706825020c9ee + languageName: node + linkType: hard + +"glob-parent@npm:^6.0.2": + version: 6.0.2 + resolution: "glob-parent@npm:6.0.2" + dependencies: + is-glob: "npm:^4.0.3" + checksum: 10c0/317034d88654730230b3f43bb7ad4f7c90257a426e872ea0bf157473ac61c99bf5d205fad8f0185f989be8d2fa6d3c7dce1645d99d545b6ea9089c39f838e7f8 + languageName: node + linkType: hard + +"glob@npm:^10.2.5": + version: 10.5.0 + resolution: "glob@npm:10.5.0" + dependencies: + foreground-child: "npm:^3.1.0" + jackspeak: "npm:^3.1.2" + minimatch: "npm:^9.0.4" + minipass: "npm:^7.1.2" + package-json-from-dist: "npm:^1.0.0" + path-scurry: "npm:^1.11.1" + bin: + glob: dist/esm/bin.mjs + checksum: 10c0/100705eddbde6323e7b35e1d1ac28bcb58322095bd8e63a7d0bef1a2cdafe0d0f7922a981b2b48369a4f8c1b077be5c171804534c3509dfe950dde15fbe6d828 + languageName: node + linkType: hard + +"glob@npm:^13.0.0": + version: 13.0.0 + resolution: "glob@npm:13.0.0" + dependencies: + minimatch: "npm:^10.1.1" + minipass: "npm:^7.1.2" + path-scurry: "npm:^2.0.0" + checksum: 10c0/8e2f5821f3f7c312dd102e23a15b80c79e0837a9872784293ba2e15ec73b3f3749a49a42a31bfcb4e52c84820a474e92331c2eebf18819d20308f5c33876630a + languageName: node + linkType: hard + +"glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.1.6": + version: 7.2.3 + resolution: "glob@npm:7.2.3" + dependencies: + fs.realpath: "npm:^1.0.0" + inflight: "npm:^1.0.4" + inherits: "npm:2" + minimatch: "npm:^3.1.1" + once: "npm:^1.3.0" + path-is-absolute: "npm:^1.0.0" + checksum: 10c0/65676153e2b0c9095100fe7f25a778bf45608eeb32c6048cf307f579649bcc30353277b3b898a3792602c65764e5baa4f643714dfbdfd64ea271d210c7a425fe + languageName: node + linkType: hard + +"global-agent@npm:^3.0.0": + version: 3.0.0 + resolution: "global-agent@npm:3.0.0" + dependencies: + boolean: "npm:^3.0.1" + es6-error: "npm:^4.1.1" + matcher: "npm:^3.0.0" + roarr: "npm:^2.15.3" + semver: "npm:^7.3.2" + serialize-error: "npm:^7.0.1" + checksum: 10c0/bb8750d026b25da437072762fd739098bad92ff72f66483c3929db4579e072f5523960f7e7fd70ee0d75db48898067b5dc1c9c1d17888128cff008fcc34d1bd3 + languageName: node + linkType: hard + +"global-modules@npm:^2.0.0": + version: 2.0.0 + resolution: "global-modules@npm:2.0.0" + dependencies: + global-prefix: "npm:^3.0.0" + checksum: 10c0/43b770fe24aa6028f4b9770ea583a47f39750be15cf6e2578f851e4ccc9e4fa674b8541928c0b09c21461ca0763f0d36e4068cec86c914b07fd6e388e66ba5b9 + languageName: node + linkType: hard + +"global-prefix@npm:^3.0.0": + version: 3.0.0 + resolution: "global-prefix@npm:3.0.0" + dependencies: + ini: "npm:^1.3.5" + kind-of: "npm:^6.0.2" + which: "npm:^1.3.1" + checksum: 10c0/510f489fb68d1cc7060f276541709a0ee6d41356ef852de48f7906c648ac223082a1cc8fce86725ca6c0e032bcdc1189ae77b4744a624b29c34a9d0ece498269 + languageName: node + linkType: hard + +"global@npm:^4.3.2": + version: 4.4.0 + resolution: "global@npm:4.4.0" + dependencies: + min-document: "npm:^2.19.0" + process: "npm:^0.11.10" + checksum: 10c0/4a467aec6602c00a7c5685f310574ab04e289ad7f894f0f01c9c5763562b82f4b92d1e381ce6c5bbb12173e2a9f759c1b63dda6370cfb199970267e14d90aa91 + languageName: node + linkType: hard + +"globals@npm:^13.19.0": + version: 13.24.0 + resolution: "globals@npm:13.24.0" + dependencies: + type-fest: "npm:^0.20.2" + checksum: 10c0/d3c11aeea898eb83d5ec7a99508600fbe8f83d2cf00cbb77f873dbf2bcb39428eff1b538e4915c993d8a3b3473fa71eeebfe22c9bb3a3003d1e26b1f2c8a42cd + languageName: node + linkType: hard + +"globalthis@npm:^1.0.1, globalthis@npm:^1.0.4": + version: 1.0.4 + resolution: "globalthis@npm:1.0.4" + dependencies: + define-properties: "npm:^1.2.1" + gopd: "npm:^1.0.1" + checksum: 10c0/9d156f313af79d80b1566b93e19285f481c591ad6d0d319b4be5e03750d004dde40a39a0f26f7e635f9007a3600802f53ecd85a759b86f109e80a5f705e01846 + languageName: node + linkType: hard + +"globby@npm:^11.1.0": + version: 11.1.0 + resolution: "globby@npm:11.1.0" + dependencies: + array-union: "npm:^2.1.0" + dir-glob: "npm:^3.0.1" + fast-glob: "npm:^3.2.9" + ignore: "npm:^5.2.0" + merge2: "npm:^1.4.1" + slash: "npm:^3.0.0" + checksum: 10c0/b39511b4afe4bd8a7aead3a27c4ade2b9968649abab0a6c28b1a90141b96ca68ca5db1302f7c7bd29eab66bf51e13916b8e0a3d0ac08f75e1e84a39b35691189 + languageName: node + linkType: hard + +"globby@npm:^13.1.2": + version: 13.2.2 + resolution: "globby@npm:13.2.2" + dependencies: + dir-glob: "npm:^3.0.1" + fast-glob: "npm:^3.3.0" + ignore: "npm:^5.2.4" + merge2: "npm:^1.4.1" + slash: "npm:^4.0.0" + checksum: 10c0/a8d7cc7cbe5e1b2d0f81d467bbc5bc2eac35f74eaded3a6c85fc26d7acc8e6de22d396159db8a2fc340b8a342e74cac58de8f4aee74146d3d146921a76062664 + languageName: node + linkType: hard + +"globby@npm:^16.2.0": + version: 16.2.0 + resolution: "globby@npm:16.2.0" + dependencies: + "@sindresorhus/merge-streams": "npm:^4.0.0" + fast-glob: "npm:^3.3.3" + ignore: "npm:^7.0.5" + is-path-inside: "npm:^4.0.0" + slash: "npm:^5.1.0" + unicorn-magic: "npm:^0.4.0" + checksum: 10c0/fc0675e01dc1da5095f30dccc46a3047fc38d45ca08c21c1aa871bd79d38682f507d84a159be168019db5fffaa09c5663c3679c29190a2d4f999dc91d7ff6406 + languageName: node + linkType: hard + +"globjoin@npm:^0.1.4": + version: 0.1.4 + resolution: "globjoin@npm:0.1.4" + checksum: 10c0/236e991b48f1a9869fe2aa7bb5141fb1f32973940567a3c012f8ccb58c3c85ab78ce594d374fa819410fff3b48cfd24584d7ef726939f8a3c3772890e62ea16b + languageName: node + linkType: hard + +"gopd@npm:^1.0.1, gopd@npm:^1.2.0": + version: 1.2.0 + resolution: "gopd@npm:1.2.0" + checksum: 10c0/50fff1e04ba2b7737c097358534eacadad1e68d24cccee3272e04e007bed008e68d2614f3987788428fd192a5ae3889d08fb2331417e4fc4a9ab366b2043cead + languageName: node + linkType: hard + +"got@npm:^11.8.5": + version: 11.8.6 + resolution: "got@npm:11.8.6" + dependencies: + "@sindresorhus/is": "npm:^4.0.0" + "@szmarczak/http-timer": "npm:^4.0.5" + "@types/cacheable-request": "npm:^6.0.1" + "@types/responselike": "npm:^1.0.0" + cacheable-lookup: "npm:^5.0.3" + cacheable-request: "npm:^7.0.2" + decompress-response: "npm:^6.0.0" + http2-wrapper: "npm:^1.0.0-beta.5.2" + lowercase-keys: "npm:^2.0.0" + p-cancelable: "npm:^2.0.0" + responselike: "npm:^2.0.0" + checksum: 10c0/754dd44877e5cf6183f1e989ff01c648d9a4719e357457bd4c78943911168881f1cfb7b2cb15d885e2105b3ad313adb8f017a67265dd7ade771afdb261ee8cb1 + languageName: node + linkType: hard + +"graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": + version: 4.2.11 + resolution: "graceful-fs@npm:4.2.11" + checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 + languageName: node + linkType: hard + +"graceful-readlink@npm:>= 1.0.0": + version: 1.0.1 + resolution: "graceful-readlink@npm:1.0.1" + checksum: 10c0/c53e703257e77f8a4495ff0d476c09aa413251acd26684f4544771b15e0ad361d1075b8f6d27b52af6942ea58155a9bbdb8125d717c70df27117460fee295a54 + languageName: node + linkType: hard + +"graphemer@npm:^1.4.0": + version: 1.4.0 + resolution: "graphemer@npm:1.4.0" + checksum: 10c0/e951259d8cd2e0d196c72ec711add7115d42eb9a8146c8eeda5b8d3ac91e5dd816b9cd68920726d9fd4490368e7ed86e9c423f40db87e2d8dfafa00fa17c3a31 + languageName: node + linkType: hard + +"handle-thing@npm:^2.0.0": + version: 2.0.1 + resolution: "handle-thing@npm:2.0.1" + checksum: 10c0/7ae34ba286a3434f1993ebd1cc9c9e6b6d8ea672182db28b1afc0a7119229552fa7031e3e5f3cd32a76430ece4e94b7da6f12af2eb39d6239a7693e4bd63a998 + languageName: node + linkType: hard + +"harmony-reflect@npm:^1.4.6": + version: 1.6.2 + resolution: "harmony-reflect@npm:1.6.2" + checksum: 10c0/fa5b251fbeff0e2d925f0bfb5ffe39e0627639e998c453562d6a39e41789c15499649dc022178c807cf99bfb97e7b974bbbc031ba82078a26be7b098b9bc2b1a + languageName: node + linkType: hard + +"has-bigints@npm:^1.0.2": + version: 1.1.0 + resolution: "has-bigints@npm:1.1.0" + checksum: 10c0/2de0cdc4a1ccf7a1e75ffede1876994525ac03cc6f5ae7392d3415dd475cd9eee5bceec63669ab61aa997ff6cceebb50ef75561c7002bed8988de2b9d1b40788 + languageName: node + linkType: hard + +"has-flag@npm:^3.0.0": + version: 3.0.0 + resolution: "has-flag@npm:3.0.0" + checksum: 10c0/1c6c83b14b8b1b3c25b0727b8ba3e3b647f99e9e6e13eb7322107261de07a4c1be56fc0d45678fc376e09772a3a1642ccdaf8fc69bdf123b6c086598397ce473 + languageName: node + linkType: hard + +"has-flag@npm:^4.0.0": + version: 4.0.0 + resolution: "has-flag@npm:4.0.0" + checksum: 10c0/2e789c61b7888d66993e14e8331449e525ef42aac53c627cc53d1c3334e768bcb6abdc4f5f0de1478a25beec6f0bd62c7549058b7ac53e924040d4f301f02fd1 + languageName: node + linkType: hard + +"has-flag@npm:^5.0.1": + version: 5.0.1 + resolution: "has-flag@npm:5.0.1" + checksum: 10c0/6c214902e9d829979ef0f906980599df1db5ca289a9c72cc1b1ebc2c8c924681c60b632a21bfc23728a1098a3f300029a8608f293fcc559962ecd495652aa250 + languageName: node + linkType: hard + +"has-property-descriptors@npm:^1.0.0, has-property-descriptors@npm:^1.0.2": + version: 1.0.2 + resolution: "has-property-descriptors@npm:1.0.2" + dependencies: + es-define-property: "npm:^1.0.0" + checksum: 10c0/253c1f59e80bb476cf0dde8ff5284505d90c3bdb762983c3514d36414290475fe3fd6f574929d84de2a8eec00d35cf07cb6776205ff32efd7c50719125f00236 + languageName: node + linkType: hard + +"has-proto@npm:^1.2.0": + version: 1.2.0 + resolution: "has-proto@npm:1.2.0" + dependencies: + dunder-proto: "npm:^1.0.0" + checksum: 10c0/46538dddab297ec2f43923c3d35237df45d8c55a6fc1067031e04c13ed8a9a8f94954460632fd4da84c31a1721eefee16d901cbb1ae9602bab93bb6e08f93b95 + languageName: node + linkType: hard + +"has-symbols@npm:^1.0.3, has-symbols@npm:^1.1.0": + version: 1.1.0 + resolution: "has-symbols@npm:1.1.0" + checksum: 10c0/dde0a734b17ae51e84b10986e651c664379018d10b91b6b0e9b293eddb32f0f069688c841fb40f19e9611546130153e0a2a48fd7f512891fb000ddfa36f5a20e + languageName: node + linkType: hard + +"has-tostringtag@npm:^1.0.2": + version: 1.0.2 + resolution: "has-tostringtag@npm:1.0.2" + dependencies: + has-symbols: "npm:^1.0.3" + checksum: 10c0/a8b166462192bafe3d9b6e420a1d581d93dd867adb61be223a17a8d6dad147aa77a8be32c961bb2f27b3ef893cae8d36f564ab651f5e9b7938ae86f74027c48c + languageName: node + linkType: hard + +"hash-base@npm:^3.0.0, hash-base@npm:^3.1.2": + version: 3.1.2 + resolution: "hash-base@npm:3.1.2" + dependencies: + inherits: "npm:^2.0.4" + readable-stream: "npm:^2.3.8" + safe-buffer: "npm:^5.2.1" + to-buffer: "npm:^1.2.1" + checksum: 10c0/f3b7fae1853b31340048dd659f40f5260ca6f3ff53b932f807f4ab701ee09039f6e9dbe1841723ff61e20f3f69d6387a352e4ccc5f997dedb0d375c7d88bc15e + languageName: node + linkType: hard + +"hash-base@npm:~3.0.4": + version: 3.0.5 + resolution: "hash-base@npm:3.0.5" + dependencies: + inherits: "npm:^2.0.4" + safe-buffer: "npm:^5.2.1" + checksum: 10c0/6dc185b79bad9b6d525cd132a588e4215380fdc36fec6f7a8a58c5db8e3b642557d02ad9c367f5e476c7c3ad3ccffa3607f308b124e1ed80e3b80a1b254db61e + languageName: node + linkType: hard + +"hash.js@npm:^1.0.0, hash.js@npm:^1.0.3": + version: 1.1.7 + resolution: "hash.js@npm:1.1.7" + dependencies: + inherits: "npm:^2.0.3" + minimalistic-assert: "npm:^1.0.1" + checksum: 10c0/41ada59494eac5332cfc1ce6b7ebdd7b88a3864a6d6b08a3ea8ef261332ed60f37f10877e0c825aaa4bddebf164fbffa618286aeeec5296675e2671cbfa746c4 + languageName: node + linkType: hard + +"hashery@npm:^1.4.0, hashery@npm:^1.5.1": + version: 1.5.1 + resolution: "hashery@npm:1.5.1" + dependencies: + hookified: "npm:^1.15.0" + checksum: 10c0/ab4225b655a7b0d05df99b1a59d5b3a51fe433f82422ca25e6f3f4c4ddd30adb49ebd38e0047ef9bded93319c1e9fc857e16aa382e554929c871cb77d39fc463 + languageName: node + linkType: hard + +"hasown@npm:^2.0.0, hasown@npm:^2.0.2": + version: 2.0.2 + resolution: "hasown@npm:2.0.2" + dependencies: + function-bind: "npm:^1.1.2" + checksum: 10c0/3769d434703b8ac66b209a4cca0737519925bbdb61dd887f93a16372b14694c63ff4e797686d87c90f08168e81082248b9b028bad60d4da9e0d1148766f56eb9 + languageName: node + linkType: hard + +"hast-util-whitespace@npm:^2.0.0": + version: 2.0.1 + resolution: "hast-util-whitespace@npm:2.0.1" + checksum: 10c0/dcf6ebab091c802ffa7bb3112305c7631c15adb6c07a258f5528aefbddf82b4e162c8310ef426c48dc1dc623982cc33920e6dde5a50015d307f2778dcf6c2487 + languageName: node + linkType: hard + +"he@npm:^1.2.0": + version: 1.2.0 + resolution: "he@npm:1.2.0" + bin: + he: bin/he + checksum: 10c0/a27d478befe3c8192f006cdd0639a66798979dfa6e2125c6ac582a19a5ebfec62ad83e8382e6036170d873f46e4536a7e795bf8b95bf7c247f4cc0825ccc8c17 + languageName: node + linkType: hard + +"highlight.js@npm:^11.9.0": + version: 11.11.1 + resolution: "highlight.js@npm:11.11.1" + checksum: 10c0/40f53ac19dac079891fcefd5bd8a21cf2e8931fd47da5bd1dca73b7e4375c1defed0636fc39120c639b9c44119b7d110f7f0c15aa899557a5a1c8910f3c0144c + languageName: node + linkType: hard + +"history@npm:5.3.0, history@npm:^5.2.0": + version: 5.3.0 + resolution: "history@npm:5.3.0" + dependencies: + "@babel/runtime": "npm:^7.7.6" + checksum: 10c0/812ec839386222d6437bd78d9f05db32e47d105ada0ad8834b32626919dd2fee7a10001bc489510f93a8069d02f118214bd8d42a82f7cf9daf8e84fbcbbb2016 + languageName: node + linkType: hard + +"hmac-drbg@npm:^1.0.1": + version: 1.0.1 + resolution: "hmac-drbg@npm:1.0.1" + dependencies: + hash.js: "npm:^1.0.3" + minimalistic-assert: "npm:^1.0.0" + minimalistic-crypto-utils: "npm:^1.0.1" + checksum: 10c0/f3d9ba31b40257a573f162176ac5930109816036c59a09f901eb2ffd7e5e705c6832bedfff507957125f2086a0ab8f853c0df225642a88bf1fcaea945f20600d + languageName: node + linkType: hard + +"hoist-non-react-statics@npm:^3.0.0, hoist-non-react-statics@npm:^3.3.0, hoist-non-react-statics@npm:^3.3.1, hoist-non-react-statics@npm:^3.3.2": + version: 3.3.2 + resolution: "hoist-non-react-statics@npm:3.3.2" + dependencies: + react-is: "npm:^16.7.0" + checksum: 10c0/fe0889169e845d738b59b64badf5e55fa3cf20454f9203d1eb088df322d49d4318df774828e789898dcb280e8a5521bb59b3203385662ca5e9218a6ca5820e74 + languageName: node + linkType: hard + +"hookified@npm:^1.15.0, hookified@npm:^1.15.1": + version: 1.15.1 + resolution: "hookified@npm:1.15.1" + checksum: 10c0/6b691374fa97ae57169fb29f90e723499fda5e85494654fbe55c4768b3ccbf3e14c0adc8d0f365f32c503b60d7c06f907781f5966c03d41c423575eb5e16860c + languageName: node + linkType: hard + +"hookified@npm:^2.1.1": + version: 2.1.1 + resolution: "hookified@npm:2.1.1" + checksum: 10c0/f1d3e6a86a91061d321d44cde4bdb90340ff6083992182e08e3c60ba1210c2a9592c24988c66995d7f3e0b32214c3e5c71b8405e2ffb8022b2bb140b1eb1645f + languageName: node + linkType: hard + +"hosted-git-info@npm:^4.1.0": + version: 4.1.0 + resolution: "hosted-git-info@npm:4.1.0" + dependencies: + lru-cache: "npm:^6.0.0" + checksum: 10c0/150fbcb001600336d17fdbae803264abed013548eea7946c2264c49ebe2ebd8c4441ba71dd23dd8e18c65de79d637f98b22d4760ba5fb2e0b15d62543d0fff07 + languageName: node + linkType: hard + +"hpack.js@npm:^2.1.6": + version: 2.1.6 + resolution: "hpack.js@npm:2.1.6" + dependencies: + inherits: "npm:^2.0.1" + obuf: "npm:^1.0.0" + readable-stream: "npm:^2.0.1" + wbuf: "npm:^1.1.0" + checksum: 10c0/55b9e824430bab82a19d079cb6e33042d7d0640325678c9917fcc020c61d8a08ca671b6c942c7f0aae9bb6e4b67ffb50734a72f9e21d66407c3138c1983b70f0 + languageName: node + linkType: hard + +"htm@npm:^3.1.0": + version: 3.1.1 + resolution: "htm@npm:3.1.1" + checksum: 10c0/0de4c8fff2b8e76c162235ae80dbf93ca5eef1575bd50596a06ce9bebf1a6da5efc467417c53034a9ffa2ab9ecff819cbec041dc9087894b2b900ad4de26c7e7 + languageName: node + linkType: hard + +"html-entities@npm:^2.1.0": + version: 2.6.0 + resolution: "html-entities@npm:2.6.0" + checksum: 10c0/7c8b15d9ea0cd00dc9279f61bab002ba6ca8a7a0f3c36ed2db3530a67a9621c017830d1d2c1c65beb9b8e3436ea663e9cf8b230472e0e413359399413b27c8b7 + languageName: node + linkType: hard + +"html-minifier-terser@npm:^6.0.2": + version: 6.1.0 + resolution: "html-minifier-terser@npm:6.1.0" + dependencies: + camel-case: "npm:^4.1.2" + clean-css: "npm:^5.2.2" + commander: "npm:^8.3.0" + he: "npm:^1.2.0" + param-case: "npm:^3.0.4" + relateurl: "npm:^0.2.7" + terser: "npm:^5.10.0" + bin: + html-minifier-terser: cli.js + checksum: 10c0/1aa4e4f01cf7149e3ac5ea84fb7a1adab86da40d38d77a6fff42852b5ee3daccb78b615df97264e3a6a5c33e57f0c77f471d607ca1e1debd1dab9b58286f4b5a + languageName: node + linkType: hard + +"html-tags@npm:^5.1.0": + version: 5.1.0 + resolution: "html-tags@npm:5.1.0" + checksum: 10c0/2dda19bc07e75837d0c52984558d92e8b82768050e4d6421b3164b1cb6ca5e73719209c2b23c0fa71faf097a7a3d18cf7f2021b488f1b1f270fca516c4c634c9 + languageName: node + linkType: hard + +"html-to-image@npm:^1.11.13": + version: 1.11.13 + resolution: "html-to-image@npm:1.11.13" + checksum: 10c0/03bd7192d87b99499e37ff12a8e2b41ddce4cf8e33a93dfadff18df0f213434c7f1ba8328db2158f4805da07b7aec3c90aec3152908281153217e86784218b10 + languageName: node + linkType: hard + +"html-webpack-plugin@npm:5.5.0": + version: 5.5.0 + resolution: "html-webpack-plugin@npm:5.5.0" + dependencies: + "@types/html-minifier-terser": "npm:^6.0.0" + html-minifier-terser: "npm:^6.0.2" + lodash: "npm:^4.17.21" + pretty-error: "npm:^4.0.0" + tapable: "npm:^2.0.0" + peerDependencies: + webpack: ^5.20.0 + checksum: 10c0/d10fa5888db9ee2afe1d8544107d3d8eb0f30fd88a3304842725e91f9b86cd70fae9954342e6d513bdf9bb13f345c5f51c09421dbd96285593ea7ee8444b188e + languageName: node + linkType: hard + +"htmlparser2@npm:^6.1.0": + version: 6.1.0 + resolution: "htmlparser2@npm:6.1.0" + dependencies: + domelementtype: "npm:^2.0.1" + domhandler: "npm:^4.0.0" + domutils: "npm:^2.5.2" + entities: "npm:^2.0.0" + checksum: 10c0/3058499c95634f04dc66be8c2e0927cd86799413b2d6989d8ae542ca4dbf5fa948695d02c27d573acf44843af977aec6d9a7bdd0f6faa6b2d99e2a729b2a31b6 + languageName: node + linkType: hard + +"http-cache-semantics@npm:^4.0.0, http-cache-semantics@npm:^4.1.1": + version: 4.2.0 + resolution: "http-cache-semantics@npm:4.2.0" + checksum: 10c0/45b66a945cf13ec2d1f29432277201313babf4a01d9e52f44b31ca923434083afeca03f18417f599c9ab3d0e7b618ceb21257542338b57c54b710463b4a53e37 + languageName: node + linkType: hard + +"http-deceiver@npm:^1.2.7": + version: 1.2.7 + resolution: "http-deceiver@npm:1.2.7" + checksum: 10c0/8bb9b716f5fc55f54a451da7f49b9c695c3e45498a789634daec26b61e4add7c85613a4a9e53726c39d09de7a163891ecd6eb5809adb64500a840fd86fe81d03 + languageName: node + linkType: hard + +"http-errors@npm:2.0.0": + version: 2.0.0 + resolution: "http-errors@npm:2.0.0" + dependencies: + depd: "npm:2.0.0" + inherits: "npm:2.0.4" + setprototypeof: "npm:1.2.0" + statuses: "npm:2.0.1" + toidentifier: "npm:1.0.1" + checksum: 10c0/fc6f2715fe188d091274b5ffc8b3657bd85c63e969daa68ccb77afb05b071a4b62841acb7a21e417b5539014dff2ebf9550f0b14a9ff126f2734a7c1387f8e19 + languageName: node + linkType: hard + +"http-errors@npm:~1.7.2": + version: 1.7.3 + resolution: "http-errors@npm:1.7.3" + dependencies: + depd: "npm:~1.1.2" + inherits: "npm:2.0.4" + setprototypeof: "npm:1.1.1" + statuses: "npm:>= 1.5.0 < 2" + toidentifier: "npm:1.0.0" + checksum: 10c0/5c3443c340d35b2f18ce908266c4ae93305b7d900bef765ac8dc56fa90125b9fe18a1ed9ebf6af23dc3ba7763731921a2682bf968e199eccf383eb8f508be6c2 + languageName: node + linkType: hard + +"http-errors@npm:~2.0.1": + version: 2.0.1 + resolution: "http-errors@npm:2.0.1" + dependencies: + depd: "npm:~2.0.0" + inherits: "npm:~2.0.4" + setprototypeof: "npm:~1.2.0" + statuses: "npm:~2.0.2" + toidentifier: "npm:~1.0.1" + checksum: 10c0/fb38906cef4f5c83952d97661fe14dc156cb59fe54812a42cd448fa57b5c5dfcb38a40a916957737bd6b87aab257c0648d63eb5b6a9ca9f548e105b6072712d4 + languageName: node + linkType: hard + +"http-proxy-agent@npm:^5.0.0": + version: 5.0.0 + resolution: "http-proxy-agent@npm:5.0.0" + dependencies: + "@tootallnate/once": "npm:2" + agent-base: "npm:6" + debug: "npm:4" + checksum: 10c0/32a05e413430b2c1e542e5c74b38a9f14865301dd69dff2e53ddb684989440e3d2ce0c4b64d25eb63cf6283e6265ff979a61cf93e3ca3d23047ddfdc8df34a32 + languageName: node + linkType: hard + +"http-proxy-agent@npm:^7.0.0": + version: 7.0.2 + resolution: "http-proxy-agent@npm:7.0.2" + dependencies: + agent-base: "npm:^7.1.0" + debug: "npm:^4.3.4" + checksum: 10c0/4207b06a4580fb85dd6dff521f0abf6db517489e70863dca1a0291daa7f2d3d2d6015a57bd702af068ea5cf9f1f6ff72314f5f5b4228d299c0904135d2aef921 + languageName: node + linkType: hard + +"http2-wrapper@npm:^1.0.0-beta.5.2": + version: 1.0.3 + resolution: "http2-wrapper@npm:1.0.3" + dependencies: + quick-lru: "npm:^5.1.1" + resolve-alpn: "npm:^1.0.0" + checksum: 10c0/6a9b72a033e9812e1476b9d776ce2f387bc94bc46c88aea0d5dab6bd47d0a539b8178830e77054dd26d1142c866d515a28a4dc7c3ff4232c88ff2ebe4f5d12d1 + languageName: node + linkType: hard + +"https-browserify@npm:^1.0.0": + version: 1.0.0 + resolution: "https-browserify@npm:1.0.0" + checksum: 10c0/e17b6943bc24ea9b9a7da5714645d808670af75a425f29baffc3284962626efdc1eb3aa9bbffaa6e64028a6ad98af5b09fabcb454a8f918fb686abfdc9e9b8ae + languageName: node + linkType: hard + +"https-proxy-agent@npm:^5.0.0": + version: 5.0.1 + resolution: "https-proxy-agent@npm:5.0.1" + dependencies: + agent-base: "npm:6" + debug: "npm:4" + checksum: 10c0/6dd639f03434003577c62b27cafdb864784ef19b2de430d8ae2a1d45e31c4fd60719e5637b44db1a88a046934307da7089e03d6089ec3ddacc1189d8de8897d1 + languageName: node + linkType: hard + +"https-proxy-agent@npm:^7.0.1": + version: 7.0.6 + resolution: "https-proxy-agent@npm:7.0.6" + dependencies: + agent-base: "npm:^7.1.2" + debug: "npm:4" + checksum: 10c0/f729219bc735edb621fa30e6e84e60ee5d00802b8247aac0d7b79b0bd6d4b3294737a337b93b86a0bd9e68099d031858a39260c976dc14cdbba238ba1f8779ac + languageName: node + linkType: hard + +"human-signals@npm:^2.1.0": + version: 2.1.0 + resolution: "human-signals@npm:2.1.0" + checksum: 10c0/695edb3edfcfe9c8b52a76926cd31b36978782062c0ed9b1192b36bebc75c4c87c82e178dfcb0ed0fc27ca59d434198aac0bd0be18f5781ded775604db22304a + languageName: node + linkType: hard + +"human-signals@npm:^4.3.0": + version: 4.3.1 + resolution: "human-signals@npm:4.3.1" + checksum: 10c0/40498b33fe139f5cc4ef5d2f95eb1803d6318ac1b1c63eaf14eeed5484d26332c828de4a5a05676b6c83d7b9e57727c59addb4b1dea19cb8d71e83689e5b336c + languageName: node + linkType: hard + +"iconv-corefoundation@npm:^1.1.7": + version: 1.1.7 + resolution: "iconv-corefoundation@npm:1.1.7" + dependencies: + cli-truncate: "npm:^2.1.0" + node-addon-api: "npm:^1.6.3" + conditions: os=darwin + languageName: node + linkType: hard + +"iconv-lite@npm:0.4.24, iconv-lite@npm:~0.4.24": + version: 0.4.24 + resolution: "iconv-lite@npm:0.4.24" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3" + checksum: 10c0/c6886a24cc00f2a059767440ec1bc00d334a89f250db8e0f7feb4961c8727118457e27c495ba94d082e51d3baca378726cd110aaf7ded8b9bbfd6a44760cf1d4 + languageName: node + linkType: hard + +"iconv-lite@npm:^0.6.2, iconv-lite@npm:^0.6.3": + version: 0.6.3 + resolution: "iconv-lite@npm:0.6.3" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3.0.0" + checksum: 10c0/98102bc66b33fcf5ac044099d1257ba0b7ad5e3ccd3221f34dd508ab4070edff183276221684e1e0555b145fce0850c9f7d2b60a9fcac50fbb4ea0d6e845a3b1 + languageName: node + linkType: hard + +"icss-utils@npm:^5.0.0, icss-utils@npm:^5.1.0": + version: 5.1.0 + resolution: "icss-utils@npm:5.1.0" + peerDependencies: + postcss: ^8.1.0 + checksum: 10c0/39c92936fabd23169c8611d2b5cc39e39d10b19b0d223352f20a7579f75b39d5f786114a6b8fc62bee8c5fed59ba9e0d38f7219a4db383e324fb3061664b043d + languageName: node + linkType: hard + +"identity-obj-proxy@npm:3.0.0": + version: 3.0.0 + resolution: "identity-obj-proxy@npm:3.0.0" + dependencies: + harmony-reflect: "npm:^1.4.6" + checksum: 10c0/a3fc4de0042d7b45bf8652d5596c80b42139d8625c9cd6a8834e29e1b6dce8fccabd1228e08744b78677a19ceed7201a32fed8ca3dc3e4852e8fee24360a6cfc + languageName: node + linkType: hard + +"ieee754@npm:^1.1.13, ieee754@npm:^1.1.4": + version: 1.2.1 + resolution: "ieee754@npm:1.2.1" + checksum: 10c0/b0782ef5e0935b9f12883a2e2aa37baa75da6e66ce6515c168697b42160807d9330de9a32ec1ed73149aea02e0d822e572bca6f1e22bdcbd2149e13b050b17bb + languageName: node + linkType: hard + +"ignore@npm:^5.2.0, ignore@npm:^5.2.4": + version: 5.3.2 + resolution: "ignore@npm:5.3.2" + checksum: 10c0/f9f652c957983634ded1e7f02da3b559a0d4cc210fca3792cb67f1b153623c9c42efdc1c4121af171e295444459fc4a9201101fb041b1104a3c000bccb188337 + languageName: node + linkType: hard + +"ignore@npm:^7.0.5": + version: 7.0.5 + resolution: "ignore@npm:7.0.5" + checksum: 10c0/ae00db89fe873064a093b8999fe4cc284b13ef2a178636211842cceb650b9c3e390d3339191acb145d81ed5379d2074840cf0c33a20bdbd6f32821f79eb4ad5d + languageName: node + linkType: hard + +"image-size@npm:~0.5.0": + version: 0.5.5 + resolution: "image-size@npm:0.5.5" + bin: + image-size: bin/image-size.js + checksum: 10c0/655204163af06732f483a9fe7cce9dff4a29b7b2e88f5c957a5852e8143fa750f5e54b1955a2ca83de99c5220dbd680002d0d4e09140b01433520f4d5a0b1f4c + languageName: node + linkType: hard + +"immer@npm:^8.0.4": + version: 8.0.4 + resolution: "immer@npm:8.0.4" + checksum: 10c0/c02e9bf6cff1db8c51578663878403beaf2eafaf5ad02fac344dd82336578de948ecf609fd67665d6fe8bf115745866fb8b66f7d6348273cf8b9cc134ac71bb6 + languageName: node + linkType: hard + +"immutable@npm:^4.0.0": + version: 4.3.7 + resolution: "immutable@npm:4.3.7" + checksum: 10c0/9b099197081b22f6433003e34929da8ecddbbdc1474cdc8aa3b7669dee4adda349c06143de22def36016d1b6de5322b043eccd7a11db1dad2ca85dad4fff5435 + languageName: node + linkType: hard + +"import-fresh@npm:^3.2.1, import-fresh@npm:^3.3.0": + version: 3.3.1 + resolution: "import-fresh@npm:3.3.1" + dependencies: + parent-module: "npm:^1.0.0" + resolve-from: "npm:^4.0.0" + checksum: 10c0/bf8cc494872fef783249709385ae883b447e3eb09db0ebd15dcead7d9afe7224dad7bd7591c6b73b0b19b3c0f9640eb8ee884f01cfaf2887ab995b0b36a0cbec + languageName: node + linkType: hard + +"import-html-entry@npm:^1.15.1": + version: 1.17.0 + resolution: "import-html-entry@npm:1.17.0" + dependencies: + "@babel/runtime": "npm:^7.7.2" + checksum: 10c0/69fc526bb4c9ec734fe441f289662a4c94875cbd1e76fe2e2baa06269cdd734c7391625b7fba44c35e52a4a5bfed139834c56ffc6031b964bb8c1d9429196797 + languageName: node + linkType: hard + +"import-meta-resolve@npm:^4.2.0": + version: 4.2.0 + resolution: "import-meta-resolve@npm:4.2.0" + checksum: 10c0/3ee8aeecb61d19b49d2703987f977e9d1c7d4ba47db615a570eaa02fe414f40dfa63f7b953e842cbe8470d26df6371332bfcf21b2fd92b0112f9fea80dde2c4c + languageName: node + linkType: hard + +"imurmurhash@npm:^0.1.4": + version: 0.1.4 + resolution: "imurmurhash@npm:0.1.4" + checksum: 10c0/8b51313850dd33605c6c9d3fd9638b714f4c4c40250cff658209f30d40da60f78992fb2df5dabee4acf589a6a82bbc79ad5486550754bd9ec4e3fc0d4a57d6a6 + languageName: node + linkType: hard + +"inflight@npm:^1.0.4": + version: 1.0.6 + resolution: "inflight@npm:1.0.6" + dependencies: + once: "npm:^1.3.0" + wrappy: "npm:1" + checksum: 10c0/7faca22584600a9dc5b9fca2cd5feb7135ac8c935449837b315676b4c90aa4f391ec4f42240178244b5a34e8bede1948627fda392ca3191522fc46b34e985ab2 + languageName: node + linkType: hard + +"inherits@npm:2, inherits@npm:2.0.4, inherits@npm:^2.0.1, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.1, inherits@npm:~2.0.3, inherits@npm:~2.0.4": + version: 2.0.4 + resolution: "inherits@npm:2.0.4" + checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2 + languageName: node + linkType: hard + +"inherits@npm:2.0.1": + version: 2.0.1 + resolution: "inherits@npm:2.0.1" + checksum: 10c0/bfc7b37c21a2cddb272adc65b053b1716612d408bb2c9a4e5c32679dc2b08032aadd67880c405be3dff060a62e45b353fc3d9fa79a3067ad7a3deb6a283cc5c6 + languageName: node + linkType: hard + +"inherits@npm:2.0.3": + version: 2.0.3 + resolution: "inherits@npm:2.0.3" + checksum: 10c0/6e56402373149ea076a434072671f9982f5fad030c7662be0332122fe6c0fa490acb3cc1010d90b6eff8d640b1167d77674add52dfd1bb85d545cf29e80e73e7 + languageName: node + linkType: hard + +"ini@npm:^1.3.5": + version: 1.3.8 + resolution: "ini@npm:1.3.8" + checksum: 10c0/ec93838d2328b619532e4f1ff05df7909760b6f66d9c9e2ded11e5c1897d6f2f9980c54dd638f88654b00919ce31e827040631eab0a3969e4d1abefa0719516a + languageName: node + linkType: hard + +"inline-style-parser@npm:0.1.1": + version: 0.1.1 + resolution: "inline-style-parser@npm:0.1.1" + checksum: 10c0/08832a533f51a1e17619f2eabf2f5ec5e956d6dcba1896351285c65df022c9420de61d73256e1dca8015a52abf96cc84ddc3b73b898b22de6589d3962b5e501b + languageName: node + linkType: hard + +"internal-slot@npm:^1.1.0": + version: 1.1.0 + resolution: "internal-slot@npm:1.1.0" + dependencies: + es-errors: "npm:^1.3.0" + hasown: "npm:^2.0.2" + side-channel: "npm:^1.1.0" + checksum: 10c0/03966f5e259b009a9bf1a78d60da920df198af4318ec004f57b8aef1dd3fe377fbc8cce63a96e8c810010302654de89f9e19de1cd8ad0061d15be28a695465c7 + languageName: node + linkType: hard + +"interpret@npm:^1.4.0": + version: 1.4.0 + resolution: "interpret@npm:1.4.0" + checksum: 10c0/08c5ad30032edeec638485bc3f6db7d0094d9b3e85e0f950866600af3c52e9fd69715416d29564731c479d9f4d43ff3e4d302a178196bdc0e6837ec147640450 + languageName: node + linkType: hard + +"intersection-observer@npm:^0.12.0": + version: 0.12.2 + resolution: "intersection-observer@npm:0.12.2" + checksum: 10c0/9591f46b2b742f5801ed69dbc8860f487771b4af8361e7a5dcb28a377beff2ba56336a2b090af261825430d225dae9417121496d2e6925e000e4a469958843ff + languageName: node + linkType: hard + +"intl-format-cache@npm:^4.2.21": + version: 4.3.1 + resolution: "intl-format-cache@npm:4.3.1" + checksum: 10c0/791b285630fbc0b41fb8251fc8a3baf4bb0d1e9cd9bbe0a93f9166f15adcd969b6319f57893e325a3214ec0db5e596f398b0efc8bdfac8495022cf50d55aa23f + languageName: node + linkType: hard + +"intl-messageformat-parser@npm:^3.6.4": + version: 3.6.4 + resolution: "intl-messageformat-parser@npm:3.6.4" + dependencies: + "@formatjs/intl-unified-numberformat": "npm:^3.2.0" + checksum: 10c0/89b9809b21f9caa68d8238ba1488052227789e40c3cbcc14ed80e23d6e1f7e3006a83a5889997794969e90b9fe64b1529141c95d3ae2e48364d0bab1b6888d18 + languageName: node + linkType: hard + +"intl-messageformat@npm:^7.8.4": + version: 7.8.4 + resolution: "intl-messageformat@npm:7.8.4" + dependencies: + intl-format-cache: "npm:^4.2.21" + intl-messageformat-parser: "npm:^3.6.4" + checksum: 10c0/731fca7b3c9c41520d9db4f65c68c0212be275b83bc8f24b370e0c880cb8ad9c43e14daa41900007367aff36b8659175f173f2bbf05c4642afb5047675574789 + languageName: node + linkType: hard + +"intl@npm:1.2.5": + version: 1.2.5 + resolution: "intl@npm:1.2.5" + checksum: 10c0/ee6b0ab274ab730b4947604eb9b155dfb32caf8f1d7826da2b3480fca42bf19092f6a723028f6a6751d90c0a709ec80a5149780acc24ebf6f1eb8a7a27cc54c8 + languageName: node + linkType: hard + +"invariant@npm:^2.2.1, invariant@npm:^2.2.4": + version: 2.2.4 + resolution: "invariant@npm:2.2.4" + dependencies: + loose-envify: "npm:^1.0.0" + checksum: 10c0/5af133a917c0bcf65e84e7f23e779e7abc1cd49cb7fdc62d00d1de74b0d8c1b5ee74ac7766099fb3be1b05b26dfc67bab76a17030d2fe7ea2eef867434362dfc + languageName: node + linkType: hard + +"ip-address@npm:^10.0.1": + version: 10.1.0 + resolution: "ip-address@npm:10.1.0" + checksum: 10c0/0103516cfa93f6433b3bd7333fa876eb21263912329bfa47010af5e16934eeeff86f3d2ae700a3744a137839ddfad62b900c7a445607884a49b5d1e32a3d7566 + languageName: node + linkType: hard + +"ipaddr.js@npm:1.9.1": + version: 1.9.1 + resolution: "ipaddr.js@npm:1.9.1" + checksum: 10c0/0486e775047971d3fdb5fb4f063829bac45af299ae0b82dcf3afa2145338e08290563a2a70f34b732d795ecc8311902e541a8530eeb30d75860a78ff4e94ce2a + languageName: node + linkType: hard + +"is-arguments@npm:^1.1.1": + version: 1.2.0 + resolution: "is-arguments@npm:1.2.0" + dependencies: + call-bound: "npm:^1.0.2" + has-tostringtag: "npm:^1.0.2" + checksum: 10c0/6377344b31e9fcb707c6751ee89b11f132f32338e6a782ec2eac9393b0cbd32235dad93052998cda778ee058754860738341d8114910d50ada5615912bb929fc + languageName: node + linkType: hard + +"is-array-buffer@npm:^3.0.4, is-array-buffer@npm:^3.0.5": + version: 3.0.5 + resolution: "is-array-buffer@npm:3.0.5" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.3" + get-intrinsic: "npm:^1.2.6" + checksum: 10c0/c5c9f25606e86dbb12e756694afbbff64bc8b348d1bc989324c037e1068695131930199d6ad381952715dad3a9569333817f0b1a72ce5af7f883ce802e49c83d + languageName: node + linkType: hard + +"is-arrayish@npm:^0.2.1": + version: 0.2.1 + resolution: "is-arrayish@npm:0.2.1" + checksum: 10c0/e7fb686a739068bb70f860b39b67afc62acc62e36bb61c5f965768abce1873b379c563e61dd2adad96ebb7edf6651111b385e490cf508378959b0ed4cac4e729 + languageName: node + linkType: hard + +"is-arrow-function@npm:^2.0.3": + version: 2.0.3 + resolution: "is-arrow-function@npm:2.0.3" + dependencies: + is-callable: "npm:^1.0.4" + checksum: 10c0/b9b3dd71e21079afef85daa5a3ed570461a329bbf6e0af2c34151d9e17f93781ec51de517bf9efd1ae8cd8a9b87adc62d7804110fc55d384e6ed6c4c0e8c6b29 + languageName: node + linkType: hard + +"is-async-function@npm:^2.0.0": + version: 2.1.1 + resolution: "is-async-function@npm:2.1.1" + dependencies: + async-function: "npm:^1.0.0" + call-bound: "npm:^1.0.3" + get-proto: "npm:^1.0.1" + has-tostringtag: "npm:^1.0.2" + safe-regex-test: "npm:^1.1.0" + checksum: 10c0/d70c236a5e82de6fc4d44368ffd0c2fee2b088b893511ce21e679da275a5ecc6015ff59a7d7e1bdd7ca39f71a8dbdd253cf8cce5c6b3c91cdd5b42b5ce677298 + languageName: node + linkType: hard + +"is-bigint@npm:^1.0.4, is-bigint@npm:^1.1.0": + version: 1.1.0 + resolution: "is-bigint@npm:1.1.0" + dependencies: + has-bigints: "npm:^1.0.2" + checksum: 10c0/f4f4b905ceb195be90a6ea7f34323bf1c18e3793f18922e3e9a73c684c29eeeeff5175605c3a3a74cc38185fe27758f07efba3dbae812e5c5afbc0d2316b40e4 + languageName: node + linkType: hard + +"is-binary-path@npm:~2.1.0": + version: 2.1.0 + resolution: "is-binary-path@npm:2.1.0" + dependencies: + binary-extensions: "npm:^2.0.0" + checksum: 10c0/a16eaee59ae2b315ba36fad5c5dcaf8e49c3e27318f8ab8fa3cdb8772bf559c8d1ba750a589c2ccb096113bb64497084361a25960899cb6172a6925ab6123d38 + languageName: node + linkType: hard + +"is-boolean-object@npm:^1.1.2, is-boolean-object@npm:^1.2.1": + version: 1.2.2 + resolution: "is-boolean-object@npm:1.2.2" + dependencies: + call-bound: "npm:^1.0.3" + has-tostringtag: "npm:^1.0.2" + checksum: 10c0/36ff6baf6bd18b3130186990026f5a95c709345c39cd368468e6c1b6ab52201e9fd26d8e1f4c066357b4938b0f0401e1a5000e08257787c1a02f3a719457001e + languageName: node + linkType: hard + +"is-buffer@npm:^2.0.0": + version: 2.0.5 + resolution: "is-buffer@npm:2.0.5" + checksum: 10c0/e603f6fced83cf94c53399cff3bda1a9f08e391b872b64a73793b0928be3e5f047f2bcece230edb7632eaea2acdbfcb56c23b33d8a20c820023b230f1485679a + languageName: node + linkType: hard + +"is-callable@npm:^1.0.4, is-callable@npm:^1.2.7": + version: 1.2.7 + resolution: "is-callable@npm:1.2.7" + checksum: 10c0/ceebaeb9d92e8adee604076971dd6000d38d6afc40bb843ea8e45c5579b57671c3f3b50d7f04869618242c6cee08d1b67806a8cb8edaaaf7c0748b3720d6066f + languageName: node + linkType: hard + +"is-ci@npm:^3.0.0": + version: 3.0.1 + resolution: "is-ci@npm:3.0.1" + dependencies: + ci-info: "npm:^3.2.0" + bin: + is-ci: bin.js + checksum: 10c0/0e81caa62f4520d4088a5bef6d6337d773828a88610346c4b1119fb50c842587ed8bef1e5d9a656835a599e7209405b5761ddf2339668f2d0f4e889a92fe6051 + languageName: node + linkType: hard + +"is-core-module@npm:^2.13.0, is-core-module@npm:^2.15.1, is-core-module@npm:^2.16.1": + version: 2.16.1 + resolution: "is-core-module@npm:2.16.1" + dependencies: + hasown: "npm:^2.0.2" + checksum: 10c0/898443c14780a577e807618aaae2b6f745c8538eca5c7bc11388a3f2dc6de82b9902bcc7eb74f07be672b11bbe82dd6a6edded44a00cb3d8f933d0459905eedd + languageName: node + linkType: hard + +"is-data-view@npm:^1.0.1, is-data-view@npm:^1.0.2": + version: 1.0.2 + resolution: "is-data-view@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.2" + get-intrinsic: "npm:^1.2.6" + is-typed-array: "npm:^1.1.13" + checksum: 10c0/ef3548a99d7e7f1370ce21006baca6d40c73e9f15c941f89f0049c79714c873d03b02dae1c64b3f861f55163ecc16da06506c5b8a1d4f16650b3d9351c380153 + languageName: node + linkType: hard + +"is-date-object@npm:^1.0.5, is-date-object@npm:^1.1.0": + version: 1.1.0 + resolution: "is-date-object@npm:1.1.0" + dependencies: + call-bound: "npm:^1.0.2" + has-tostringtag: "npm:^1.0.2" + checksum: 10c0/1a4d199c8e9e9cac5128d32e6626fa7805175af9df015620ac0d5d45854ccf348ba494679d872d37301032e35a54fc7978fba1687e8721b2139aea7870cafa2f + languageName: node + linkType: hard + +"is-docker@npm:^2.0.0, is-docker@npm:^2.1.1": + version: 2.2.1 + resolution: "is-docker@npm:2.2.1" + bin: + is-docker: cli.js + checksum: 10c0/e828365958d155f90c409cdbe958f64051d99e8aedc2c8c4cd7c89dcf35329daed42f7b99346f7828df013e27deb8f721cf9408ba878c76eb9e8290235fbcdcc + languageName: node + linkType: hard + +"is-docker@npm:^3.0.0": + version: 3.0.0 + resolution: "is-docker@npm:3.0.0" + bin: + is-docker: cli.js + checksum: 10c0/d2c4f8e6d3e34df75a5defd44991b6068afad4835bb783b902fa12d13ebdb8f41b2a199dcb0b5ed2cb78bfee9e4c0bbdb69c2d9646f4106464674d3e697a5856 + languageName: node + linkType: hard + +"is-electron@npm:^2.2.2": + version: 2.2.2 + resolution: "is-electron@npm:2.2.2" + checksum: 10c0/327bb373f7be01b16cdff3998b5ddaa87d28f576092affaa7fe0659571b3306fdd458afbf0683a66841e7999af13f46ad0e1b51647b469526cd05a4dd736438a + languageName: node + linkType: hard + +"is-equal@npm:^1.6.4": + version: 1.7.0 + resolution: "is-equal@npm:1.7.0" + dependencies: + es-get-iterator: "npm:^1.1.3" + es-to-primitive: "npm:^1.2.1" + functions-have-names: "npm:^1.2.3" + has-bigints: "npm:^1.0.2" + has-symbols: "npm:^1.0.3" + hasown: "npm:^2.0.0" + is-arrow-function: "npm:^2.0.3" + is-bigint: "npm:^1.0.4" + is-boolean-object: "npm:^1.1.2" + is-callable: "npm:^1.2.7" + is-date-object: "npm:^1.0.5" + is-generator-function: "npm:^1.0.10" + is-number-object: "npm:^1.0.7" + is-regex: "npm:^1.1.4" + is-string: "npm:^1.0.7" + is-symbol: "npm:^1.0.4" + isarray: "npm:^2.0.5" + object-inspect: "npm:^1.13.1" + object.entries: "npm:^1.1.7" + object.getprototypeof: "npm:^1.0.5" + which-boxed-primitive: "npm:^1.0.2" + which-collection: "npm:^1.0.1" + checksum: 10c0/83bf27b64a2d422bb58dd2b3dbc504e012253f2ad236eb0d4790ba5d355345adeff2aed8f08501397a7c3cfdc0f4c4f1671bfe258f11d6755f07952ab08e566d + languageName: node + linkType: hard + +"is-extglob@npm:^2.1.1": + version: 2.1.1 + resolution: "is-extglob@npm:2.1.1" + checksum: 10c0/5487da35691fbc339700bbb2730430b07777a3c21b9ebaecb3072512dfd7b4ba78ac2381a87e8d78d20ea08affb3f1971b4af629173a6bf435ff8a4c47747912 + languageName: node + linkType: hard + +"is-finalizationregistry@npm:^1.1.0": + version: 1.1.1 + resolution: "is-finalizationregistry@npm:1.1.1" + dependencies: + call-bound: "npm:^1.0.3" + checksum: 10c0/818dff679b64f19e228a8205a1e2d09989a98e98def3a817f889208cfcbf918d321b251aadf2c05918194803ebd2eb01b14fc9d0b2bea53d984f4137bfca5e97 + languageName: node + linkType: hard + +"is-fullwidth-code-point@npm:^3.0.0": + version: 3.0.0 + resolution: "is-fullwidth-code-point@npm:3.0.0" + checksum: 10c0/bb11d825e049f38e04c06373a8d72782eee0205bda9d908cc550ccb3c59b99d750ff9537982e01733c1c94a58e35400661f57042158ff5e8f3e90cf936daf0fc + languageName: node + linkType: hard + +"is-generator-function@npm:^1.0.10": + version: 1.1.2 + resolution: "is-generator-function@npm:1.1.2" + dependencies: + call-bound: "npm:^1.0.4" + generator-function: "npm:^2.0.0" + get-proto: "npm:^1.0.1" + has-tostringtag: "npm:^1.0.2" + safe-regex-test: "npm:^1.1.0" + checksum: 10c0/83da102e89c3e3b71d67b51d47c9f9bc862bceb58f87201727e27f7fa19d1d90b0ab223644ecaee6fc6e3d2d622bb25c966fbdaf87c59158b01ce7c0fe2fa372 + languageName: node + linkType: hard + +"is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:^4.0.3, is-glob@npm:~4.0.1": + version: 4.0.3 + resolution: "is-glob@npm:4.0.3" + dependencies: + is-extglob: "npm:^2.1.1" + checksum: 10c0/17fb4014e22be3bbecea9b2e3a76e9e34ff645466be702f1693e8f1ee1adac84710d0be0bd9f967d6354036fd51ab7c2741d954d6e91dae6bb69714de92c197a + languageName: node + linkType: hard + +"is-inside-container@npm:^1.0.0": + version: 1.0.0 + resolution: "is-inside-container@npm:1.0.0" + dependencies: + is-docker: "npm:^3.0.0" + bin: + is-inside-container: cli.js + checksum: 10c0/a8efb0e84f6197e6ff5c64c52890fa9acb49b7b74fed4da7c95383965da6f0fa592b4dbd5e38a79f87fc108196937acdbcd758fcefc9b140e479b39ce1fcd1cd + languageName: node + linkType: hard + +"is-map@npm:^2.0.2, is-map@npm:^2.0.3": + version: 2.0.3 + resolution: "is-map@npm:2.0.3" + checksum: 10c0/2c4d431b74e00fdda7162cd8e4b763d6f6f217edf97d4f8538b94b8702b150610e2c64961340015fe8df5b1fcee33ccd2e9b62619c4a8a3a155f8de6d6d355fc + languageName: node + linkType: hard + +"is-negative-zero@npm:^2.0.3": + version: 2.0.3 + resolution: "is-negative-zero@npm:2.0.3" + checksum: 10c0/bcdcf6b8b9714063ffcfa9929c575ac69bfdabb8f4574ff557dfc086df2836cf07e3906f5bbc4f2a5c12f8f3ba56af640c843cdfc74da8caed86c7c7d66fd08e + languageName: node + linkType: hard + +"is-number-object@npm:^1.0.7, is-number-object@npm:^1.1.1": + version: 1.1.1 + resolution: "is-number-object@npm:1.1.1" + dependencies: + call-bound: "npm:^1.0.3" + has-tostringtag: "npm:^1.0.2" + checksum: 10c0/97b451b41f25135ff021d85c436ff0100d84a039bb87ffd799cbcdbea81ef30c464ced38258cdd34f080be08fc3b076ca1f472086286d2aa43521d6ec6a79f53 + languageName: node + linkType: hard + +"is-number@npm:^7.0.0": + version: 7.0.0 + resolution: "is-number@npm:7.0.0" + checksum: 10c0/b4686d0d3053146095ccd45346461bc8e53b80aeb7671cc52a4de02dbbf7dc0d1d2a986e2fe4ae206984b4d34ef37e8b795ebc4f4295c978373e6575e295d811 + languageName: node + linkType: hard + +"is-path-inside@npm:^3.0.3": + version: 3.0.3 + resolution: "is-path-inside@npm:3.0.3" + checksum: 10c0/cf7d4ac35fb96bab6a1d2c3598fe5ebb29aafb52c0aaa482b5a3ed9d8ba3edc11631e3ec2637660c44b3ce0e61a08d54946e8af30dec0b60a7c27296c68ffd05 + languageName: node + linkType: hard + +"is-path-inside@npm:^4.0.0": + version: 4.0.0 + resolution: "is-path-inside@npm:4.0.0" + checksum: 10c0/51188d7e2b1d907a9a5f7c18d99a90b60870b951ed87cf97595d9aaa429d4c010652c3350bcbf31182e7f4b0eab9a1860b43e16729b13cb1a44baaa6cdb64c46 + languageName: node + linkType: hard + +"is-plain-obj@npm:^4.0.0, is-plain-obj@npm:^4.1.0": + version: 4.1.0 + resolution: "is-plain-obj@npm:4.1.0" + checksum: 10c0/32130d651d71d9564dc88ba7e6fda0e91a1010a3694648e9f4f47bb6080438140696d3e3e15c741411d712e47ac9edc1a8a9de1fe76f3487b0d90be06ac9975e + languageName: node + linkType: hard + +"is-plain-object@npm:^2.0.3": + version: 2.0.4 + resolution: "is-plain-object@npm:2.0.4" + dependencies: + isobject: "npm:^3.0.1" + checksum: 10c0/f050fdd5203d9c81e8c4df1b3ff461c4bc64e8b5ca383bcdde46131361d0a678e80bcf00b5257646f6c636197629644d53bd8e2375aea633de09a82d57e942f4 + languageName: node + linkType: hard + +"is-plain-object@npm:^5.0.0": + version: 5.0.0 + resolution: "is-plain-object@npm:5.0.0" + checksum: 10c0/893e42bad832aae3511c71fd61c0bf61aa3a6d853061c62a307261842727d0d25f761ce9379f7ba7226d6179db2a3157efa918e7fe26360f3bf0842d9f28942c + languageName: node + linkType: hard + +"is-regex@npm:^1.1.4, is-regex@npm:^1.2.0, is-regex@npm:^1.2.1": + version: 1.2.1 + resolution: "is-regex@npm:1.2.1" + dependencies: + call-bound: "npm:^1.0.2" + gopd: "npm:^1.2.0" + has-tostringtag: "npm:^1.0.2" + hasown: "npm:^2.0.2" + checksum: 10c0/1d3715d2b7889932349241680032e85d0b492cfcb045acb75ffc2c3085e8d561184f1f7e84b6f8321935b4aea39bc9c6ba74ed595b57ce4881a51dfdbc214e04 + languageName: node + linkType: hard + +"is-set@npm:^2.0.2, is-set@npm:^2.0.3": + version: 2.0.3 + resolution: "is-set@npm:2.0.3" + checksum: 10c0/f73732e13f099b2dc879c2a12341cfc22ccaca8dd504e6edae26484bd5707a35d503fba5b4daad530a9b088ced1ae6c9d8200fd92e09b428fe14ea79ce8080b7 + languageName: node + linkType: hard + +"is-shared-array-buffer@npm:^1.0.4": + version: 1.0.4 + resolution: "is-shared-array-buffer@npm:1.0.4" + dependencies: + call-bound: "npm:^1.0.3" + checksum: 10c0/65158c2feb41ff1edd6bbd6fd8403a69861cf273ff36077982b5d4d68e1d59278c71691216a4a64632bd76d4792d4d1d2553901b6666d84ade13bba5ea7bc7db + languageName: node + linkType: hard + +"is-stream@npm:^1.0.1": + version: 1.1.0 + resolution: "is-stream@npm:1.1.0" + checksum: 10c0/b8ae7971e78d2e8488d15f804229c6eed7ed36a28f8807a1815938771f4adff0e705218b7dab968270433f67103e4fef98062a0beea55d64835f705ee72c7002 + languageName: node + linkType: hard + +"is-stream@npm:^2.0.0": + version: 2.0.1 + resolution: "is-stream@npm:2.0.1" + checksum: 10c0/7c284241313fc6efc329b8d7f08e16c0efeb6baab1b4cd0ba579eb78e5af1aa5da11e68559896a2067cd6c526bd29241dda4eb1225e627d5aa1a89a76d4635a5 + languageName: node + linkType: hard + +"is-stream@npm:^3.0.0": + version: 3.0.0 + resolution: "is-stream@npm:3.0.0" + checksum: 10c0/eb2f7127af02ee9aa2a0237b730e47ac2de0d4e76a4a905a50a11557f2339df5765eaea4ceb8029f1efa978586abe776908720bfcb1900c20c6ec5145f6f29d8 + languageName: node + linkType: hard + +"is-string@npm:^1.0.7, is-string@npm:^1.1.1": + version: 1.1.1 + resolution: "is-string@npm:1.1.1" + dependencies: + call-bound: "npm:^1.0.3" + has-tostringtag: "npm:^1.0.2" + checksum: 10c0/2f518b4e47886bb81567faba6ffd0d8a8333cf84336e2e78bf160693972e32ad00fe84b0926491cc598dee576fdc55642c92e62d0cbe96bf36f643b6f956f94d + languageName: node + linkType: hard + +"is-symbol@npm:^1.0.4, is-symbol@npm:^1.1.1": + version: 1.1.1 + resolution: "is-symbol@npm:1.1.1" + dependencies: + call-bound: "npm:^1.0.2" + has-symbols: "npm:^1.1.0" + safe-regex-test: "npm:^1.1.0" + checksum: 10c0/f08f3e255c12442e833f75a9e2b84b2d4882fdfd920513cf2a4a2324f0a5b076c8fd913778e3ea5d258d5183e9d92c0cd20e04b03ab3df05316b049b2670af1e + languageName: node + linkType: hard + +"is-typed-array@npm:^1.1.13, is-typed-array@npm:^1.1.14, is-typed-array@npm:^1.1.15": + version: 1.1.15 + resolution: "is-typed-array@npm:1.1.15" + dependencies: + which-typed-array: "npm:^1.1.16" + checksum: 10c0/415511da3669e36e002820584e264997ffe277ff136643a3126cc949197e6ca3334d0f12d084e83b1994af2e9c8141275c741cf2b7da5a2ff62dd0cac26f76c4 + languageName: node + linkType: hard + +"is-weakmap@npm:^2.0.2": + version: 2.0.2 + resolution: "is-weakmap@npm:2.0.2" + checksum: 10c0/443c35bb86d5e6cc5929cd9c75a4024bb0fff9586ed50b092f94e700b89c43a33b186b76dbc6d54f3d3d09ece689ab38dcdc1af6a482cbe79c0f2da0a17f1299 + languageName: node + linkType: hard + +"is-weakref@npm:^1.0.2, is-weakref@npm:^1.1.1": + version: 1.1.1 + resolution: "is-weakref@npm:1.1.1" + dependencies: + call-bound: "npm:^1.0.3" + checksum: 10c0/8e0a9c07b0c780949a100e2cab2b5560a48ecd4c61726923c1a9b77b6ab0aa0046c9e7fb2206042296817045376dee2c8ab1dabe08c7c3dfbf195b01275a085b + languageName: node + linkType: hard + +"is-weakset@npm:^2.0.3": + version: 2.0.4 + resolution: "is-weakset@npm:2.0.4" + dependencies: + call-bound: "npm:^1.0.3" + get-intrinsic: "npm:^1.2.6" + checksum: 10c0/6491eba08acb8dc9532da23cb226b7d0192ede0b88f16199e592e4769db0a077119c1f5d2283d1e0d16d739115f70046e887e477eb0e66cd90e1bb29f28ba647 + languageName: node + linkType: hard + +"is-what@npm:^3.14.1": + version: 3.14.1 + resolution: "is-what@npm:3.14.1" + checksum: 10c0/4b770b85454c877b6929a84fd47c318e1f8c2ff70fd72fd625bc3fde8e0c18a6e57345b6e7aa1ee9fbd1c608d27cfe885df473036c5c2e40cd2187250804a2c7 + languageName: node + linkType: hard + +"is-what@npm:^4.1.8": + version: 4.1.16 + resolution: "is-what@npm:4.1.16" + checksum: 10c0/611f1947776826dcf85b57cfb7bd3b3ea6f4b94a9c2f551d4a53f653cf0cb9d1e6518846648256d46ee6c91d114b6d09d2ac8a07306f7430c5900f87466aae5b + languageName: node + linkType: hard + +"is-wsl@npm:^2.2.0": + version: 2.2.0 + resolution: "is-wsl@npm:2.2.0" + dependencies: + is-docker: "npm:^2.0.0" + checksum: 10c0/a6fa2d370d21be487c0165c7a440d567274fbba1a817f2f0bfa41cc5e3af25041d84267baa22df66696956038a43973e72fca117918c91431920bdef490fa25e + languageName: node + linkType: hard + +"isarray@npm:0.0.1": + version: 0.0.1 + resolution: "isarray@npm:0.0.1" + checksum: 10c0/ed1e62da617f71fe348907c71743b5ed550448b455f8d269f89a7c7ddb8ae6e962de3dab6a74a237b06f5eb7f6ece7a45ada8ce96d87fe972926530f91ae3311 + languageName: node + linkType: hard + +"isarray@npm:^1.0.0, isarray@npm:~1.0.0": + version: 1.0.0 + resolution: "isarray@npm:1.0.0" + checksum: 10c0/18b5be6669be53425f0b84098732670ed4e727e3af33bc7f948aac01782110eb9a18b3b329c5323bcdd3acdaae547ee077d3951317e7f133bff7105264b3003d + languageName: node + linkType: hard + +"isarray@npm:^2.0.5": + version: 2.0.5 + resolution: "isarray@npm:2.0.5" + checksum: 10c0/4199f14a7a13da2177c66c31080008b7124331956f47bca57dd0b6ea9f11687aa25e565a2c7a2b519bc86988d10398e3049a1f5df13c9f6b7664154690ae79fd + languageName: node + linkType: hard + +"isbinaryfile@npm:^3.0.2": + version: 3.0.3 + resolution: "isbinaryfile@npm:3.0.3" + dependencies: + buffer-alloc: "npm:^1.2.0" + checksum: 10c0/9f726a0fa083d28b568b0f137f214fa5b94e9497d0a2bcdf6370d0167333bba61e4e89f0f1841768706715bcc1c92d02d8123050503c5cc6621f89e65fb1cbed + languageName: node + linkType: hard + +"isbinaryfile@npm:^4.0.10": + version: 4.0.10 + resolution: "isbinaryfile@npm:4.0.10" + checksum: 10c0/0703d8cfeb69ed79e6d173120f327450011a066755150a6bbf97ffecec1069a5f2092777868315b21359098c84b54984871cad1abce877ad9141fb2caf3dcabf + languageName: node + linkType: hard + +"isexe@npm:^2.0.0": + version: 2.0.0 + resolution: "isexe@npm:2.0.0" + checksum: 10c0/228cfa503fadc2c31596ab06ed6aa82c9976eec2bfd83397e7eaf06d0ccf42cd1dfd6743bf9aeb01aebd4156d009994c5f76ea898d2832c1fe342da923ca457d + languageName: node + linkType: hard + +"isexe@npm:^3.1.1": + version: 3.1.1 + resolution: "isexe@npm:3.1.1" + checksum: 10c0/9ec257654093443eb0a528a9c8cbba9c0ca7616ccb40abd6dde7202734d96bb86e4ac0d764f0f8cd965856aacbff2f4ce23e730dc19dfb41e3b0d865ca6fdcc7 + languageName: node + linkType: hard + +"isobject@npm:^3.0.1": + version: 3.0.1 + resolution: "isobject@npm:3.0.1" + checksum: 10c0/03344f5064a82f099a0cd1a8a407f4c0d20b7b8485e8e816c39f249e9416b06c322e8dec5b842b6bb8a06de0af9cb48e7bc1b5352f0fadc2f0abac033db3d4db + languageName: node + linkType: hard + +"isomorphic-fetch@npm:^2.2.1": + version: 2.2.1 + resolution: "isomorphic-fetch@npm:2.2.1" + dependencies: + node-fetch: "npm:^1.0.1" + whatwg-fetch: "npm:>=0.10.0" + checksum: 10c0/ea9fd37d31ec7b35b82180e1946d4a2f512506d0559fa567ec6ee6701ff1c6d924be90e75499c50982274b707e03ecd9eaa21d618872dd0deff530e4c3bdb074 + languageName: node + linkType: hard + +"isomorphic-rslog@npm:0.0.7": + version: 0.0.7 + resolution: "isomorphic-rslog@npm:0.0.7" + checksum: 10c0/525b8155fc6d0e3c3c0ee44ec3a8f2d683c923365416d13a2f2bd550ba70d3fd1b5be73f88cd69f0af6c21bd8d26c90f73e2a9cf9d4889bbecabb8b0d2f93de2 + languageName: node + linkType: hard + +"isomorphic-unfetch@npm:4.0.2": + version: 4.0.2 + resolution: "isomorphic-unfetch@npm:4.0.2" + dependencies: + node-fetch: "npm:^3.2.0" + unfetch: "npm:^5.0.0" + checksum: 10c0/1727d85344818eaf798b569904f70313e8eafbc192d84400a3e646bb0b893a2e405727ee45ccac0fc3d41ee48561eaa5cdd55813131613d7f8a55031ed49103d + languageName: node + linkType: hard + +"istanbul-lib-coverage@npm:^3.2.0": + version: 3.2.2 + resolution: "istanbul-lib-coverage@npm:3.2.2" + checksum: 10c0/6c7ff2106769e5f592ded1fb418f9f73b4411fd5a084387a5410538332b6567cd1763ff6b6cadca9b9eb2c443cce2f7ea7d7f1b8d315f9ce58539793b1e0922b + languageName: node + linkType: hard + +"istanbul-lib-instrument@npm:^5.0.4": + version: 5.2.1 + resolution: "istanbul-lib-instrument@npm:5.2.1" + dependencies: + "@babel/core": "npm:^7.12.3" + "@babel/parser": "npm:^7.14.7" + "@istanbuljs/schema": "npm:^0.1.2" + istanbul-lib-coverage: "npm:^3.2.0" + semver: "npm:^6.3.0" + checksum: 10c0/8a1bdf3e377dcc0d33ec32fe2b6ecacdb1e4358fd0eb923d4326bb11c67622c0ceb99600a680f3dad5d29c66fc1991306081e339b4d43d0b8a2ab2e1d910a6ee + languageName: node + linkType: hard + +"iterator.prototype@npm:^1.1.4": + version: 1.1.5 + resolution: "iterator.prototype@npm:1.1.5" + dependencies: + define-data-property: "npm:^1.1.4" + es-object-atoms: "npm:^1.0.0" + get-intrinsic: "npm:^1.2.6" + get-proto: "npm:^1.0.0" + has-symbols: "npm:^1.1.0" + set-function-name: "npm:^2.0.2" + checksum: 10c0/f7a262808e1b41049ab55f1e9c29af7ec1025a000d243b83edf34ce2416eedd56079b117fa59376bb4a724110690f13aa8427f2ee29a09eec63a7e72367626d0 + languageName: node + linkType: hard + +"jackspeak@npm:^3.1.2": + version: 3.4.3 + resolution: "jackspeak@npm:3.4.3" + dependencies: + "@isaacs/cliui": "npm:^8.0.2" + "@pkgjs/parseargs": "npm:^0.11.0" + dependenciesMeta: + "@pkgjs/parseargs": + optional: true + checksum: 10c0/6acc10d139eaefdbe04d2f679e6191b3abf073f111edf10b1de5302c97ec93fffeb2fdd8681ed17f16268aa9dd4f8c588ed9d1d3bffbbfa6e8bf897cbb3149b9 + languageName: node + linkType: hard + +"jake@npm:^10.8.5": + version: 10.9.4 + resolution: "jake@npm:10.9.4" + dependencies: + async: "npm:^3.2.6" + filelist: "npm:^1.0.4" + picocolors: "npm:^1.1.1" + bin: + jake: bin/cli.js + checksum: 10c0/bb52f000340d4a32f1a3893b9abe56ef2b77c25da4dbf2c0c874a8159d082dddda50a5ad10e26060198bd645b928ba8dba3b362710f46a247e335321188c5a9c + languageName: node + linkType: hard + +"javascript-stringify@npm:^2.0.1": + version: 2.1.0 + resolution: "javascript-stringify@npm:2.1.0" + checksum: 10c0/374e74ebff29b94de78da39daa6e530999c58a145aeb293dc21180c4584459b14d9e5721d9bc6ed4eba319c437ef0145c157c946b70ecddcff6668682a002bcc + languageName: node + linkType: hard + +"jest-haste-map@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-haste-map@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + "@types/graceful-fs": "npm:^4.1.3" + "@types/node": "npm:*" + anymatch: "npm:^3.0.3" + fb-watchman: "npm:^2.0.0" + fsevents: "npm:^2.3.2" + graceful-fs: "npm:^4.2.9" + jest-regex-util: "npm:^29.6.3" + jest-util: "npm:^29.7.0" + jest-worker: "npm:^29.7.0" + micromatch: "npm:^4.0.4" + walker: "npm:^1.0.8" + dependenciesMeta: + fsevents: + optional: true + checksum: 10c0/2683a8f29793c75a4728787662972fedd9267704c8f7ef9d84f2beed9a977f1cf5e998c07b6f36ba5603f53cb010c911fe8cd0ac9886e073fe28ca66beefd30c + languageName: node + linkType: hard + +"jest-regex-util@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-regex-util@npm:29.6.3" + checksum: 10c0/4e33fb16c4f42111159cafe26397118dcfc4cf08bc178a67149fb05f45546a91928b820894572679d62559839d0992e21080a1527faad65daaae8743a5705a3b + languageName: node + linkType: hard + +"jest-util@npm:^29.4.3, jest-util@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-util@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + ci-info: "npm:^3.2.0" + graceful-fs: "npm:^4.2.9" + picomatch: "npm:^2.2.3" + checksum: 10c0/bc55a8f49fdbb8f51baf31d2a4f312fb66c9db1483b82f602c9c990e659cdd7ec529c8e916d5a89452ecbcfae4949b21b40a7a59d4ffc0cd813a973ab08c8150 + languageName: node + linkType: hard + +"jest-worker@npm:29.4.3": + version: 29.4.3 + resolution: "jest-worker@npm:29.4.3" + dependencies: + "@types/node": "npm:*" + jest-util: "npm:^29.4.3" + merge-stream: "npm:^2.0.0" + supports-color: "npm:^8.0.0" + checksum: 10c0/fae75c4e5c45f26838571fb86b15cac0b4d4af0a6ccb69a648a11d5661c52c31423f06fe907f329475d57f799cc3f63110679368d8c134393f537b090698b381 + languageName: node + linkType: hard + +"jest-worker@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-worker@npm:29.7.0" + dependencies: + "@types/node": "npm:*" + jest-util: "npm:^29.7.0" + merge-stream: "npm:^2.0.0" + supports-color: "npm:^8.0.0" + checksum: 10c0/5570a3a005b16f46c131968b8a5b56d291f9bbb85ff4217e31c80bd8a02e7de799e59a54b95ca28d5c302f248b54cbffde2d177c2f0f52ffcee7504c6eabf660 + languageName: node + linkType: hard + +"jiti@npm:^1.21.7": + version: 1.21.7 + resolution: "jiti@npm:1.21.7" + bin: + jiti: bin/jiti.js + checksum: 10c0/77b61989c758ff32407cdae8ddc77f85e18e1a13fc4977110dbd2e05fc761842f5f71bce684d9a01316e1c4263971315a111385759951080bbfe17cbb5de8f7a + languageName: node + linkType: hard + +"jiti@npm:^2.5.1": + version: 2.6.1 + resolution: "jiti@npm:2.6.1" + bin: + jiti: lib/jiti-cli.mjs + checksum: 10c0/79b2e96a8e623f66c1b703b98ec1b8be4500e1d217e09b09e343471bbb9c105381b83edbb979d01cef18318cc45ce6e153571b6c83122170eefa531c64b6789b + languageName: node + linkType: hard + +"js-cookie@npm:^3.0.5": + version: 3.0.5 + resolution: "js-cookie@npm:3.0.5" + checksum: 10c0/04a0e560407b4489daac3a63e231d35f4e86f78bff9d792011391b49c59f721b513411cd75714c418049c8dc9750b20fcddad1ca5a2ca616c3aca4874cce5b3a + languageName: node + linkType: hard + +"js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0": + version: 4.0.0 + resolution: "js-tokens@npm:4.0.0" + checksum: 10c0/e248708d377aa058eacf2037b07ded847790e6de892bbad3dac0abba2e759cb9f121b00099a65195616badcb6eca8d14d975cb3e89eb1cfda644756402c8aeed + languageName: node + linkType: hard + +"js-yaml@npm:^3.13.1": + version: 3.14.2 + resolution: "js-yaml@npm:3.14.2" + dependencies: + argparse: "npm:^1.0.7" + esprima: "npm:^4.0.0" + bin: + js-yaml: bin/js-yaml.js + checksum: 10c0/3261f25912f5dd76605e5993d0a126c2b6c346311885d3c483706cd722efe34f697ea0331f654ce27c00a42b426e524518ec89d65ed02ea47df8ad26dcc8ce69 + languageName: node + linkType: hard + +"js-yaml@npm:^4.1.0": + version: 4.1.1 + resolution: "js-yaml@npm:4.1.1" + dependencies: + argparse: "npm:^2.0.1" + bin: + js-yaml: bin/js-yaml.js + checksum: 10c0/561c7d7088c40a9bb53cc75becbfb1df6ae49b34b5e6e5a81744b14ae8667ec564ad2527709d1a6e7d5e5fa6d483aa0f373a50ad98d42fde368ec4a190d4fae7 + languageName: node + linkType: hard + +"jsesc@npm:^2.5.1": + version: 2.5.2 + resolution: "jsesc@npm:2.5.2" + bin: + jsesc: bin/jsesc + checksum: 10c0/dbf59312e0ebf2b4405ef413ec2b25abb5f8f4d9bc5fb8d9f90381622ebca5f2af6a6aa9a8578f65903f9e33990a6dc798edd0ce5586894bf0e9e31803a1de88 + languageName: node + linkType: hard + +"jsesc@npm:^3.0.2": + version: 3.1.0 + resolution: "jsesc@npm:3.1.0" + bin: + jsesc: bin/jsesc + checksum: 10c0/531779df5ec94f47e462da26b4cbf05eb88a83d9f08aac2ba04206508fc598527a153d08bd462bae82fc78b3eaa1a908e1a4a79f886e9238641c4cdefaf118b1 + languageName: node + linkType: hard + +"json-buffer@npm:3.0.1": + version: 3.0.1 + resolution: "json-buffer@npm:3.0.1" + checksum: 10c0/0d1c91569d9588e7eef2b49b59851f297f3ab93c7b35c7c221e288099322be6b562767d11e4821da500f3219542b9afd2e54c5dc573107c1126ed1080f8e96d7 + languageName: node + linkType: hard + +"json-parse-even-better-errors@npm:^2.3.0": + version: 2.3.1 + resolution: "json-parse-even-better-errors@npm:2.3.1" + checksum: 10c0/140932564c8f0b88455432e0f33c4cb4086b8868e37524e07e723f4eaedb9425bdc2bafd71bd1d9765bd15fd1e2d126972bc83990f55c467168c228c24d665f3 + languageName: node + linkType: hard + +"json-schema-traverse@npm:^0.4.1": + version: 0.4.1 + resolution: "json-schema-traverse@npm:0.4.1" + checksum: 10c0/108fa90d4cc6f08243aedc6da16c408daf81793bf903e9fd5ab21983cda433d5d2da49e40711da016289465ec2e62e0324dcdfbc06275a607fe3233fde4942ce + languageName: node + linkType: hard + +"json-schema-traverse@npm:^1.0.0": + version: 1.0.0 + resolution: "json-schema-traverse@npm:1.0.0" + checksum: 10c0/71e30015d7f3d6dc1c316d6298047c8ef98a06d31ad064919976583eb61e1018a60a0067338f0f79cabc00d84af3fcc489bd48ce8a46ea165d9541ba17fb30c6 + languageName: node + linkType: hard + +"json-stable-stringify-without-jsonify@npm:^1.0.1": + version: 1.0.1 + resolution: "json-stable-stringify-without-jsonify@npm:1.0.1" + checksum: 10c0/cb168b61fd4de83e58d09aaa6425ef71001bae30d260e2c57e7d09a5fd82223e2f22a042dedaab8db23b7d9ae46854b08bb1f91675a8be11c5cffebef5fb66a5 + languageName: node + linkType: hard + +"json-stringify-safe@npm:^5.0.1": + version: 5.0.1 + resolution: "json-stringify-safe@npm:5.0.1" + checksum: 10c0/7dbf35cd0411d1d648dceb6d59ce5857ec939e52e4afc37601aa3da611f0987d5cee5b38d58329ceddf3ed48bd7215229c8d52059ab01f2444a338bf24ed0f37 + languageName: node + linkType: hard + +"json2mq@npm:^0.2.0": + version: 0.2.0 + resolution: "json2mq@npm:0.2.0" + dependencies: + string-convert: "npm:^0.2.0" + checksum: 10c0/fc9e2f2306572522d3e61d246afdf70b56ca9ea32f4ad5924c30949867851ab59c926bd0ffc821ebb54d32f3e82e95225f3906eacdb3e54c1ad49acdadf7e0c7 + languageName: node + linkType: hard + +"json5@npm:^1.0.2": + version: 1.0.2 + resolution: "json5@npm:1.0.2" + dependencies: + minimist: "npm:^1.2.0" + bin: + json5: lib/cli.js + checksum: 10c0/9ee316bf21f000b00752e6c2a3b79ecf5324515a5c60ee88983a1910a45426b643a4f3461657586e8aeca87aaf96f0a519b0516d2ae527a6c3e7eed80f68717f + languageName: node + linkType: hard + +"json5@npm:^2.1.2, json5@npm:^2.2.0, json5@npm:^2.2.3": + version: 2.2.3 + resolution: "json5@npm:2.2.3" + bin: + json5: lib/cli.js + checksum: 10c0/5a04eed94810fa55c5ea138b2f7a5c12b97c3750bc63d11e511dcecbfef758003861522a070c2272764ee0f4e3e323862f386945aeb5b85b87ee43f084ba586c + languageName: node + linkType: hard + +"jsonfile@npm:^4.0.0": + version: 4.0.0 + resolution: "jsonfile@npm:4.0.0" + dependencies: + graceful-fs: "npm:^4.1.6" + dependenciesMeta: + graceful-fs: + optional: true + checksum: 10c0/7dc94b628d57a66b71fb1b79510d460d662eb975b5f876d723f81549c2e9cd316d58a2ddf742b2b93a4fa6b17b2accaf1a738a0e2ea114bdfb13a32e5377e480 + languageName: node + linkType: hard + +"jsonfile@npm:^6.0.1": + version: 6.2.0 + resolution: "jsonfile@npm:6.2.0" + dependencies: + graceful-fs: "npm:^4.1.6" + universalify: "npm:^2.0.0" + dependenciesMeta: + graceful-fs: + optional: true + checksum: 10c0/7f4f43b08d1869ded8a6822213d13ae3b99d651151d77efd1557ced0889c466296a7d9684e397bd126acf5eb2cfcb605808c3e681d0fdccd2fe5a04b47e76c0d + languageName: node + linkType: hard + +"jsx-ast-utils@npm:^2.4.1 || ^3.0.0": + version: 3.3.5 + resolution: "jsx-ast-utils@npm:3.3.5" + dependencies: + array-includes: "npm:^3.1.6" + array.prototype.flat: "npm:^1.3.1" + object.assign: "npm:^4.1.4" + object.values: "npm:^1.1.6" + checksum: 10c0/a32679e9cb55469cb6d8bbc863f7d631b2c98b7fc7bf172629261751a6e7bc8da6ae374ddb74d5fbd8b06cf0eb4572287b259813d92b36e384024ed35e4c13e1 + languageName: node + linkType: hard + +"keyboardevent-from-electron-accelerator@npm:^2.0.0": + version: 2.0.0 + resolution: "keyboardevent-from-electron-accelerator@npm:2.0.0" + checksum: 10c0/94bd9da6eb80145b36f336adb3f0a55cc8fdf0138f0df3028feb30d790d0727f8de27f040278805a499cc61dba816c8fab012e7f76c2495033d2fd7c2762f309 + languageName: node + linkType: hard + +"keyboardevents-areequal@npm:^0.2.1": + version: 0.2.2 + resolution: "keyboardevents-areequal@npm:0.2.2" + checksum: 10c0/1612c2aa52001163b2517ef6c0ea9abf20117e206e6796ba15eb99c0ae331a8826ab60a18e7ddb8ed0e15272af64d3b14383d2ae832beaa2f68548c1c53e4fa6 + languageName: node + linkType: hard + +"keyv@npm:^4.0.0, keyv@npm:^4.5.3": + version: 4.5.4 + resolution: "keyv@npm:4.5.4" + dependencies: + json-buffer: "npm:3.0.1" + checksum: 10c0/aa52f3c5e18e16bb6324876bb8b59dd02acf782a4b789c7b2ae21107fab95fab3890ed448d4f8dba80ce05391eeac4bfabb4f02a20221342982f806fa2cf271e + languageName: node + linkType: hard + +"keyv@npm:^5.6.0": + version: 5.6.0 + resolution: "keyv@npm:5.6.0" + dependencies: + "@keyv/serialize": "npm:^1.1.1" + checksum: 10c0/c3ea795b6e03593ca57c8f70928a69bad14c13389a7fb75649a115ff55615244b04d8902798d841c17f0bb4a8a8866c97133b543b93f151b440170bba09176db + languageName: node + linkType: hard + +"kind-of@npm:^6.0.2": + version: 6.0.3 + resolution: "kind-of@npm:6.0.3" + checksum: 10c0/61cdff9623dabf3568b6445e93e31376bee1cdb93f8ba7033d86022c2a9b1791a1d9510e026e6465ebd701a6dd2f7b0808483ad8838341ac52f003f512e0b4c4 + languageName: node + linkType: hard + +"kleur@npm:^4.0.3": + version: 4.1.5 + resolution: "kleur@npm:4.1.5" + checksum: 10c0/e9de6cb49657b6fa70ba2d1448fd3d691a5c4370d8f7bbf1c2f64c24d461270f2117e1b0afe8cb3114f13bbd8e51de158c2a224953960331904e636a5e4c0f2a + languageName: node + linkType: hard + +"kolorist@npm:^1.6.0": + version: 1.8.0 + resolution: "kolorist@npm:1.8.0" + checksum: 10c0/73075db44a692bf6c34a649f3b4b3aea4993b84f6b754cbf7a8577e7c7db44c0bad87752bd23b0ce533f49de2244ce2ce03b7b1b667a85ae170a94782cc50f9b + languageName: node + linkType: hard + +"lazy-val@npm:^1.0.4, lazy-val@npm:^1.0.5": + version: 1.0.5 + resolution: "lazy-val@npm:1.0.5" + checksum: 10c0/28ba7a0e704895a444eed47d110274090f485b991f2ea6fff2ab0878c529c53f60f2eb2d944cbbd68b91408e7455eabc62861c48289d4757fa9c818b97454f24 + languageName: node + linkType: hard + +"less-loader@npm:^12.0.0, less-loader@npm:^12.2.0": + version: 12.3.0 + resolution: "less-loader@npm:12.3.0" + peerDependencies: + "@rspack/core": 0.x || 1.x + less: ^3.5.0 || ^4.0.0 + webpack: ^5.0.0 + peerDependenciesMeta: + "@rspack/core": + optional: true + webpack: + optional: true + checksum: 10c0/11814ce601fe9a9a148f28643ffcb6041939b1142b21538c2c0a7a220f79e35f7eeffd4ac5f4d9495e41f1f25aabb98652fa18792d22eebb1d151716d8297332 + languageName: node + linkType: hard + +"less-plugin-resolve@npm:1.0.2": + version: 1.0.2 + resolution: "less-plugin-resolve@npm:1.0.2" + dependencies: + enhanced-resolve: "npm:^5.15.0" + checksum: 10c0/c38cab1d75c11c56de5b0d1bac0463f2ef45699f8da74f3b63b9d5ac53e80fc3ec8b41f5b6fa731a76c51c96e745db5621fe9877ff9dcc229dcc0e78dc6fa306 + languageName: node + linkType: hard + +"less@npm:4.1.3": + version: 4.1.3 + resolution: "less@npm:4.1.3" + dependencies: + copy-anything: "npm:^2.0.1" + errno: "npm:^0.1.1" + graceful-fs: "npm:^4.1.2" + image-size: "npm:~0.5.0" + make-dir: "npm:^2.1.0" + mime: "npm:^1.4.1" + needle: "npm:^3.1.0" + parse-node-version: "npm:^1.0.1" + source-map: "npm:~0.6.0" + tslib: "npm:^2.3.0" + dependenciesMeta: + errno: + optional: true + graceful-fs: + optional: true + image-size: + optional: true + make-dir: + optional: true + mime: + optional: true + needle: + optional: true + source-map: + optional: true + bin: + lessc: bin/lessc + checksum: 10c0/d67ca673a2c409a3069bb088c21976fa6a22eaf4428a23f486afa3ca57c2c004f424e7466dfc8d38a4dca25bc7b75943de5e3394d3a7841d8812cec696790e22 + languageName: node + linkType: hard + +"less@npm:^4.0.0, less@npm:^4.2.0": + version: 4.4.2 + resolution: "less@npm:4.4.2" + dependencies: + copy-anything: "npm:^2.0.1" + errno: "npm:^0.1.1" + graceful-fs: "npm:^4.1.2" + image-size: "npm:~0.5.0" + make-dir: "npm:^2.1.0" + mime: "npm:^1.4.1" + needle: "npm:^3.1.0" + parse-node-version: "npm:^1.0.1" + source-map: "npm:~0.6.0" + tslib: "npm:^2.3.0" + dependenciesMeta: + errno: + optional: true + graceful-fs: + optional: true + image-size: + optional: true + make-dir: + optional: true + mime: + optional: true + needle: + optional: true + source-map: + optional: true + bin: + lessc: bin/lessc + checksum: 10c0/f8b796e45ef171adc390b5250f3018922cd046c256181dd9d4cbcbbdc5d6de7cb88c8327741c10eff7ff76421cd826fd95a664ea1b88fbf6f31742428d4a2dab + languageName: node + linkType: hard + +"levn@npm:^0.4.1": + version: 0.4.1 + resolution: "levn@npm:0.4.1" + dependencies: + prelude-ls: "npm:^1.2.1" + type-check: "npm:~0.4.0" + checksum: 10c0/effb03cad7c89dfa5bd4f6989364bfc79994c2042ec5966cb9b95990e2edee5cd8969ddf42616a0373ac49fac1403437deaf6e9050fbbaa3546093a59b9ac94e + languageName: node + linkType: hard + +"lightningcss-darwin-arm64@npm:1.22.1": + version: 1.22.1 + resolution: "lightningcss-darwin-arm64@npm:1.22.1" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"lightningcss-darwin-x64@npm:1.22.1": + version: 1.22.1 + resolution: "lightningcss-darwin-x64@npm:1.22.1" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"lightningcss-freebsd-x64@npm:1.22.1": + version: 1.22.1 + resolution: "lightningcss-freebsd-x64@npm:1.22.1" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"lightningcss-linux-arm-gnueabihf@npm:1.22.1": + version: 1.22.1 + resolution: "lightningcss-linux-arm-gnueabihf@npm:1.22.1" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"lightningcss-linux-arm64-gnu@npm:1.22.1": + version: 1.22.1 + resolution: "lightningcss-linux-arm64-gnu@npm:1.22.1" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"lightningcss-linux-arm64-musl@npm:1.22.1": + version: 1.22.1 + resolution: "lightningcss-linux-arm64-musl@npm:1.22.1" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"lightningcss-linux-x64-gnu@npm:1.22.1": + version: 1.22.1 + resolution: "lightningcss-linux-x64-gnu@npm:1.22.1" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"lightningcss-linux-x64-musl@npm:1.22.1": + version: 1.22.1 + resolution: "lightningcss-linux-x64-musl@npm:1.22.1" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"lightningcss-win32-x64-msvc@npm:1.22.1": + version: 1.22.1 + resolution: "lightningcss-win32-x64-msvc@npm:1.22.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"lightningcss@npm:1.22.1": + version: 1.22.1 + resolution: "lightningcss@npm:1.22.1" + dependencies: + detect-libc: "npm:^1.0.3" + lightningcss-darwin-arm64: "npm:1.22.1" + lightningcss-darwin-x64: "npm:1.22.1" + lightningcss-freebsd-x64: "npm:1.22.1" + lightningcss-linux-arm-gnueabihf: "npm:1.22.1" + lightningcss-linux-arm64-gnu: "npm:1.22.1" + lightningcss-linux-arm64-musl: "npm:1.22.1" + lightningcss-linux-x64-gnu: "npm:1.22.1" + lightningcss-linux-x64-musl: "npm:1.22.1" + lightningcss-win32-x64-msvc: "npm:1.22.1" + dependenciesMeta: + lightningcss-darwin-arm64: + optional: true + lightningcss-darwin-x64: + optional: true + lightningcss-freebsd-x64: + optional: true + lightningcss-linux-arm-gnueabihf: + optional: true + lightningcss-linux-arm64-gnu: + optional: true + lightningcss-linux-arm64-musl: + optional: true + lightningcss-linux-x64-gnu: + optional: true + lightningcss-linux-x64-musl: + optional: true + lightningcss-win32-x64-msvc: + optional: true + checksum: 10c0/b1e5f740b729bb786222b2bed1e87ef059c30bf31998aea284a51de6328d0ff51345713574a721f2b6e7fbb5893721fae9754d029fc0642150adc3548862c8e5 + languageName: node + linkType: hard + +"lilconfig@npm:^3.1.1, lilconfig@npm:^3.1.3": + version: 3.1.3 + resolution: "lilconfig@npm:3.1.3" + checksum: 10c0/f5604e7240c5c275743561442fbc5abf2a84ad94da0f5adc71d25e31fa8483048de3dcedcb7a44112a942fed305fd75841cdf6c9681c7f640c63f1049e9a5dcc + languageName: node + linkType: hard + +"lines-and-columns@npm:^1.1.6": + version: 1.2.4 + resolution: "lines-and-columns@npm:1.2.4" + checksum: 10c0/3da6ee62d4cd9f03f5dc90b4df2540fb85b352081bee77fe4bbcd12c9000ead7f35e0a38b8d09a9bb99b13223446dd8689ff3c4959807620726d788701a83d2d + languageName: node + linkType: hard + +"loader-runner@npm:^4.3.0": + version: 4.3.1 + resolution: "loader-runner@npm:4.3.1" + checksum: 10c0/a523b6329f114e0a98317158e30a7dfce044b731521be5399464010472a93a15ece44757d1eaed1d8845019869c5390218bc1c7c3110f4eeaef5157394486eac + languageName: node + linkType: hard + +"loader-utils@npm:^2.0.0, loader-utils@npm:^2.0.2, loader-utils@npm:^2.0.4": + version: 2.0.4 + resolution: "loader-utils@npm:2.0.4" + dependencies: + big.js: "npm:^5.2.2" + emojis-list: "npm:^3.0.0" + json5: "npm:^2.1.2" + checksum: 10c0/d5654a77f9d339ec2a03d88221a5a695f337bf71eb8dea031b3223420bb818964ba8ed0069145c19b095f6c8b8fd386e602a3fc7ca987042bd8bb1dcc90d7100 + languageName: node + linkType: hard + +"loader-utils@npm:^3.3.1": + version: 3.3.1 + resolution: "loader-utils@npm:3.3.1" + checksum: 10c0/f2af4eb185ac5bf7e56e1337b666f90744e9f443861ac521b48f093fb9e8347f191c8960b4388a3365147d218913bc23421234e7788db69f385bacfefa0b4758 + languageName: node + linkType: hard + +"local-pkg@npm:^0.4.2": + version: 0.4.3 + resolution: "local-pkg@npm:0.4.3" + checksum: 10c0/361c77d7873a629f09c9e86128926227171ee0fe3435d282fb80303ff255bb4d3c053b555d47e953b4f41d2561f2a7bc0e53e9ca5c9bc9607226a77c91ea4994 + languageName: node + linkType: hard + +"locate-path@npm:^5.0.0": + version: 5.0.0 + resolution: "locate-path@npm:5.0.0" + dependencies: + p-locate: "npm:^4.1.0" + checksum: 10c0/33a1c5247e87e022f9713e6213a744557a3e9ec32c5d0b5efb10aa3a38177615bf90221a5592674857039c1a0fd2063b82f285702d37b792d973e9e72ace6c59 + languageName: node + linkType: hard + +"locate-path@npm:^6.0.0": + version: 6.0.0 + resolution: "locate-path@npm:6.0.0" + dependencies: + p-locate: "npm:^5.0.0" + checksum: 10c0/d3972ab70dfe58ce620e64265f90162d247e87159b6126b01314dd67be43d50e96a50b517bce2d9452a79409c7614054c277b5232377de50416564a77ac7aad3 + languageName: node + linkType: hard + +"lodash-es@npm:^4.17.21": + version: 4.17.21 + resolution: "lodash-es@npm:4.17.21" + checksum: 10c0/fb407355f7e6cd523a9383e76e6b455321f0f153a6c9625e21a8827d10c54c2a2341bd2ae8d034358b60e07325e1330c14c224ff582d04612a46a4f0479ff2f2 + languageName: node + linkType: hard + +"lodash.debounce@npm:^4.0.8": + version: 4.0.8 + resolution: "lodash.debounce@npm:4.0.8" + checksum: 10c0/762998a63e095412b6099b8290903e0a8ddcb353ac6e2e0f2d7e7d03abd4275fe3c689d88960eb90b0dde4f177554d51a690f22a343932ecbc50a5d111849987 + languageName: node + linkType: hard + +"lodash.merge@npm:^4.6.2": + version: 4.6.2 + resolution: "lodash.merge@npm:4.6.2" + checksum: 10c0/402fa16a1edd7538de5b5903a90228aa48eb5533986ba7fa26606a49db2572bf414ff73a2c9f5d5fd36b31c46a5d5c7e1527749c07cbcf965ccff5fbdf32c506 + languageName: node + linkType: hard + +"lodash.throttle@npm:^4.1.1": + version: 4.1.1 + resolution: "lodash.throttle@npm:4.1.1" + checksum: 10c0/14628013e9e7f65ac904fc82fd8ecb0e55a9c4c2416434b1dd9cf64ae70a8937f0b15376a39a68248530adc64887ed0fe2b75204b2c9ec3eea1cb2d66ddd125d + languageName: node + linkType: hard + +"lodash.truncate@npm:^4.4.2": + version: 4.4.2 + resolution: "lodash.truncate@npm:4.4.2" + checksum: 10c0/4e870d54e8a6c86c8687e057cec4069d2e941446ccab7f40b4d9555fa5872d917d0b6aa73bece7765500a3123f1723bcdba9ae881b679ef120bba9e1a0b0ed70 + languageName: node + linkType: hard + +"lodash@npm:^4.0.1, lodash@npm:^4.17.10, lodash@npm:^4.17.11, lodash@npm:^4.17.15, lodash@npm:^4.17.20, lodash@npm:^4.17.21": + version: 4.17.21 + resolution: "lodash@npm:4.17.21" + checksum: 10c0/d8cbea072bb08655bb4c989da418994b073a608dffa608b09ac04b43a791b12aeae7cd7ad919aa4c925f33b48490b5cfe6c1f71d827956071dae2e7bb3a6b74c + languageName: node + linkType: hard + +"longest-streak@npm:^3.0.0": + version: 3.1.0 + resolution: "longest-streak@npm:3.1.0" + checksum: 10c0/7c2f02d0454b52834d1bcedef79c557bd295ee71fdabb02d041ff3aa9da48a90b5df7c0409156dedbc4df9b65da18742652aaea4759d6ece01f08971af6a7eaa + languageName: node + linkType: hard + +"loose-envify@npm:^1.0.0, loose-envify@npm:^1.1.0, loose-envify@npm:^1.4.0": + version: 1.4.0 + resolution: "loose-envify@npm:1.4.0" + dependencies: + js-tokens: "npm:^3.0.0 || ^4.0.0" + bin: + loose-envify: cli.js + checksum: 10c0/655d110220983c1a4b9c0c679a2e8016d4b67f6e9c7b5435ff5979ecdb20d0813f4dec0a08674fcbdd4846a3f07edbb50a36811fd37930b94aaa0d9daceb017e + languageName: node + linkType: hard + +"lower-case@npm:^2.0.2": + version: 2.0.2 + resolution: "lower-case@npm:2.0.2" + dependencies: + tslib: "npm:^2.0.3" + checksum: 10c0/3d925e090315cf7dc1caa358e0477e186ffa23947740e4314a7429b6e62d72742e0bbe7536a5ae56d19d7618ce998aba05caca53c2902bd5742fdca5fc57fd7b + languageName: node + linkType: hard + +"lowercase-keys@npm:^2.0.0": + version: 2.0.0 + resolution: "lowercase-keys@npm:2.0.0" + checksum: 10c0/f82a2b3568910509da4b7906362efa40f5b54ea14c2584778ddb313226f9cbf21020a5db35f9b9a0e95847a9b781d548601f31793d736b22a2b8ae8eb9ab1082 + languageName: node + linkType: hard + +"lru-cache@npm:^10.2.0": + version: 10.4.3 + resolution: "lru-cache@npm:10.4.3" + checksum: 10c0/ebd04fbca961e6c1d6c0af3799adcc966a1babe798f685bb84e6599266599cd95d94630b10262f5424539bc4640107e8a33aa28585374abf561d30d16f4b39fb + languageName: node + linkType: hard + +"lru-cache@npm:^11.0.0, lru-cache@npm:^11.1.0, lru-cache@npm:^11.2.1": + version: 11.2.2 + resolution: "lru-cache@npm:11.2.2" + checksum: 10c0/72d7831bbebc85e2bdefe01047ee5584db69d641c48d7a509e86f66f6ee111b30af7ec3bd68a967d47b69a4b1fa8bbf3872630bd06a63b6735e6f0a5f1c8e83d + languageName: node + linkType: hard + +"lru-cache@npm:^5.1.1": + version: 5.1.1 + resolution: "lru-cache@npm:5.1.1" + dependencies: + yallist: "npm:^3.0.2" + checksum: 10c0/89b2ef2ef45f543011e38737b8a8622a2f8998cddf0e5437174ef8f1f70a8b9d14a918ab3e232cb3ba343b7abddffa667f0b59075b2b80e6b4d63c3de6127482 + languageName: node + linkType: hard + +"lru-cache@npm:^6.0.0": + version: 6.0.0 + resolution: "lru-cache@npm:6.0.0" + dependencies: + yallist: "npm:^4.0.0" + checksum: 10c0/cb53e582785c48187d7a188d3379c181b5ca2a9c78d2bce3e7dee36f32761d1c42983da3fe12b55cb74e1779fa94cdc2e5367c028a9b35317184ede0c07a30a9 + languageName: node + linkType: hard + +"make-dir@npm:^2.1.0": + version: 2.1.0 + resolution: "make-dir@npm:2.1.0" + dependencies: + pify: "npm:^4.0.1" + semver: "npm:^5.6.0" + checksum: 10c0/ada869944d866229819735bee5548944caef560d7a8536ecbc6536edca28c72add47cc4f6fc39c54fb25d06b58da1f8994cf7d9df7dadea047064749efc085d8 + languageName: node + linkType: hard + +"make-fetch-happen@npm:^15.0.0": + version: 15.0.3 + resolution: "make-fetch-happen@npm:15.0.3" + dependencies: + "@npmcli/agent": "npm:^4.0.0" + cacache: "npm:^20.0.1" + http-cache-semantics: "npm:^4.1.1" + minipass: "npm:^7.0.2" + minipass-fetch: "npm:^5.0.0" + minipass-flush: "npm:^1.0.5" + minipass-pipeline: "npm:^1.2.4" + negotiator: "npm:^1.0.0" + proc-log: "npm:^6.0.0" + promise-retry: "npm:^2.0.1" + ssri: "npm:^13.0.0" + checksum: 10c0/525f74915660be60b616bcbd267c4a5b59481b073ba125e45c9c3a041bb1a47a2bd0ae79d028eb6f5f95bf9851a4158423f5068539c3093621abb64027e8e461 + languageName: node + linkType: hard + +"makeerror@npm:1.0.12": + version: 1.0.12 + resolution: "makeerror@npm:1.0.12" + dependencies: + tmpl: "npm:1.0.5" + checksum: 10c0/b0e6e599780ce6bab49cc413eba822f7d1f0dfebd1c103eaa3785c59e43e22c59018323cf9e1708f0ef5329e94a745d163fcbb6bff8e4c6742f9be9e86f3500c + languageName: node + linkType: hard + +"markdown-it-link-attributes@npm:^4.0.1": + version: 4.0.1 + resolution: "markdown-it-link-attributes@npm:4.0.1" + checksum: 10c0/2033214ca0af1c94bc9493c2f3a7c6e5e73cec9f6655e043bc14e256b23c70bac768525ca4723f40e6ac850b36d43b64285b21ca03fedcb2f3436ef0c96355e3 + languageName: node + linkType: hard + +"markdown-table@npm:^3.0.0": + version: 3.0.4 + resolution: "markdown-table@npm:3.0.4" + checksum: 10c0/1257b31827629a54c24a5030a3dac952256c559174c95ce3ef89bebd6bff0cb1444b1fd667b1a1bb53307f83278111505b3e26f0c4e7b731e0060d435d2d930b + languageName: node + linkType: hard + +"matcher@npm:^3.0.0": + version: 3.0.0 + resolution: "matcher@npm:3.0.0" + dependencies: + escape-string-regexp: "npm:^4.0.0" + checksum: 10c0/2edf24194a2879690bcdb29985fc6bc0d003df44e04df21ebcac721fa6ce2f6201c579866bb92f9380bffe946f11ecd8cd31f34117fb67ebf8aca604918e127e + languageName: node + linkType: hard + +"math-intrinsics@npm:^1.1.0": + version: 1.1.0 + resolution: "math-intrinsics@npm:1.1.0" + checksum: 10c0/7579ff94e899e2f76ab64491d76cf606274c874d8f2af4a442c016bd85688927fcfca157ba6bf74b08e9439dc010b248ce05b96cc7c126a354c3bae7fcb48b7f + languageName: node + linkType: hard + +"mathml-tag-names@npm:^4.0.0": + version: 4.0.0 + resolution: "mathml-tag-names@npm:4.0.0" + checksum: 10c0/2e928554c61b5e502ee551a23bb4157b99ffd042578e28fb4038ee67d19ea2b1a79c34a3c2ab1611437f668f6b29436e1c8f6fdc20eaf3c88d511ce5b8954fe8 + languageName: node + linkType: hard + +"md5.js@npm:^1.3.4": + version: 1.3.5 + resolution: "md5.js@npm:1.3.5" + dependencies: + hash-base: "npm:^3.0.0" + inherits: "npm:^2.0.1" + safe-buffer: "npm:^5.1.2" + checksum: 10c0/b7bd75077f419c8e013fc4d4dada48be71882e37d69a44af65a2f2804b91e253441eb43a0614423a1c91bb830b8140b0dc906bc797245e2e275759584f4efcc5 + languageName: node + linkType: hard + +"mdast-util-definitions@npm:^5.0.0": + version: 5.1.2 + resolution: "mdast-util-definitions@npm:5.1.2" + dependencies: + "@types/mdast": "npm:^3.0.0" + "@types/unist": "npm:^2.0.0" + unist-util-visit: "npm:^4.0.0" + checksum: 10c0/da9049c15562e44ee4ea4a36113d98c6c9eaa3d8a17d6da2aef6a0626376dcd01d9ec007d77a8dfcad6d0cbd5c32a4abbad72a3f48c3172a55934c7d9a916480 + languageName: node + linkType: hard + +"mdast-util-find-and-replace@npm:^2.0.0": + version: 2.2.2 + resolution: "mdast-util-find-and-replace@npm:2.2.2" + dependencies: + "@types/mdast": "npm:^3.0.0" + escape-string-regexp: "npm:^5.0.0" + unist-util-is: "npm:^5.0.0" + unist-util-visit-parents: "npm:^5.0.0" + checksum: 10c0/ce935f4bd4aeab47f91531a7f09dfab89aaeea62ad31029b43185c5b626921357703d8e5093c13073c097fdabfc57cb2f884d7dfad83dbe7239e351375d6797c + languageName: node + linkType: hard + +"mdast-util-from-markdown@npm:^1.0.0": + version: 1.3.1 + resolution: "mdast-util-from-markdown@npm:1.3.1" + dependencies: + "@types/mdast": "npm:^3.0.0" + "@types/unist": "npm:^2.0.0" + decode-named-character-reference: "npm:^1.0.0" + mdast-util-to-string: "npm:^3.1.0" + micromark: "npm:^3.0.0" + micromark-util-decode-numeric-character-reference: "npm:^1.0.0" + micromark-util-decode-string: "npm:^1.0.0" + micromark-util-normalize-identifier: "npm:^1.0.0" + micromark-util-symbol: "npm:^1.0.0" + micromark-util-types: "npm:^1.0.0" + unist-util-stringify-position: "npm:^3.0.0" + uvu: "npm:^0.5.0" + checksum: 10c0/f4e901bf2a2e93fe35a339e0cff581efacce2f7117cd5652e9a270847bd7e2508b3e717b7b4156af54d4f896d63033e06ff9fafbf59a1d46fe17dd5e2a3f7846 + languageName: node + linkType: hard + +"mdast-util-gfm-autolink-literal@npm:^1.0.0": + version: 1.0.3 + resolution: "mdast-util-gfm-autolink-literal@npm:1.0.3" + dependencies: + "@types/mdast": "npm:^3.0.0" + ccount: "npm:^2.0.0" + mdast-util-find-and-replace: "npm:^2.0.0" + micromark-util-character: "npm:^1.0.0" + checksum: 10c0/750e312eae73c3f2e8aa0e8c5232cb1b905357ff37ac236927f1af50cdbee7c2cfe2379b148ac32fa4137eeb3b24601e1bb6135084af926c7cd808867804193f + languageName: node + linkType: hard + +"mdast-util-gfm-footnote@npm:^1.0.0": + version: 1.0.2 + resolution: "mdast-util-gfm-footnote@npm:1.0.2" + dependencies: + "@types/mdast": "npm:^3.0.0" + mdast-util-to-markdown: "npm:^1.3.0" + micromark-util-normalize-identifier: "npm:^1.0.0" + checksum: 10c0/767973e46b9e2ae44e80e51a5e38ad0b032fc7f06a1a3095aa96c2886ba333941c764474a56b82e7db05efc56242a4789bc7fbbcc753d61512750e86a4192fe8 + languageName: node + linkType: hard + +"mdast-util-gfm-strikethrough@npm:^1.0.0": + version: 1.0.3 + resolution: "mdast-util-gfm-strikethrough@npm:1.0.3" + dependencies: + "@types/mdast": "npm:^3.0.0" + mdast-util-to-markdown: "npm:^1.3.0" + checksum: 10c0/29616b3dfdd33d3cd13f9b3181a8562fa2fbacfcb04a37dba3c690ba6829f0231b145444de984726d9277b2bc90dd7d96fb9df9f6292d5e77d65a8659ee2f52b + languageName: node + linkType: hard + +"mdast-util-gfm-table@npm:^1.0.0": + version: 1.0.7 + resolution: "mdast-util-gfm-table@npm:1.0.7" + dependencies: + "@types/mdast": "npm:^3.0.0" + markdown-table: "npm:^3.0.0" + mdast-util-from-markdown: "npm:^1.0.0" + mdast-util-to-markdown: "npm:^1.3.0" + checksum: 10c0/a37a05a936292c4f48394123332d3c034a6e1b15bb3e7f3b94e6bce3260c9184fd388abbc4100827edd5485a6563098306994d15a729bde3c96de7a62ed5720b + languageName: node + linkType: hard + +"mdast-util-gfm-task-list-item@npm:^1.0.0": + version: 1.0.2 + resolution: "mdast-util-gfm-task-list-item@npm:1.0.2" + dependencies: + "@types/mdast": "npm:^3.0.0" + mdast-util-to-markdown: "npm:^1.3.0" + checksum: 10c0/91fa91f7d1a8797bf129008dab12d23917015ad12df00044e275b4459e8b383fbec6234338953a0089ef9c3a114d0a360c3e652eb0ebf6ece7e7a8fd3b5977c6 + languageName: node + linkType: hard + +"mdast-util-gfm@npm:^2.0.0": + version: 2.0.2 + resolution: "mdast-util-gfm@npm:2.0.2" + dependencies: + mdast-util-from-markdown: "npm:^1.0.0" + mdast-util-gfm-autolink-literal: "npm:^1.0.0" + mdast-util-gfm-footnote: "npm:^1.0.0" + mdast-util-gfm-strikethrough: "npm:^1.0.0" + mdast-util-gfm-table: "npm:^1.0.0" + mdast-util-gfm-task-list-item: "npm:^1.0.0" + mdast-util-to-markdown: "npm:^1.0.0" + checksum: 10c0/5b7f7f98a90a2962d7e0787e080c4e55b70119100c7685bbdb772d8d7865524aeffd1757edba5afba434250e0246b987c0617c2c635baaf51c26dbbb3b72dbec + languageName: node + linkType: hard + +"mdast-util-phrasing@npm:^3.0.0": + version: 3.0.1 + resolution: "mdast-util-phrasing@npm:3.0.1" + dependencies: + "@types/mdast": "npm:^3.0.0" + unist-util-is: "npm:^5.0.0" + checksum: 10c0/5e00e303652a7581593549dbce20dfb69d687d79a972f7928f6ca1920ef5385bceb737a3d5292ab6d937ed8c67bb59771e80e88f530b78734fe7d155f833e32b + languageName: node + linkType: hard + +"mdast-util-to-hast@npm:^12.1.0": + version: 12.3.0 + resolution: "mdast-util-to-hast@npm:12.3.0" + dependencies: + "@types/hast": "npm:^2.0.0" + "@types/mdast": "npm:^3.0.0" + mdast-util-definitions: "npm:^5.0.0" + micromark-util-sanitize-uri: "npm:^1.1.0" + trim-lines: "npm:^3.0.0" + unist-util-generated: "npm:^2.0.0" + unist-util-position: "npm:^4.0.0" + unist-util-visit: "npm:^4.0.0" + checksum: 10c0/0753e45bfcce423f7a13979ac720a23ed8d6bafed174c387f43bbe8baf3838f3a043cd8006975b71e5c4068b7948f83f1348acea79801101af31eaec4e7a499a + languageName: node + linkType: hard + +"mdast-util-to-markdown@npm:^1.0.0, mdast-util-to-markdown@npm:^1.3.0": + version: 1.5.0 + resolution: "mdast-util-to-markdown@npm:1.5.0" + dependencies: + "@types/mdast": "npm:^3.0.0" + "@types/unist": "npm:^2.0.0" + longest-streak: "npm:^3.0.0" + mdast-util-phrasing: "npm:^3.0.0" + mdast-util-to-string: "npm:^3.0.0" + micromark-util-decode-string: "npm:^1.0.0" + unist-util-visit: "npm:^4.0.0" + zwitch: "npm:^2.0.0" + checksum: 10c0/9831d14aa6c097750a90c7b87b4e814b040731c30606a794c9b136dc746633dd9ec07154ca97d4fec4eaf732cf89d14643424e2581732d6ee18c9b0e51ff7664 + languageName: node + linkType: hard + +"mdast-util-to-string@npm:^3.0.0, mdast-util-to-string@npm:^3.1.0": + version: 3.2.0 + resolution: "mdast-util-to-string@npm:3.2.0" + dependencies: + "@types/mdast": "npm:^3.0.0" + checksum: 10c0/112f4bf0f6758dcb95deffdcf37afba7eaecdfe2ee13252de031723094d4d55220e147326690a8b91244758e2d678e7aeb1fdd0fa6ef3317c979bc42effd9a21 + languageName: node + linkType: hard + +"mdn-data@npm:2.0.14": + version: 2.0.14 + resolution: "mdn-data@npm:2.0.14" + checksum: 10c0/67241f8708c1e665a061d2b042d2d243366e93e5bf1f917693007f6d55111588b952dcbfd3ea9c2d0969fb754aad81b30fdcfdcc24546495fc3b24336b28d4bd + languageName: node + linkType: hard + +"mdn-data@npm:2.27.1": + version: 2.27.1 + resolution: "mdn-data@npm:2.27.1" + checksum: 10c0/eb8abf5d22e4d1e090346f5e81b67d23cef14c83940e445da5c44541ad874dc8fb9f6ca236e8258c3a489d9fb5884188a4d7d58773adb9089ac2c0b966796393 + languageName: node + linkType: hard + +"media-typer@npm:0.3.0": + version: 0.3.0 + resolution: "media-typer@npm:0.3.0" + checksum: 10c0/d160f31246907e79fed398470285f21bafb45a62869dc469b1c8877f3f064f5eabc4bcc122f9479b8b605bc5c76187d7871cf84c4ee3ecd3e487da1993279928 + languageName: node + linkType: hard + +"memfs@npm:^3.4.1": + version: 3.5.3 + resolution: "memfs@npm:3.5.3" + dependencies: + fs-monkey: "npm:^1.0.4" + checksum: 10c0/038fc81bce17ea92dde15aaa68fa0fdaf4960c721ce3ffc7c2cb87a259333f5159784ea48b3b72bf9e054254d9d0d0d5209d0fdc3d07d08653a09933b168fbd7 + languageName: node + linkType: hard + +"memory-fs@npm:^0.2.0": + version: 0.2.0 + resolution: "memory-fs@npm:0.2.0" + checksum: 10c0/bef3dffddded62258f7f9075fc13cb119d4f0cadd1379c12cc39dd4d2173acda37c05f292e28c6e5661817e492030282da8d8920b63753bc0bde81d240f4241e + languageName: node + linkType: hard + +"meow@npm:^14.1.0": + version: 14.1.0 + resolution: "meow@npm:14.1.0" + checksum: 10c0/f0ca4bb4fd08e4b9470fcbb7332deb61d72d40d4bda18ffb87c1a98e5014c0b44749ae9f0cab18fa532e26d61cef5d453831f9ae23ac09fa8ea0e0469be73ebc + languageName: node + linkType: hard + +"merge-descriptors@npm:1.0.3": + version: 1.0.3 + resolution: "merge-descriptors@npm:1.0.3" + checksum: 10c0/866b7094afd9293b5ea5dcd82d71f80e51514bed33b4c4e9f516795dc366612a4cbb4dc94356e943a8a6914889a914530badff27f397191b9b75cda20b6bae93 + languageName: node + linkType: hard + +"merge-stream@npm:^2.0.0": + version: 2.0.0 + resolution: "merge-stream@npm:2.0.0" + checksum: 10c0/867fdbb30a6d58b011449b8885601ec1690c3e41c759ecd5a9d609094f7aed0096c37823ff4a7190ef0b8f22cc86beb7049196ff68c016e3b3c671d0dac91ce5 + languageName: node + linkType: hard + +"merge2@npm:^1.3.0, merge2@npm:^1.4.1": + version: 1.4.1 + resolution: "merge2@npm:1.4.1" + checksum: 10c0/254a8a4605b58f450308fc474c82ac9a094848081bf4c06778200207820e5193726dc563a0d2c16468810516a5c97d9d3ea0ca6585d23c58ccfff2403e8dbbeb + languageName: node + linkType: hard + +"methods@npm:~1.1.2": + version: 1.1.2 + resolution: "methods@npm:1.1.2" + checksum: 10c0/bdf7cc72ff0a33e3eede03708c08983c4d7a173f91348b4b1e4f47d4cdbf734433ad971e7d1e8c77247d9e5cd8adb81ea4c67b0a2db526b758b2233d7814b8b2 + languageName: node + linkType: hard + +"micromark-core-commonmark@npm:^1.0.0, micromark-core-commonmark@npm:^1.0.1": + version: 1.1.0 + resolution: "micromark-core-commonmark@npm:1.1.0" + dependencies: + decode-named-character-reference: "npm:^1.0.0" + micromark-factory-destination: "npm:^1.0.0" + micromark-factory-label: "npm:^1.0.0" + micromark-factory-space: "npm:^1.0.0" + micromark-factory-title: "npm:^1.0.0" + micromark-factory-whitespace: "npm:^1.0.0" + micromark-util-character: "npm:^1.0.0" + micromark-util-chunked: "npm:^1.0.0" + micromark-util-classify-character: "npm:^1.0.0" + micromark-util-html-tag-name: "npm:^1.0.0" + micromark-util-normalize-identifier: "npm:^1.0.0" + micromark-util-resolve-all: "npm:^1.0.0" + micromark-util-subtokenize: "npm:^1.0.0" + micromark-util-symbol: "npm:^1.0.0" + micromark-util-types: "npm:^1.0.1" + uvu: "npm:^0.5.0" + checksum: 10c0/b3bf7b7004ce7dbb3ae151dcca4db1d12546f1b943affb2418da4b90b9ce59357373c433ee2eea4c868aee0791dafa355aeed19f5ef2b0acaf271f32f1ecbe6a + languageName: node + linkType: hard + +"micromark-extension-gfm-autolink-literal@npm:^1.0.0": + version: 1.0.5 + resolution: "micromark-extension-gfm-autolink-literal@npm:1.0.5" + dependencies: + micromark-util-character: "npm:^1.0.0" + micromark-util-sanitize-uri: "npm:^1.0.0" + micromark-util-symbol: "npm:^1.0.0" + micromark-util-types: "npm:^1.0.0" + checksum: 10c0/4964a52605ac36d24501d427e2d173fa39b5e0402275cb45068eba4898f4cb9cc57f7007b21b7514f0ab5f7b371b1701a5156a10b6ac8e77a7f36e830cf481d4 + languageName: node + linkType: hard + +"micromark-extension-gfm-footnote@npm:^1.0.0": + version: 1.1.2 + resolution: "micromark-extension-gfm-footnote@npm:1.1.2" + dependencies: + micromark-core-commonmark: "npm:^1.0.0" + micromark-factory-space: "npm:^1.0.0" + micromark-util-character: "npm:^1.0.0" + micromark-util-normalize-identifier: "npm:^1.0.0" + micromark-util-sanitize-uri: "npm:^1.0.0" + micromark-util-symbol: "npm:^1.0.0" + micromark-util-types: "npm:^1.0.0" + uvu: "npm:^0.5.0" + checksum: 10c0/b8090876cc3da5436c6253b0b40e39ceaa470c2429f699c19ee4163cef3102c4cd16c4ac2ec8caf916037fad310cfb52a9ef182c75d50fca7419ba08faad9b39 + languageName: node + linkType: hard + +"micromark-extension-gfm-strikethrough@npm:^1.0.0": + version: 1.0.7 + resolution: "micromark-extension-gfm-strikethrough@npm:1.0.7" + dependencies: + micromark-util-chunked: "npm:^1.0.0" + micromark-util-classify-character: "npm:^1.0.0" + micromark-util-resolve-all: "npm:^1.0.0" + micromark-util-symbol: "npm:^1.0.0" + micromark-util-types: "npm:^1.0.0" + uvu: "npm:^0.5.0" + checksum: 10c0/b45fe93a7a412fc44bae7a183b92a988e17b49ed9d683bd80ee4dde96d462e1ca6b316dd64bda7759e4086d6d8686790a711e53c244f1f4d2b37e1cfe852884d + languageName: node + linkType: hard + +"micromark-extension-gfm-table@npm:^1.0.0": + version: 1.0.7 + resolution: "micromark-extension-gfm-table@npm:1.0.7" + dependencies: + micromark-factory-space: "npm:^1.0.0" + micromark-util-character: "npm:^1.0.0" + micromark-util-symbol: "npm:^1.0.0" + micromark-util-types: "npm:^1.0.0" + uvu: "npm:^0.5.0" + checksum: 10c0/38b5af80ecab8206845a057338235bee6f47fb6cb904208be4b76e87906765821683e25bef85dfa485809f931eaf8cd55f16cd2f4d6e33b84f56edfaf1dfb129 + languageName: node + linkType: hard + +"micromark-extension-gfm-tagfilter@npm:^1.0.0": + version: 1.0.2 + resolution: "micromark-extension-gfm-tagfilter@npm:1.0.2" + dependencies: + micromark-util-types: "npm:^1.0.0" + checksum: 10c0/7e1bf278255cf2a8d2dda9de84bc238b39c53100e25ba8d7168220d5b00dc74869a6cb038fbf2e76b8ae89efc66906762311797a906d7d9cdd71e07bfe1ed505 + languageName: node + linkType: hard + +"micromark-extension-gfm-task-list-item@npm:^1.0.0": + version: 1.0.5 + resolution: "micromark-extension-gfm-task-list-item@npm:1.0.5" + dependencies: + micromark-factory-space: "npm:^1.0.0" + micromark-util-character: "npm:^1.0.0" + micromark-util-symbol: "npm:^1.0.0" + micromark-util-types: "npm:^1.0.0" + uvu: "npm:^0.5.0" + checksum: 10c0/2179742fa2cbb243cc06bd9e43fbb94cd98e4814c9d368ddf8b4b5afa0348023f335626ae955e89d679e2c2662a7f82c315117a3b060c87bdb4420fee5a219d1 + languageName: node + linkType: hard + +"micromark-extension-gfm@npm:^2.0.0": + version: 2.0.3 + resolution: "micromark-extension-gfm@npm:2.0.3" + dependencies: + micromark-extension-gfm-autolink-literal: "npm:^1.0.0" + micromark-extension-gfm-footnote: "npm:^1.0.0" + micromark-extension-gfm-strikethrough: "npm:^1.0.0" + micromark-extension-gfm-table: "npm:^1.0.0" + micromark-extension-gfm-tagfilter: "npm:^1.0.0" + micromark-extension-gfm-task-list-item: "npm:^1.0.0" + micromark-util-combine-extensions: "npm:^1.0.0" + micromark-util-types: "npm:^1.0.0" + checksum: 10c0/53056376d14caf3fab2cc44881c1ad49d975776cc2267bca74abda2cb31f2a77ec0fb2bdb2dd97565f0d9943ad915ff192b89c1cee5d9d727569a5e38505799b + languageName: node + linkType: hard + +"micromark-factory-destination@npm:^1.0.0": + version: 1.1.0 + resolution: "micromark-factory-destination@npm:1.1.0" + dependencies: + micromark-util-character: "npm:^1.0.0" + micromark-util-symbol: "npm:^1.0.0" + micromark-util-types: "npm:^1.0.0" + checksum: 10c0/71ebd9089bf0c9689b98ef42215c04032ae2701ae08c3546b663628553255dca18e5310dbdacddad3acd8de4f12a789835fff30dadc4da3c4e30387a75e6b488 + languageName: node + linkType: hard + +"micromark-factory-label@npm:^1.0.0": + version: 1.1.0 + resolution: "micromark-factory-label@npm:1.1.0" + dependencies: + micromark-util-character: "npm:^1.0.0" + micromark-util-symbol: "npm:^1.0.0" + micromark-util-types: "npm:^1.0.0" + uvu: "npm:^0.5.0" + checksum: 10c0/5e2cd2d8214bb92a34dfcedf9c7aecf565e3648650a3a6a0495ededf15f2318dd214dc069e3026402792cd5839d395313f8ef9c2e86ca34a8facaa0f75a77753 + languageName: node + linkType: hard + +"micromark-factory-space@npm:^1.0.0": + version: 1.1.0 + resolution: "micromark-factory-space@npm:1.1.0" + dependencies: + micromark-util-character: "npm:^1.0.0" + micromark-util-types: "npm:^1.0.0" + checksum: 10c0/3da81187ce003dd4178c7adc4674052fb8befc8f1a700ae4c8227755f38581a4ae963866dc4857488d62d1dc9837606c9f2f435fa1332f62a0f1c49b83c6a822 + languageName: node + linkType: hard + +"micromark-factory-title@npm:^1.0.0": + version: 1.1.0 + resolution: "micromark-factory-title@npm:1.1.0" + dependencies: + micromark-factory-space: "npm:^1.0.0" + micromark-util-character: "npm:^1.0.0" + micromark-util-symbol: "npm:^1.0.0" + micromark-util-types: "npm:^1.0.0" + checksum: 10c0/cf8c687d1d5c3928846a4791d4a7e2f1d7bdd2397051e20d60f06b7565a48bf85198ab6f85735e997ab3f0cbb80b8b6391f4f7ebc0aae2f2f8c3a08541257bf6 + languageName: node + linkType: hard + +"micromark-factory-whitespace@npm:^1.0.0": + version: 1.1.0 + resolution: "micromark-factory-whitespace@npm:1.1.0" + dependencies: + micromark-factory-space: "npm:^1.0.0" + micromark-util-character: "npm:^1.0.0" + micromark-util-symbol: "npm:^1.0.0" + micromark-util-types: "npm:^1.0.0" + checksum: 10c0/7248cc4534f9befb38c6f398b6e38efd3199f1428fc214c9cb7ed5b6e9fa7a82c0d8cdfa9bcacde62887c9a7c8c46baf5c318b2ae8f701afbccc8ad702e92dce + languageName: node + linkType: hard + +"micromark-util-character@npm:^1.0.0": + version: 1.2.0 + resolution: "micromark-util-character@npm:1.2.0" + dependencies: + micromark-util-symbol: "npm:^1.0.0" + micromark-util-types: "npm:^1.0.0" + checksum: 10c0/3390a675a50731b58a8e5493cd802e190427f10fa782079b455b00f6b54e406e36882df7d4a3bd32b709f7a2c3735b4912597ebc1c0a99566a8d8d0b816e2cd4 + languageName: node + linkType: hard + +"micromark-util-chunked@npm:^1.0.0": + version: 1.1.0 + resolution: "micromark-util-chunked@npm:1.1.0" + dependencies: + micromark-util-symbol: "npm:^1.0.0" + checksum: 10c0/59534cf4aaf481ed58d65478d00eae0080df9b5816673f79b5ddb0cea263e5a9ee9cbb6cc565daf1eb3c8c4ff86fc4e25d38a0577539655cda823a4249efd358 + languageName: node + linkType: hard + +"micromark-util-classify-character@npm:^1.0.0": + version: 1.1.0 + resolution: "micromark-util-classify-character@npm:1.1.0" + dependencies: + micromark-util-character: "npm:^1.0.0" + micromark-util-symbol: "npm:^1.0.0" + micromark-util-types: "npm:^1.0.0" + checksum: 10c0/3266453dc0fdaf584e24c9b3c91d1ed180f76b5856699c51fd2549305814fcab7ec52afb4d3e83d002a9115cd2d2b2ffdc9c0b38ed85120822bf515cc00636ec + languageName: node + linkType: hard + +"micromark-util-combine-extensions@npm:^1.0.0": + version: 1.1.0 + resolution: "micromark-util-combine-extensions@npm:1.1.0" + dependencies: + micromark-util-chunked: "npm:^1.0.0" + micromark-util-types: "npm:^1.0.0" + checksum: 10c0/0bc572fab3fe77f533c29aa1b75cb847b9fc9455f67a98623ef9740b925c0b0426ad9f09bbb56f1e844ea9ebada7873d1f06d27f7c979a917692b273c4b69e31 + languageName: node + linkType: hard + +"micromark-util-decode-numeric-character-reference@npm:^1.0.0": + version: 1.1.0 + resolution: "micromark-util-decode-numeric-character-reference@npm:1.1.0" + dependencies: + micromark-util-symbol: "npm:^1.0.0" + checksum: 10c0/64ef2575e3fc2426976c19e16973348f20b59ddd5543f1467ac2e251f29e0a91f12089703d29ae985b0b9a408ee0d72f06d04ed3920811aa2402aabca3bdf9e4 + languageName: node + linkType: hard + +"micromark-util-decode-string@npm:^1.0.0": + version: 1.1.0 + resolution: "micromark-util-decode-string@npm:1.1.0" + dependencies: + decode-named-character-reference: "npm:^1.0.0" + micromark-util-character: "npm:^1.0.0" + micromark-util-decode-numeric-character-reference: "npm:^1.0.0" + micromark-util-symbol: "npm:^1.0.0" + checksum: 10c0/757a0aaa5ad6c50c7480bd75371d407ac75f5022cd4404aba07adadf1448189502aea9bb7b2d09d25e18745e0abf72b95506b6beb184bcccabe919e48e3a5df7 + languageName: node + linkType: hard + +"micromark-util-encode@npm:^1.0.0": + version: 1.1.0 + resolution: "micromark-util-encode@npm:1.1.0" + checksum: 10c0/9878c9bc96999d45626a7597fffac85348ea842dce75d2417345cbf070a9941c62477bd0963bef37d4f0fd29f2982be6ddf416d62806f00ccb334af9d6ee87e7 + languageName: node + linkType: hard + +"micromark-util-html-tag-name@npm:^1.0.0": + version: 1.2.0 + resolution: "micromark-util-html-tag-name@npm:1.2.0" + checksum: 10c0/15421869678d36b4fe51df453921e8186bff514a14e9f79f32b7e1cdd67874e22a66ad34a7f048dd132cbbbfc7c382ae2f777a2bfd1f245a47705dc1c6d4f199 + languageName: node + linkType: hard + +"micromark-util-normalize-identifier@npm:^1.0.0": + version: 1.1.0 + resolution: "micromark-util-normalize-identifier@npm:1.1.0" + dependencies: + micromark-util-symbol: "npm:^1.0.0" + checksum: 10c0/a9657321a2392584e4d978061882117a84db7d2c2c1c052c0f5d25da089d463edb9f956d5beaf7f5768984b6f72d046d59b5972951ec7bf25397687a62b8278a + languageName: node + linkType: hard + +"micromark-util-resolve-all@npm:^1.0.0": + version: 1.1.0 + resolution: "micromark-util-resolve-all@npm:1.1.0" + dependencies: + micromark-util-types: "npm:^1.0.0" + checksum: 10c0/b5c95484c06e87bbbb60d8430eb030a458733a5270409f4c67892d1274737087ca6a7ca888987430e57cf1dcd44bb16390d3b3936a2bf07f7534ec8f52ce43c9 + languageName: node + linkType: hard + +"micromark-util-sanitize-uri@npm:^1.0.0, micromark-util-sanitize-uri@npm:^1.1.0": + version: 1.2.0 + resolution: "micromark-util-sanitize-uri@npm:1.2.0" + dependencies: + micromark-util-character: "npm:^1.0.0" + micromark-util-encode: "npm:^1.0.0" + micromark-util-symbol: "npm:^1.0.0" + checksum: 10c0/dbdb98248e9f0408c7a00f1c1cd805775b41d213defd659533835f34b38da38e8f990bf7b3f782e96bffbc549aec9c3ecdab197d4ad5adbfe08f814a70327b6e + languageName: node + linkType: hard + +"micromark-util-subtokenize@npm:^1.0.0": + version: 1.1.0 + resolution: "micromark-util-subtokenize@npm:1.1.0" + dependencies: + micromark-util-chunked: "npm:^1.0.0" + micromark-util-symbol: "npm:^1.0.0" + micromark-util-types: "npm:^1.0.0" + uvu: "npm:^0.5.0" + checksum: 10c0/f292b1b162845db50d36255c9d4c4c6d47931fbca3ac98a80c7e536d2163233fd662f8ca0479ee2b80f145c66a1394c7ed17dfce801439741211015e77e3901e + languageName: node + linkType: hard + +"micromark-util-symbol@npm:^1.0.0": + version: 1.1.0 + resolution: "micromark-util-symbol@npm:1.1.0" + checksum: 10c0/10ceaed33a90e6bfd3a5d57053dbb53f437d4809cc11430b5a09479c0ba601577059be9286df4a7eae6e350a60a2575dc9fa9d9872b5b8d058c875e075c33803 + languageName: node + linkType: hard + +"micromark-util-types@npm:^1.0.0, micromark-util-types@npm:^1.0.1": + version: 1.1.0 + resolution: "micromark-util-types@npm:1.1.0" + checksum: 10c0/a9749cb0a12a252ff536baabcb7012421b6fad4d91a5fdd80d7b33dc7b4c22e2d0c4637dfe5b902d00247fe6c9b01f4a24fce6b572b16ccaa4da90e6ce2a11e4 + languageName: node + linkType: hard + +"micromark@npm:^3.0.0": + version: 3.2.0 + resolution: "micromark@npm:3.2.0" + dependencies: + "@types/debug": "npm:^4.0.0" + debug: "npm:^4.0.0" + decode-named-character-reference: "npm:^1.0.0" + micromark-core-commonmark: "npm:^1.0.1" + micromark-factory-space: "npm:^1.0.0" + micromark-util-character: "npm:^1.0.0" + micromark-util-chunked: "npm:^1.0.0" + micromark-util-combine-extensions: "npm:^1.0.0" + micromark-util-decode-numeric-character-reference: "npm:^1.0.0" + micromark-util-encode: "npm:^1.0.0" + micromark-util-normalize-identifier: "npm:^1.0.0" + micromark-util-resolve-all: "npm:^1.0.0" + micromark-util-sanitize-uri: "npm:^1.0.0" + micromark-util-subtokenize: "npm:^1.0.0" + micromark-util-symbol: "npm:^1.0.0" + micromark-util-types: "npm:^1.0.1" + uvu: "npm:^0.5.0" + checksum: 10c0/f243e805d1b3cc699fddae2de0b1492bc82462f1a709d7ae5c82039f88b1e009c959100184717e748be057b5f88603289d5681679a4e6fbabcd037beb34bc744 + languageName: node + linkType: hard + +"micromatch@npm:^4.0.4, micromatch@npm:^4.0.8": + version: 4.0.8 + resolution: "micromatch@npm:4.0.8" + dependencies: + braces: "npm:^3.0.3" + picomatch: "npm:^2.3.1" + checksum: 10c0/166fa6eb926b9553f32ef81f5f531d27b4ce7da60e5baf8c021d043b27a388fb95e46a8038d5045877881e673f8134122b59624d5cecbd16eb50a42e7a6b5ca8 + languageName: node + linkType: hard + +"miller-rabin@npm:^4.0.0": + version: 4.0.1 + resolution: "miller-rabin@npm:4.0.1" + dependencies: + bn.js: "npm:^4.0.0" + brorand: "npm:^1.0.1" + bin: + miller-rabin: bin/miller-rabin + checksum: 10c0/26b2b96f6e49dbcff7faebb78708ed2f5f9ae27ac8cbbf1d7c08f83cf39bed3d418c0c11034dce997da70d135cc0ff6f3a4c15dc452f8e114c11986388a64346 + languageName: node + linkType: hard + +"mime-db@npm:1.52.0": + version: 1.52.0 + resolution: "mime-db@npm:1.52.0" + checksum: 10c0/0557a01deebf45ac5f5777fe7740b2a5c309c6d62d40ceab4e23da9f821899ce7a900b7ac8157d4548ddbb7beffe9abc621250e6d182b0397ec7f10c7b91a5aa + languageName: node + linkType: hard + +"mime-db@npm:>= 1.43.0 < 2": + version: 1.54.0 + resolution: "mime-db@npm:1.54.0" + checksum: 10c0/8d907917bc2a90fa2df842cdf5dfeaf509adc15fe0531e07bb2f6ab15992416479015828d6a74200041c492e42cce3ebf78e5ce714388a0a538ea9c53eece284 + languageName: node + linkType: hard + +"mime-types@npm:^2.1.12, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": + version: 2.1.35 + resolution: "mime-types@npm:2.1.35" + dependencies: + mime-db: "npm:1.52.0" + checksum: 10c0/82fb07ec56d8ff1fc999a84f2f217aa46cb6ed1033fefaabd5785b9a974ed225c90dc72fff460259e66b95b73648596dbcc50d51ed69cdf464af2d237d3149b2 + languageName: node + linkType: hard + +"mime@npm:1.6.0, mime@npm:^1.4.1": + version: 1.6.0 + resolution: "mime@npm:1.6.0" + bin: + mime: cli.js + checksum: 10c0/b92cd0adc44888c7135a185bfd0dddc42c32606401c72896a842ae15da71eb88858f17669af41e498b463cd7eb998f7b48939a25b08374c7924a9c8a6f8a81b0 + languageName: node + linkType: hard + +"mime@npm:^2.5.2": + version: 2.6.0 + resolution: "mime@npm:2.6.0" + bin: + mime: cli.js + checksum: 10c0/a7f2589900d9c16e3bdf7672d16a6274df903da958c1643c9c45771f0478f3846dcb1097f31eb9178452570271361e2149310931ec705c037210fc69639c8e6c + languageName: node + linkType: hard + +"mimic-fn@npm:^2.1.0": + version: 2.1.0 + resolution: "mimic-fn@npm:2.1.0" + checksum: 10c0/b26f5479d7ec6cc2bce275a08f146cf78f5e7b661b18114e2506dd91ec7ec47e7a25bf4360e5438094db0560bcc868079fb3b1fb3892b833c1ecbf63f80c95a4 + languageName: node + linkType: hard + +"mimic-fn@npm:^4.0.0": + version: 4.0.0 + resolution: "mimic-fn@npm:4.0.0" + checksum: 10c0/de9cc32be9996fd941e512248338e43407f63f6d497abe8441fa33447d922e927de54d4cc3c1a3c6d652857acd770389d5a3823f311a744132760ce2be15ccbf + languageName: node + linkType: hard + +"mimic-response@npm:^1.0.0": + version: 1.0.1 + resolution: "mimic-response@npm:1.0.1" + checksum: 10c0/c5381a5eae997f1c3b5e90ca7f209ed58c3615caeee850e85329c598f0c000ae7bec40196580eef1781c60c709f47258131dab237cad8786f8f56750594f27fa + languageName: node + linkType: hard + +"mimic-response@npm:^3.1.0": + version: 3.1.0 + resolution: "mimic-response@npm:3.1.0" + checksum: 10c0/0d6f07ce6e03e9e4445bee655202153bdb8a98d67ee8dc965ac140900d7a2688343e6b4c9a72cfc9ef2f7944dfd76eef4ab2482eb7b293a68b84916bac735362 + languageName: node + linkType: hard + +"min-document@npm:^2.19.0": + version: 2.19.2 + resolution: "min-document@npm:2.19.2" + dependencies: + dom-walk: "npm:^0.1.0" + checksum: 10c0/f6cd59ae07758583bda19cf86ffa8e072cc6e1d72d4e2a62fbf72af3ca630f66ac6a0b3e0ca2b83d5939886da2d006c309fbd0e94f17931ad117860c3fb51bf7 + languageName: node + linkType: hard + +"minimalistic-assert@npm:^1.0.0, minimalistic-assert@npm:^1.0.1": + version: 1.0.1 + resolution: "minimalistic-assert@npm:1.0.1" + checksum: 10c0/96730e5601cd31457f81a296f521eb56036e6f69133c0b18c13fe941109d53ad23a4204d946a0d638d7f3099482a0cec8c9bb6d642604612ce43ee536be3dddd + languageName: node + linkType: hard + +"minimalistic-crypto-utils@npm:^1.0.1": + version: 1.0.1 + resolution: "minimalistic-crypto-utils@npm:1.0.1" + checksum: 10c0/790ecec8c5c73973a4fbf2c663d911033e8494d5fb0960a4500634766ab05d6107d20af896ca2132e7031741f19888154d44b2408ada0852446705441383e9f8 + languageName: node + linkType: hard + +"minimatch@npm:3.0.4": + version: 3.0.4 + resolution: "minimatch@npm:3.0.4" + dependencies: + brace-expansion: "npm:^1.1.7" + checksum: 10c0/d0a2bcd93ebec08a9eef3ca83ba33c9fb6feb93932e0b4dc6aa46c5f37a9404bea7ad9ff7cafe23ce6634f1fe3b206f5315ecbb05812da6e692c21d8ecfd3dae + languageName: node + linkType: hard + +"minimatch@npm:9.0.3": + version: 9.0.3 + resolution: "minimatch@npm:9.0.3" + dependencies: + brace-expansion: "npm:^2.0.1" + checksum: 10c0/85f407dcd38ac3e180f425e86553911d101455ca3ad5544d6a7cec16286657e4f8a9aa6695803025c55e31e35a91a2252b5dc8e7d527211278b8b65b4dbd5eac + languageName: node + linkType: hard + +"minimatch@npm:^10.1.1": + version: 10.1.1 + resolution: "minimatch@npm:10.1.1" + dependencies: + "@isaacs/brace-expansion": "npm:^5.0.0" + checksum: 10c0/c85d44821c71973d636091fddbfbffe62370f5ee3caf0241c5b60c18cd289e916200acb2361b7e987558cd06896d153e25d505db9fc1e43e6b4b6752e2702902 + languageName: node + linkType: hard + +"minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": + version: 3.1.2 + resolution: "minimatch@npm:3.1.2" + dependencies: + brace-expansion: "npm:^1.1.7" + checksum: 10c0/0262810a8fc2e72cca45d6fd86bd349eee435eb95ac6aa45c9ea2180e7ee875ef44c32b55b5973ceabe95ea12682f6e3725cbb63d7a2d1da3ae1163c8b210311 + languageName: node + linkType: hard + +"minimatch@npm:^5.0.1": + version: 5.1.6 + resolution: "minimatch@npm:5.1.6" + dependencies: + brace-expansion: "npm:^2.0.1" + checksum: 10c0/3defdfd230914f22a8da203747c42ee3c405c39d4d37ffda284dac5e45b7e1f6c49aa8be606509002898e73091ff2a3bbfc59c2c6c71d4660609f63aa92f98e3 + languageName: node + linkType: hard + +"minimatch@npm:^9.0.4": + version: 9.0.5 + resolution: "minimatch@npm:9.0.5" + dependencies: + brace-expansion: "npm:^2.0.1" + checksum: 10c0/de96cf5e35bdf0eab3e2c853522f98ffbe9a36c37797778d2665231ec1f20a9447a7e567cb640901f89e4daaa95ae5d70c65a9e8aa2bb0019b6facbc3c0575ed + languageName: node + linkType: hard + +"minimist@npm:^1.2.0, minimist@npm:^1.2.6": + version: 1.2.8 + resolution: "minimist@npm:1.2.8" + checksum: 10c0/19d3fcdca050087b84c2029841a093691a91259a47def2f18222f41e7645a0b7c44ef4b40e88a1e58a40c84d2ef0ee6047c55594d298146d0eb3f6b737c20ce6 + languageName: node + linkType: hard + +"minipass-collect@npm:^2.0.1": + version: 2.0.1 + resolution: "minipass-collect@npm:2.0.1" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/5167e73f62bb74cc5019594709c77e6a742051a647fe9499abf03c71dca75515b7959d67a764bdc4f8b361cf897fbf25e2d9869ee039203ed45240f48b9aa06e + languageName: node + linkType: hard + +"minipass-fetch@npm:^5.0.0": + version: 5.0.0 + resolution: "minipass-fetch@npm:5.0.0" + dependencies: + encoding: "npm:^0.1.13" + minipass: "npm:^7.0.3" + minipass-sized: "npm:^1.0.3" + minizlib: "npm:^3.0.1" + dependenciesMeta: + encoding: + optional: true + checksum: 10c0/9443aab5feab190972f84b64116e54e58dd87a58e62399cae0a4a7461b80568281039b7c3a38ba96453431ebc799d1e26999e548540156216729a4967cd5ef06 + languageName: node + linkType: hard + +"minipass-flush@npm:^1.0.5": + version: 1.0.5 + resolution: "minipass-flush@npm:1.0.5" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/2a51b63feb799d2bb34669205eee7c0eaf9dce01883261a5b77410c9408aa447e478efd191b4de6fc1101e796ff5892f8443ef20d9544385819093dbb32d36bd + languageName: node + linkType: hard + +"minipass-pipeline@npm:^1.2.4": + version: 1.2.4 + resolution: "minipass-pipeline@npm:1.2.4" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/cbda57cea20b140b797505dc2cac71581a70b3247b84480c1fed5ca5ba46c25ecc25f68bfc9e6dcb1a6e9017dab5c7ada5eab73ad4f0a49d84e35093e0c643f2 + languageName: node + linkType: hard + +"minipass-sized@npm:^1.0.3": + version: 1.0.3 + resolution: "minipass-sized@npm:1.0.3" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/298f124753efdc745cfe0f2bdfdd81ba25b9f4e753ca4a2066eb17c821f25d48acea607dfc997633ee5bf7b6dfffb4eee4f2051eb168663f0b99fad2fa4829cb + languageName: node + linkType: hard + +"minipass@npm:^3.0.0": + version: 3.3.6 + resolution: "minipass@npm:3.3.6" + dependencies: + yallist: "npm:^4.0.0" + checksum: 10c0/a114746943afa1dbbca8249e706d1d38b85ed1298b530f5808ce51f8e9e941962e2a5ad2e00eae7dd21d8a4aae6586a66d4216d1a259385e9d0358f0c1eba16c + languageName: node + linkType: hard + +"minipass@npm:^5.0.0": + version: 5.0.0 + resolution: "minipass@npm:5.0.0" + checksum: 10c0/a91d8043f691796a8ac88df039da19933ef0f633e3d7f0d35dcd5373af49131cf2399bfc355f41515dc495e3990369c3858cd319e5c2722b4753c90bf3152462 + languageName: node + linkType: hard + +"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2": + version: 7.1.2 + resolution: "minipass@npm:7.1.2" + checksum: 10c0/b0fd20bb9fb56e5fa9a8bfac539e8915ae07430a619e4b86ff71f5fc757ef3924b23b2c4230393af1eda647ed3d75739e4e0acb250a6b1eb277cf7f8fe449557 + languageName: node + linkType: hard + +"minizlib@npm:^2.1.1": + version: 2.1.2 + resolution: "minizlib@npm:2.1.2" + dependencies: + minipass: "npm:^3.0.0" + yallist: "npm:^4.0.0" + checksum: 10c0/64fae024e1a7d0346a1102bb670085b17b7f95bf6cfdf5b128772ec8faf9ea211464ea4add406a3a6384a7d87a0cd1a96263692134323477b4fb43659a6cab78 + languageName: node + linkType: hard + +"minizlib@npm:^3.0.1, minizlib@npm:^3.1.0": + version: 3.1.0 + resolution: "minizlib@npm:3.1.0" + dependencies: + minipass: "npm:^7.1.2" + checksum: 10c0/5aad75ab0090b8266069c9aabe582c021ae53eb33c6c691054a13a45db3b4f91a7fb1bd79151e6b4e9e9a86727b522527c0a06ec7d45206b745d54cd3097bcec + languageName: node + linkType: hard + +"mkdirp@npm:^1.0.3": + version: 1.0.4 + resolution: "mkdirp@npm:1.0.4" + bin: + mkdirp: bin/cmd.js + checksum: 10c0/46ea0f3ffa8bc6a5bc0c7081ffc3907777f0ed6516888d40a518c5111f8366d97d2678911ad1a6882bf592fa9de6c784fea32e1687bb94e1f4944170af48a5cf + languageName: node + linkType: hard + +"moment@npm:^2.29.4": + version: 2.30.1 + resolution: "moment@npm:2.30.1" + checksum: 10c0/865e4279418c6de666fca7786607705fd0189d8a7b7624e2e56be99290ac846f90878a6f602e34b4e0455c549b85385b1baf9966845962b313699e7cb847543a + languageName: node + linkType: hard + +"monaco-editor-esm-webpack-plugin@npm:^2.1.0": + version: 2.1.0 + resolution: "monaco-editor-esm-webpack-plugin@npm:2.1.0" + peerDependencies: + monaco-editor: "*" + monaco-editor-nls: "*" + monaco-editor-webpack-plugin: "*" + webpack: "*" + checksum: 10c0/52739c9651d7db3177ea776f8a558886618a92da4657c88def82fbedd6f6a8de058cc2a06038926563a0f1e9fa08b27518ca3d22781367ecabc923d021608789 + languageName: node + linkType: hard + +"monaco-editor-webpack-plugin@npm:^7.0.1": + version: 7.1.1 + resolution: "monaco-editor-webpack-plugin@npm:7.1.1" + dependencies: + loader-utils: "npm:^2.0.2" + peerDependencies: + monaco-editor: ">= 0.31.0" + webpack: ^4.5.0 || 5.x + checksum: 10c0/fe75611813617277330524e502d5413abf03cb0fc0ca92a9f79cd88bb8ed689dd2868676950132decb15765a05ca9c96b31830c8ad70cc3f164d70d543b79461 + languageName: node + linkType: hard + +"monaco-editor@npm:^0.44.0": + version: 0.44.0 + resolution: "monaco-editor@npm:0.44.0" + checksum: 10c0/22676f597f702763c33dcdec8b5d6d1c3a5e1a52910074c46a6477ac9c8308cec2dede968230fb9b69fc91f4fc4687cff8bbe0f2365ece49ded9b248a7dcbc62 + languageName: node + linkType: hard + +"moo@npm:^0.5.0": + version: 0.5.2 + resolution: "moo@npm:0.5.2" + checksum: 10c0/a9d9ad8198a51fe35d297f6e9fdd718298ca0b39a412e868a0ebd92286379ab4533cfc1f1f34516177f5129988ab25fe598f78e77c84e3bfe0d4a877b56525a8 + languageName: node + linkType: hard + +"mri@npm:^1.1.0": + version: 1.2.0 + resolution: "mri@npm:1.2.0" + checksum: 10c0/a3d32379c2554cf7351db6237ddc18dc9e54e4214953f3da105b97dc3babe0deb3ffe99cf409b38ea47cc29f9430561ba6b53b24ab8f9ce97a4b50409e4a50e7 + languageName: node + linkType: hard + +"ms@npm:2.0.0": + version: 2.0.0 + resolution: "ms@npm:2.0.0" + checksum: 10c0/f8fda810b39fd7255bbdc451c46286e549794fcc700dc9cd1d25658bbc4dc2563a5de6fe7c60f798a16a60c6ceb53f033cb353f493f0cf63e5199b702943159d + languageName: node + linkType: hard + +"ms@npm:2.1.1": + version: 2.1.1 + resolution: "ms@npm:2.1.1" + checksum: 10c0/056140c631e740369fa21142417aba1bd629ab912334715216c666eb681c8f015c622dd4e38bc1d836b30852b05641331661703af13a0397eb0ca420fc1e75d9 + languageName: node + linkType: hard + +"ms@npm:2.1.3, ms@npm:^2.1.1, ms@npm:^2.1.3": + version: 2.1.3 + resolution: "ms@npm:2.1.3" + checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 + languageName: node + linkType: hard + +"mz@npm:^2.7.0": + version: 2.7.0 + resolution: "mz@npm:2.7.0" + dependencies: + any-promise: "npm:^1.0.0" + object-assign: "npm:^4.0.1" + thenify-all: "npm:^1.0.0" + checksum: 10c0/103114e93f87362f0b56ab5b2e7245051ad0276b646e3902c98397d18bb8f4a77f2ea4a2c9d3ad516034ea3a56553b60d3f5f78220001ca4c404bd711bd0af39 + languageName: node + linkType: hard + +"nanoid@npm:^3.3.11, nanoid@npm:^3.3.6, nanoid@npm:^3.3.7": + version: 3.3.11 + resolution: "nanoid@npm:3.3.11" + bin: + nanoid: bin/nanoid.cjs + checksum: 10c0/40e7f70b3d15f725ca072dfc4f74e81fcf1fbb02e491cf58ac0c79093adc9b0a73b152bcde57df4b79cd097e13023d7504acb38404a4da7bc1cd8e887b82fe0b + languageName: node + linkType: hard + +"natural-compare-lite@npm:^1.4.0": + version: 1.4.0 + resolution: "natural-compare-lite@npm:1.4.0" + checksum: 10c0/f6cef26f5044515754802c0fc475d81426f3b90fe88c20fabe08771ce1f736ce46e0397c10acb569a4dd0acb84c7f1ee70676122f95d5bfdd747af3a6c6bbaa8 + languageName: node + linkType: hard + +"natural-compare@npm:^1.4.0": + version: 1.4.0 + resolution: "natural-compare@npm:1.4.0" + checksum: 10c0/f5f9a7974bfb28a91afafa254b197f0f22c684d4a1731763dda960d2c8e375b36c7d690e0d9dc8fba774c537af14a7e979129bca23d88d052fbeb9466955e447 + languageName: node + linkType: hard + +"nearley@npm:^2.20.1": + version: 2.20.1 + resolution: "nearley@npm:2.20.1" + dependencies: + commander: "npm:^2.19.0" + moo: "npm:^0.5.0" + railroad-diagrams: "npm:^1.0.0" + randexp: "npm:0.4.6" + bin: + nearley-railroad: bin/nearley-railroad.js + nearley-test: bin/nearley-test.js + nearley-unparse: bin/nearley-unparse.js + nearleyc: bin/nearleyc.js + checksum: 10c0/d25e1fd40b19c53a0ada6a688670f4a39063fd9553ab62885e81a82927d51572ce47193b946afa3d85efa608ba2c68f433c421f69b854bfb7f599eacb5fae37e + languageName: node + linkType: hard + +"needle@npm:^3.1.0": + version: 3.3.1 + resolution: "needle@npm:3.3.1" + dependencies: + iconv-lite: "npm:^0.6.3" + sax: "npm:^1.2.4" + bin: + needle: bin/needle + checksum: 10c0/233b9315d47b735867d03e7a018fb665ee6cacf3a83b991b19538019cf42b538a3e85ca745c840b4c5e9a0ffdca76472f941363bf7c166214ae8cbc650fd4d39 + languageName: node + linkType: hard + +"negotiator@npm:0.6.3": + version: 0.6.3 + resolution: "negotiator@npm:0.6.3" + checksum: 10c0/3ec9fd413e7bf071c937ae60d572bc67155262068ed522cf4b3be5edbe6ddf67d095ec03a3a14ebf8fc8e95f8e1d61be4869db0dbb0de696f6b837358bd43fc2 + languageName: node + linkType: hard + +"negotiator@npm:^1.0.0": + version: 1.0.0 + resolution: "negotiator@npm:1.0.0" + checksum: 10c0/4c559dd52669ea48e1914f9d634227c561221dd54734070791f999c52ed0ff36e437b2e07d5c1f6e32909fc625fe46491c16e4a8f0572567d4dd15c3a4fda04b + languageName: node + linkType: hard + +"negotiator@npm:~0.6.4": + version: 0.6.4 + resolution: "negotiator@npm:0.6.4" + checksum: 10c0/3e677139c7fb7628a6f36335bf11a885a62c21d5390204590a1a214a5631fcbe5ea74ef6a610b60afe84b4d975cbe0566a23f20ee17c77c73e74b80032108dea + languageName: node + linkType: hard + +"neo-async@npm:^2.6.2": + version: 2.6.2 + resolution: "neo-async@npm:2.6.2" + checksum: 10c0/c2f5a604a54a8ec5438a342e1f356dff4bc33ccccdb6dc668d94fe8e5eccfc9d2c2eea6064b0967a767ba63b33763f51ccf2cd2441b461a7322656c1f06b3f5d + languageName: node + linkType: hard + +"next-tick@npm:^1.1.0": + version: 1.1.0 + resolution: "next-tick@npm:1.1.0" + checksum: 10c0/3ba80dd805fcb336b4f52e010992f3e6175869c8d88bf4ff0a81d5d66e6049f89993463b28211613e58a6b7fe93ff5ccbba0da18d4fa574b96289e8f0b577f28 + languageName: node + linkType: hard + +"no-case@npm:^3.0.4": + version: 3.0.4 + resolution: "no-case@npm:3.0.4" + dependencies: + lower-case: "npm:^2.0.2" + tslib: "npm:^2.0.3" + checksum: 10c0/8ef545f0b3f8677c848f86ecbd42ca0ff3cd9dd71c158527b344c69ba14710d816d8489c746b6ca225e7b615108938a0bda0a54706f8c255933703ac1cf8e703 + languageName: node + linkType: hard + +"node-abort-controller@npm:^3.0.1": + version: 3.1.1 + resolution: "node-abort-controller@npm:3.1.1" + checksum: 10c0/f7ad0e7a8e33809d4f3a0d1d65036a711c39e9d23e0319d80ebe076b9a3b4432b4d6b86a7fab65521de3f6872ffed36fc35d1327487c48eb88c517803403eda3 + languageName: node + linkType: hard + +"node-addon-api@npm:^1.6.3": + version: 1.7.2 + resolution: "node-addon-api@npm:1.7.2" + dependencies: + node-gyp: "npm:latest" + checksum: 10c0/bcf526f2ce788182730d3c3df5206585873d1e837a6e1378ff84abccf2f19cf3f93a8274f9c1245af0de63a0dbd1bb95ca2f767ecf5c678d6930326aaf396c4e + languageName: node + linkType: hard + +"node-domexception@npm:^1.0.0": + version: 1.0.0 + resolution: "node-domexception@npm:1.0.0" + checksum: 10c0/5e5d63cda29856402df9472335af4bb13875e1927ad3be861dc5ebde38917aecbf9ae337923777af52a48c426b70148815e890a5d72760f1b4d758cc671b1a2b + languageName: node + linkType: hard + +"node-fetch@npm:^1.0.1": + version: 1.7.3 + resolution: "node-fetch@npm:1.7.3" + dependencies: + encoding: "npm:^0.1.11" + is-stream: "npm:^1.0.1" + checksum: 10c0/5a6b56b3edf909ccd20414355867d24f15f1885da3b26be90840241c46e63754ebf4697050f897daab676e3952d969611ffe1d4bc4506cf50f70837e20ad5328 + languageName: node + linkType: hard + +"node-fetch@npm:^3.2.0": + version: 3.3.2 + resolution: "node-fetch@npm:3.3.2" + dependencies: + data-uri-to-buffer: "npm:^4.0.0" + fetch-blob: "npm:^3.1.4" + formdata-polyfill: "npm:^4.0.10" + checksum: 10c0/f3d5e56190562221398c9f5750198b34cf6113aa304e34ee97c94fd300ec578b25b2c2906edba922050fce983338fde0d5d34fcb0fc3336ade5bd0e429ad7538 + languageName: node + linkType: hard + +"node-gyp@npm:latest": + version: 12.1.0 + resolution: "node-gyp@npm:12.1.0" + dependencies: + env-paths: "npm:^2.2.0" + exponential-backoff: "npm:^3.1.1" + graceful-fs: "npm:^4.2.6" + make-fetch-happen: "npm:^15.0.0" + nopt: "npm:^9.0.0" + proc-log: "npm:^6.0.0" + semver: "npm:^7.3.5" + tar: "npm:^7.5.2" + tinyglobby: "npm:^0.2.12" + which: "npm:^6.0.0" + bin: + node-gyp: bin/node-gyp.js + checksum: 10c0/f43efea8aaf0beb6b2f6184e533edad779b2ae38062953e21951f46221dd104006cc574154f2ad4a135467a5aae92c49e84ef289311a82e08481c5df0e8dc495 + languageName: node + linkType: hard + +"node-int64@npm:^0.4.0": + version: 0.4.0 + resolution: "node-int64@npm:0.4.0" + checksum: 10c0/a6a4d8369e2f2720e9c645255ffde909c0fbd41c92ea92a5607fc17055955daac99c1ff589d421eee12a0d24e99f7bfc2aabfeb1a4c14742f6c099a51863f31a + languageName: node + linkType: hard + +"node-libs-browser-okam@npm:^2.2.5": + version: 2.2.5 + resolution: "node-libs-browser-okam@npm:2.2.5" + dependencies: + assert-okam: "npm:^1.1.1" + browserify-zlib: "npm:^0.2.0" + buffer-okam: "npm:^4.3.0" + console-browserify: "npm:^1.1.0" + constants-browserify: "npm:^1.0.0" + crypto-browserify: "npm:^3.11.0" + domain-browser: "npm:^1.1.1" + events-okam: "npm:^3.0.0" + https-browserify: "npm:^1.0.0" + os-browserify: "npm:^0.3.0" + path-browserify: "npm:0.0.1" + process-okam: "npm:^0.11.10" + punycode-okam: "npm:^1.2.4" + querystring-es3: "npm:^0.2.0" + readable-stream: "npm:^2.3.3" + stream-browserify: "npm:^2.0.1" + stream-http: "npm:^2.7.2" + string_decoder-okam: "npm:^1.0.0" + timers-browserify: "npm:^2.0.4" + tty-browserify: "npm:0.0.0" + url-okam: "npm:^0.11.0" + util-okam: "npm:^0.11.0" + vm-browserify: "npm:^1.0.1" + checksum: 10c0/eb591c52327d26f22de22399983a61f833661d3be22ac0708b3945213c2eaba6e22a03a9b2b810012f99c030025954d50dbbf990f4ede2fe9b79dd0e3c810079 + languageName: node + linkType: hard + +"node-libs-browser@npm:2.2.1": + version: 2.2.1 + resolution: "node-libs-browser@npm:2.2.1" + dependencies: + assert: "npm:^1.1.1" + browserify-zlib: "npm:^0.2.0" + buffer: "npm:^4.3.0" + console-browserify: "npm:^1.1.0" + constants-browserify: "npm:^1.0.0" + crypto-browserify: "npm:^3.11.0" + domain-browser: "npm:^1.1.1" + events: "npm:^3.0.0" + https-browserify: "npm:^1.0.0" + os-browserify: "npm:^0.3.0" + path-browserify: "npm:0.0.1" + process: "npm:^0.11.10" + punycode: "npm:^1.2.4" + querystring-es3: "npm:^0.2.0" + readable-stream: "npm:^2.3.3" + stream-browserify: "npm:^2.0.1" + stream-http: "npm:^2.7.2" + string_decoder: "npm:^1.0.0" + timers-browserify: "npm:^2.0.4" + tty-browserify: "npm:0.0.0" + url: "npm:^0.11.0" + util: "npm:^0.11.0" + vm-browserify: "npm:^1.0.1" + checksum: 10c0/0e05321a6396408903ed642231d2bca7dd96492d074c7af161ba06a63c95378bd3de50b4105eccbbc02d93ba3da69f0ff5e624bc2a8c92ca462ceb6a403e7986 + languageName: node + linkType: hard + +"node-releases@npm:^2.0.27": + version: 2.0.27 + resolution: "node-releases@npm:2.0.27" + checksum: 10c0/f1e6583b7833ea81880627748d28a3a7ff5703d5409328c216ae57befbced10ce2c991bea86434e8ec39003bd017f70481e2e5f8c1f7e0a7663241f81d6e00e2 + languageName: node + linkType: hard + +"nopt@npm:^9.0.0": + version: 9.0.0 + resolution: "nopt@npm:9.0.0" + dependencies: + abbrev: "npm:^4.0.0" + bin: + nopt: bin/nopt.js + checksum: 10c0/1822eb6f9b020ef6f7a7516d7b64a8036e09666ea55ac40416c36e4b2b343122c3cff0e2f085675f53de1d2db99a2a89a60ccea1d120bcd6a5347bf6ceb4a7fd + languageName: node + linkType: hard + +"normalize-path@npm:^3.0.0, normalize-path@npm:~3.0.0": + version: 3.0.0 + resolution: "normalize-path@npm:3.0.0" + checksum: 10c0/e008c8142bcc335b5e38cf0d63cfd39d6cf2d97480af9abdbe9a439221fd4d749763bab492a8ee708ce7a194bb00c9da6d0a115018672310850489137b3da046 + languageName: node + linkType: hard + +"normalize-range@npm:^0.1.2": + version: 0.1.2 + resolution: "normalize-range@npm:0.1.2" + checksum: 10c0/bf39b73a63e0a42ad1a48c2bd1bda5a07ede64a7e2567307a407674e595bcff0fa0d57e8e5f1e7fa5e91000797c7615e13613227aaaa4d6d6e87f5bd5cc95de6 + languageName: node + linkType: hard + +"normalize-url@npm:^6.0.1": + version: 6.1.0 + resolution: "normalize-url@npm:6.1.0" + checksum: 10c0/95d948f9bdd2cfde91aa786d1816ae40f8262946e13700bf6628105994fe0ff361662c20af3961161c38a119dc977adeb41fc0b41b1745eb77edaaf9cb22db23 + languageName: node + linkType: hard + +"npm-run-path@npm:^4.0.1": + version: 4.0.1 + resolution: "npm-run-path@npm:4.0.1" + dependencies: + path-key: "npm:^3.0.0" + checksum: 10c0/6f9353a95288f8455cf64cbeb707b28826a7f29690244c1e4bb61ec573256e021b6ad6651b394eb1ccfd00d6ec50147253aba2c5fe58a57ceb111fad62c519ac + languageName: node + linkType: hard + +"npm-run-path@npm:^5.1.0": + version: 5.3.0 + resolution: "npm-run-path@npm:5.3.0" + dependencies: + path-key: "npm:^4.0.0" + checksum: 10c0/124df74820c40c2eb9a8612a254ea1d557ddfab1581c3e751f825e3e366d9f00b0d76a3c94ecd8398e7f3eee193018622677e95816e8491f0797b21e30b2deba + languageName: node + linkType: hard + +"nth-check@npm:^2.0.1": + version: 2.1.1 + resolution: "nth-check@npm:2.1.1" + dependencies: + boolbase: "npm:^1.0.0" + checksum: 10c0/5fee7ff309727763689cfad844d979aedd2204a817fbaaf0e1603794a7c20db28548d7b024692f953557df6ce4a0ee4ae46cd8ebd9b36cfb300b9226b567c479 + languageName: node + linkType: hard + +"object-assign@npm:4.x, object-assign@npm:^4, object-assign@npm:^4.0.1, object-assign@npm:^4.1.1": + version: 4.1.1 + resolution: "object-assign@npm:4.1.1" + checksum: 10c0/1f4df9945120325d041ccf7b86f31e8bcc14e73d29171e37a7903050e96b81323784ec59f93f102ec635bcf6fa8034ba3ea0a8c7e69fa202b87ae3b6cec5a414 + languageName: node + linkType: hard + +"object-hash@npm:^3.0.0": + version: 3.0.0 + resolution: "object-hash@npm:3.0.0" + checksum: 10c0/a06844537107b960c1c8b96cd2ac8592a265186bfa0f6ccafe0d34eabdb526f6fa81da1f37c43df7ed13b12a4ae3457a16071603bcd39d8beddb5f08c37b0f47 + languageName: node + linkType: hard + +"object-inspect@npm:^1.13.1, object-inspect@npm:^1.13.3, object-inspect@npm:^1.13.4": + version: 1.13.4 + resolution: "object-inspect@npm:1.13.4" + checksum: 10c0/d7f8711e803b96ea3191c745d6f8056ce1f2496e530e6a19a0e92d89b0fa3c76d910c31f0aa270432db6bd3b2f85500a376a83aaba849a8d518c8845b3211692 + languageName: node + linkType: hard + +"object-keys@npm:^1.1.1": + version: 1.1.1 + resolution: "object-keys@npm:1.1.1" + checksum: 10c0/b11f7ccdbc6d406d1f186cdadb9d54738e347b2692a14439ca5ac70c225fa6db46db809711b78589866d47b25fc3e8dee0b4c722ac751e11180f9380e3d8601d + languageName: node + linkType: hard + +"object.assign@npm:^4.1.0, object.assign@npm:^4.1.2, object.assign@npm:^4.1.4, object.assign@npm:^4.1.7": + version: 4.1.7 + resolution: "object.assign@npm:4.1.7" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.3" + define-properties: "npm:^1.2.1" + es-object-atoms: "npm:^1.0.0" + has-symbols: "npm:^1.1.0" + object-keys: "npm:^1.1.1" + checksum: 10c0/3b2732bd860567ea2579d1567525168de925a8d852638612846bd8082b3a1602b7b89b67b09913cbb5b9bd6e95923b2ae73580baa9d99cb4e990564e8cbf5ddc + languageName: node + linkType: hard + +"object.entries@npm:^1.1.5, object.entries@npm:^1.1.6, object.entries@npm:^1.1.7, object.entries@npm:^1.1.9": + version: 1.1.9 + resolution: "object.entries@npm:1.1.9" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" + define-properties: "npm:^1.2.1" + es-object-atoms: "npm:^1.1.1" + checksum: 10c0/d4b8c1e586650407da03370845f029aa14076caca4e4d4afadbc69cfb5b78035fd3ee7be417141abdb0258fa142e59b11923b4c44d8b1255b28f5ffcc50da7db + languageName: node + linkType: hard + +"object.fromentries@npm:^2.0.6, object.fromentries@npm:^2.0.8": + version: 2.0.8 + resolution: "object.fromentries@npm:2.0.8" + dependencies: + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.2" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/cd4327e6c3369cfa805deb4cbbe919bfb7d3aeebf0bcaba291bb568ea7169f8f8cdbcabe2f00b40db0c20cd20f08e11b5f3a5a36fb7dd3fe04850c50db3bf83b + languageName: node + linkType: hard + +"object.getprototypeof@npm:^1.0.5": + version: 1.0.7 + resolution: "object.getprototypeof@npm:1.0.7" + dependencies: + call-bind: "npm:^1.0.8" + define-properties: "npm:^1.2.1" + es-object-atoms: "npm:^1.0.0" + get-proto: "npm:^1.0.1" + reflect.getprototypeof: "npm:^1.0.10" + checksum: 10c0/81ea1bda0bfd6dc47cc9308cff48b72bad9d7486a6816ed3af54fdd5f778fd9a3c5d771729c27c5f3ae9c0c3b7ca9b7f62dfaf30b81e0490597bd76f0833cb50 + languageName: node + linkType: hard + +"object.groupby@npm:^1.0.3": + version: 1.0.3 + resolution: "object.groupby@npm:1.0.3" + dependencies: + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.2" + checksum: 10c0/60d0455c85c736fbfeda0217d1a77525956f76f7b2495edeca9e9bbf8168a45783199e77b894d30638837c654d0cc410e0e02cbfcf445bc8de71c3da1ede6a9c + languageName: node + linkType: hard + +"object.hasown@npm:^1.1.2": + version: 1.1.4 + resolution: "object.hasown@npm:1.1.4" + dependencies: + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.2" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/f23187b08d874ef1aea060118c8259eb7f99f93c15a50771d710569534119062b90e087b92952b2d0fb1bb8914d61fb0b43c57fb06f622aaad538fe6868ab987 + languageName: node + linkType: hard + +"object.values@npm:^1.1.6, object.values@npm:^1.2.1": + version: 1.2.1 + resolution: "object.values@npm:1.2.1" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.3" + define-properties: "npm:^1.2.1" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/3c47814fdc64842ae3d5a74bc9d06bdd8d21563c04d9939bf6716a9c00596a4ebc342552f8934013d1ec991c74e3671b26710a0c51815f0b603795605ab6b2c9 + languageName: node + linkType: hard + +"obuf@npm:^1.0.0, obuf@npm:^1.1.2": + version: 1.1.2 + resolution: "obuf@npm:1.1.2" + checksum: 10c0/520aaac7ea701618eacf000fc96ae458e20e13b0569845800fc582f81b386731ab22d55354b4915d58171db00e79cfcd09c1638c02f89577ef092b38c65b7d81 + languageName: node + linkType: hard + +"on-exit-leak-free@npm:^0.2.0": + version: 0.2.0 + resolution: "on-exit-leak-free@npm:0.2.0" + checksum: 10c0/d4e1f0bea59f39aa435baaee7d76955527e245538cffc1d7bb0c165ae85e37f67690aa9272247ced17bad76052afdb45faf5ea304a2248e070202d4554c4e30c + languageName: node + linkType: hard + +"on-finished@npm:2.4.1": + version: 2.4.1 + resolution: "on-finished@npm:2.4.1" + dependencies: + ee-first: "npm:1.1.1" + checksum: 10c0/46fb11b9063782f2d9968863d9cbba33d77aa13c17f895f56129c274318b86500b22af3a160fe9995aa41317efcd22941b6eba747f718ced08d9a73afdb087b4 + languageName: node + linkType: hard + +"on-finished@npm:~2.3.0": + version: 2.3.0 + resolution: "on-finished@npm:2.3.0" + dependencies: + ee-first: "npm:1.1.1" + checksum: 10c0/c904f9e518b11941eb60279a3cbfaf1289bd0001f600a950255b1dede9fe3df8cd74f38483550b3bb9485165166acb5db500c3b4c4337aec2815c88c96fcc2ea + languageName: node + linkType: hard + +"on-headers@npm:~1.1.0": + version: 1.1.0 + resolution: "on-headers@npm:1.1.0" + checksum: 10c0/2c3b6b0d68ec9adbd561dc2d61c9b14da8ac03d8a2f0fd9e97bdf0600c887d5d97f664ff3be6876cf40cda6e3c587d73a4745e10b426ac50c7664fc5a0dfc0a1 + languageName: node + linkType: hard + +"once@npm:^1.3.0, once@npm:^1.3.1, once@npm:^1.4.0": + version: 1.4.0 + resolution: "once@npm:1.4.0" + dependencies: + wrappy: "npm:1" + checksum: 10c0/5d48aca287dfefabd756621c5dfce5c91a549a93e9fdb7b8246bc4c4790aa2ec17b34a260530474635147aeb631a2dcc8b32c613df0675f96041cbb8244517d0 + languageName: node + linkType: hard + +"onetime@npm:^5.1.2": + version: 5.1.2 + resolution: "onetime@npm:5.1.2" + dependencies: + mimic-fn: "npm:^2.1.0" + checksum: 10c0/ffcef6fbb2692c3c40749f31ea2e22677a876daea92959b8a80b521d95cca7a668c884d8b2045d1d8ee7d56796aa405c405462af112a1477594cc63531baeb8f + languageName: node + linkType: hard + +"onetime@npm:^6.0.0": + version: 6.0.0 + resolution: "onetime@npm:6.0.0" + dependencies: + mimic-fn: "npm:^4.0.0" + checksum: 10c0/4eef7c6abfef697dd4479345a4100c382d73c149d2d56170a54a07418c50816937ad09500e1ed1e79d235989d073a9bade8557122aee24f0576ecde0f392bb6c + languageName: node + linkType: hard + +"open@npm:^8.4.0": + version: 8.4.2 + resolution: "open@npm:8.4.2" + dependencies: + define-lazy-prop: "npm:^2.0.0" + is-docker: "npm:^2.1.1" + is-wsl: "npm:^2.2.0" + checksum: 10c0/bb6b3a58401dacdb0aad14360626faf3fb7fba4b77816b373495988b724fb48941cad80c1b65d62bb31a17609b2cd91c41a181602caea597ca80dfbcc27e84c9 + languageName: node + linkType: hard + +"open@npm:^9.1.0": + version: 9.1.0 + resolution: "open@npm:9.1.0" + dependencies: + default-browser: "npm:^4.0.0" + define-lazy-prop: "npm:^3.0.0" + is-inside-container: "npm:^1.0.0" + is-wsl: "npm:^2.2.0" + checksum: 10c0/8073ec0dd8994a7a7d9bac208bd17d093993a65ce10f2eb9b62b6d3a91c9366ae903938a237c275493c130171d339f6dcbdd2a2de7e32953452c0867b97825af + languageName: node + linkType: hard + +"optionator@npm:^0.9.3": + version: 0.9.4 + resolution: "optionator@npm:0.9.4" + dependencies: + deep-is: "npm:^0.1.3" + fast-levenshtein: "npm:^2.0.6" + levn: "npm:^0.4.1" + prelude-ls: "npm:^1.2.1" + type-check: "npm:^0.4.0" + word-wrap: "npm:^1.2.5" + checksum: 10c0/4afb687a059ee65b61df74dfe87d8d6815cd6883cb8b3d5883a910df72d0f5d029821f37025e4bccf4048873dbdb09acc6d303d27b8f76b1a80dd5a7d5334675 + languageName: node + linkType: hard + +"os-browserify@npm:^0.3.0": + version: 0.3.0 + resolution: "os-browserify@npm:0.3.0" + checksum: 10c0/6ff32cb1efe2bc6930ad0fd4c50e30c38010aee909eba8d65be60af55efd6cbb48f0287e3649b4e3f3a63dce5a667b23c187c4293a75e557f0d5489d735bcf52 + languageName: node + linkType: hard + +"own-keys@npm:^1.0.1": + version: 1.0.1 + resolution: "own-keys@npm:1.0.1" + dependencies: + get-intrinsic: "npm:^1.2.6" + object-keys: "npm:^1.1.1" + safe-push-apply: "npm:^1.0.0" + checksum: 10c0/6dfeb3455bff92ec3f16a982d4e3e65676345f6902d9f5ded1d8265a6318d0200ce461956d6d1c70053c7fe9f9fe65e552faac03f8140d37ef0fdd108e67013a + languageName: node + linkType: hard + +"p-cancelable@npm:^2.0.0": + version: 2.1.1 + resolution: "p-cancelable@npm:2.1.1" + checksum: 10c0/8c6dc1f8dd4154fd8b96a10e55a3a832684c4365fb9108056d89e79fbf21a2465027c04a59d0d797b5ffe10b54a61a32043af287d5c4860f1e996cbdbc847f01 + languageName: node + linkType: hard + +"p-limit@npm:^2.2.0": + version: 2.3.0 + resolution: "p-limit@npm:2.3.0" + dependencies: + p-try: "npm:^2.0.0" + checksum: 10c0/8da01ac53efe6a627080fafc127c873da40c18d87b3f5d5492d465bb85ec7207e153948df6b9cbaeb130be70152f874229b8242ee2be84c0794082510af97f12 + languageName: node + linkType: hard + +"p-limit@npm:^3.0.2": + version: 3.1.0 + resolution: "p-limit@npm:3.1.0" + dependencies: + yocto-queue: "npm:^0.1.0" + checksum: 10c0/9db675949dbdc9c3763c89e748d0ef8bdad0afbb24d49ceaf4c46c02c77d30db4e0652ed36d0a0a7a95154335fab810d95c86153105bb73b3a90448e2bb14e1a + languageName: node + linkType: hard + +"p-locate@npm:^4.1.0": + version: 4.1.0 + resolution: "p-locate@npm:4.1.0" + dependencies: + p-limit: "npm:^2.2.0" + checksum: 10c0/1b476ad69ad7f6059744f343b26d51ce091508935c1dbb80c4e0a2f397ffce0ca3a1f9f5cd3c7ce19d7929a09719d5c65fe70d8ee289c3f267cd36f2881813e9 + languageName: node + linkType: hard + +"p-locate@npm:^5.0.0": + version: 5.0.0 + resolution: "p-locate@npm:5.0.0" + dependencies: + p-limit: "npm:^3.0.2" + checksum: 10c0/2290d627ab7903b8b70d11d384fee714b797f6040d9278932754a6860845c4d3190603a0772a663c8cb5a7b21d1b16acb3a6487ebcafa9773094edc3dfe6009a + languageName: node + linkType: hard + +"p-map@npm:^7.0.2": + version: 7.0.4 + resolution: "p-map@npm:7.0.4" + checksum: 10c0/a5030935d3cb2919d7e89454d1ce82141e6f9955413658b8c9403cfe379283770ed3048146b44cde168aa9e8c716505f196d5689db0ae3ce9a71521a2fef3abd + languageName: node + linkType: hard + +"p-try@npm:^2.0.0": + version: 2.2.0 + resolution: "p-try@npm:2.2.0" + checksum: 10c0/c36c19907734c904b16994e6535b02c36c2224d433e01a2f1ab777237f4d86e6289fd5fd464850491e940379d4606ed850c03e0f9ab600b0ebddb511312e177f + languageName: node + linkType: hard + +"package-json-from-dist@npm:^1.0.0": + version: 1.0.1 + resolution: "package-json-from-dist@npm:1.0.1" + checksum: 10c0/62ba2785eb655fec084a257af34dbe24292ab74516d6aecef97ef72d4897310bc6898f6c85b5cd22770eaa1ce60d55a0230e150fb6a966e3ecd6c511e23d164b + languageName: node + linkType: hard + +"pako@npm:~1.0.5": + version: 1.0.11 + resolution: "pako@npm:1.0.11" + checksum: 10c0/86dd99d8b34c3930345b8bbeb5e1cd8a05f608eeb40967b293f72fe469d0e9c88b783a8777e4cc7dc7c91ce54c5e93d88ff4b4f060e6ff18408fd21030d9ffbe + languageName: node + linkType: hard + +"param-case@npm:^3.0.4": + version: 3.0.4 + resolution: "param-case@npm:3.0.4" + dependencies: + dot-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + checksum: 10c0/ccc053f3019f878eca10e70ec546d92f51a592f762917dafab11c8b532715dcff58356118a6f350976e4ab109e321756f05739643ed0ca94298e82291e6f9e76 + languageName: node + linkType: hard + +"parent-module@npm:^1.0.0": + version: 1.0.1 + resolution: "parent-module@npm:1.0.1" + dependencies: + callsites: "npm:^3.0.0" + checksum: 10c0/c63d6e80000d4babd11978e0d3fee386ca7752a02b035fd2435960ffaa7219dc42146f07069fb65e6e8bf1caef89daf9af7535a39bddf354d78bf50d8294f556 + languageName: node + linkType: hard + +"parse-asn1@npm:^5.0.0, parse-asn1@npm:^5.1.9": + version: 5.1.9 + resolution: "parse-asn1@npm:5.1.9" + dependencies: + asn1.js: "npm:^4.10.1" + browserify-aes: "npm:^1.2.0" + evp_bytestokey: "npm:^1.0.3" + pbkdf2: "npm:^3.1.5" + safe-buffer: "npm:^5.2.1" + checksum: 10c0/6dfe27c121be3d63ebbf95f03d2ae0a07dd716d44b70b0bd3458790a822a80de05361c62147271fd7b845dcc2d37755d9c9c393064a3438fe633779df0bc07e7 + languageName: node + linkType: hard + +"parse-json@npm:^5.0.0, parse-json@npm:^5.2.0": + version: 5.2.0 + resolution: "parse-json@npm:5.2.0" + dependencies: + "@babel/code-frame": "npm:^7.0.0" + error-ex: "npm:^1.3.1" + json-parse-even-better-errors: "npm:^2.3.0" + lines-and-columns: "npm:^1.1.6" + checksum: 10c0/77947f2253005be7a12d858aedbafa09c9ae39eb4863adf330f7b416ca4f4a08132e453e08de2db46459256fb66afaac5ee758b44fe6541b7cdaf9d252e59585 + languageName: node + linkType: hard + +"parse-node-version@npm:^1.0.1": + version: 1.0.1 + resolution: "parse-node-version@npm:1.0.1" + checksum: 10c0/999cd3d7da1425c2e182dce82b226c6dc842562d3ed79ec47f5c719c32a7f6c1a5352495b894fc25df164be7f2ede4224758255da9902ddef81f2b77ba46bb2c + languageName: node + linkType: hard + +"parseurl@npm:~1.3.3": + version: 1.3.3 + resolution: "parseurl@npm:1.3.3" + checksum: 10c0/90dd4760d6f6174adb9f20cf0965ae12e23879b5f5464f38e92fce8073354341e4b3b76fa3d878351efe7d01e617121955284cfd002ab087fba1a0726ec0b4f5 + languageName: node + linkType: hard + +"pascal-case@npm:^3.1.2": + version: 3.1.2 + resolution: "pascal-case@npm:3.1.2" + dependencies: + no-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + checksum: 10c0/05ff7c344809fd272fc5030ae0ee3da8e4e63f36d47a1e0a4855ca59736254192c5a27b5822ed4bae96e54048eec5f6907713cfcfff7cdf7a464eaf7490786d8 + languageName: node + linkType: hard + +"path-browserify@npm:0.0.1": + version: 0.0.1 + resolution: "path-browserify@npm:0.0.1" + checksum: 10c0/3d59710cddeea06509d91935196185900f3d9d29376dff68ff0e146fbd41d0fb304e983d0158f30cabe4dd2ffcc6a7d3d977631994ee984c88e66aed50a1ccd3 + languageName: node + linkType: hard + +"path-exists@npm:^4.0.0": + version: 4.0.0 + resolution: "path-exists@npm:4.0.0" + checksum: 10c0/8c0bd3f5238188197dc78dced15207a4716c51cc4e3624c44fc97acf69558f5ebb9a2afff486fe1b4ee148e0c133e96c5e11a9aa5c48a3006e3467da070e5e1b + languageName: node + linkType: hard + +"path-is-absolute@npm:^1.0.0": + version: 1.0.1 + resolution: "path-is-absolute@npm:1.0.1" + checksum: 10c0/127da03c82172a2a50099cddbf02510c1791fc2cc5f7713ddb613a56838db1e8168b121a920079d052e0936c23005562059756d653b7c544c53185efe53be078 + languageName: node + linkType: hard + +"path-key@npm:^3.0.0, path-key@npm:^3.1.0": + version: 3.1.1 + resolution: "path-key@npm:3.1.1" + checksum: 10c0/748c43efd5a569c039d7a00a03b58eecd1d75f3999f5a28303d75f521288df4823bc057d8784eb72358b2895a05f29a070bc9f1f17d28226cc4e62494cc58c4c + languageName: node + linkType: hard + +"path-key@npm:^4.0.0": + version: 4.0.0 + resolution: "path-key@npm:4.0.0" + checksum: 10c0/794efeef32863a65ac312f3c0b0a99f921f3e827ff63afa5cb09a377e202c262b671f7b3832a4e64731003fa94af0263713962d317b9887bd1e0c48a342efba3 + languageName: node + linkType: hard + +"path-parse@npm:^1.0.7": + version: 1.0.7 + resolution: "path-parse@npm:1.0.7" + checksum: 10c0/11ce261f9d294cc7a58d6a574b7f1b935842355ec66fba3c3fd79e0f036462eaf07d0aa95bb74ff432f9afef97ce1926c720988c6a7451d8a584930ae7de86e1 + languageName: node + linkType: hard + +"path-scurry@npm:^1.11.1": + version: 1.11.1 + resolution: "path-scurry@npm:1.11.1" + dependencies: + lru-cache: "npm:^10.2.0" + minipass: "npm:^5.0.0 || ^6.0.2 || ^7.0.0" + checksum: 10c0/32a13711a2a505616ae1cc1b5076801e453e7aae6ac40ab55b388bb91b9d0547a52f5aaceff710ea400205f18691120d4431e520afbe4266b836fadede15872d + languageName: node + linkType: hard + +"path-scurry@npm:^2.0.0": + version: 2.0.1 + resolution: "path-scurry@npm:2.0.1" + dependencies: + lru-cache: "npm:^11.0.0" + minipass: "npm:^7.1.2" + checksum: 10c0/2a16ed0e81fbc43513e245aa5763354e25e787dab0d539581a6c3f0f967461a159ed6236b2559de23aa5b88e7dc32b469b6c47568833dd142a4b24b4f5cd2620 + languageName: node + linkType: hard + +"path-to-regexp@npm:0.1.12": + version: 0.1.12 + resolution: "path-to-regexp@npm:0.1.12" + checksum: 10c0/1c6ff10ca169b773f3bba943bbc6a07182e332464704572962d277b900aeee81ac6aa5d060ff9e01149636c30b1f63af6e69dd7786ba6e0ddb39d4dee1f0645b + languageName: node + linkType: hard + +"path-to-regexp@npm:1.7.0": + version: 1.7.0 + resolution: "path-to-regexp@npm:1.7.0" + dependencies: + isarray: "npm:0.0.1" + checksum: 10c0/ac2def3e136f215bb38fca13c6b6a7e4a5274a9657fcf02b231ba1036a727d6ff74e98fdff97149484d9028fafa903bdbcb8f54832ab220d47605218414b94f6 + languageName: node + linkType: hard + +"path-to-regexp@npm:8.2.0": + version: 8.2.0 + resolution: "path-to-regexp@npm:8.2.0" + checksum: 10c0/ef7d0a887b603c0a142fad16ccebdcdc42910f0b14830517c724466ad676107476bba2fe9fffd28fd4c141391ccd42ea426f32bb44c2c82ecaefe10c37b90f5a + languageName: node + linkType: hard + +"path-type@npm:^4.0.0": + version: 4.0.0 + resolution: "path-type@npm:4.0.0" + checksum: 10c0/666f6973f332f27581371efaf303fd6c272cc43c2057b37aa99e3643158c7e4b2626549555d88626e99ea9e046f82f32e41bbde5f1508547e9a11b149b52387c + languageName: node + linkType: hard + +"pbkdf2@npm:^3.1.2, pbkdf2@npm:^3.1.5": + version: 3.1.5 + resolution: "pbkdf2@npm:3.1.5" + dependencies: + create-hash: "npm:^1.2.0" + create-hmac: "npm:^1.1.7" + ripemd160: "npm:^2.0.3" + safe-buffer: "npm:^5.2.1" + sha.js: "npm:^2.4.12" + to-buffer: "npm:^1.2.1" + checksum: 10c0/ea42e8695e49417eefabb19a08ab19a602cc6cc72d2df3f109c39309600230dee3083a6f678d5d42fe035d6ae780038b80ace0e68f9792ee2839bf081fe386f3 + languageName: node + linkType: hard + +"pend@npm:~1.2.0": + version: 1.2.0 + resolution: "pend@npm:1.2.0" + checksum: 10c0/8a87e63f7a4afcfb0f9f77b39bb92374afc723418b9cb716ee4257689224171002e07768eeade4ecd0e86f1fa3d8f022994219fb45634f2dbd78c6803e452458 + languageName: node + linkType: hard + +"picocolors@npm:^1.0.0, picocolors@npm:^1.1.1": + version: 1.1.1 + resolution: "picocolors@npm:1.1.1" + checksum: 10c0/e2e3e8170ab9d7c7421969adaa7e1b31434f789afb9b3f115f6b96d91945041ac3ceb02e9ec6fe6510ff036bcc0bf91e69a1772edc0b707e12b19c0f2d6bcf58 + languageName: node + linkType: hard + +"picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.2.3, picomatch@npm:^2.3.1": + version: 2.3.1 + resolution: "picomatch@npm:2.3.1" + checksum: 10c0/26c02b8d06f03206fc2ab8d16f19960f2ff9e81a658f831ecb656d8f17d9edc799e8364b1f4a7873e89d9702dff96204be0fa26fe4181f6843f040f819dac4be + languageName: node + linkType: hard + +"picomatch@npm:^4.0.3": + version: 4.0.3 + resolution: "picomatch@npm:4.0.3" + checksum: 10c0/9582c951e95eebee5434f59e426cddd228a7b97a0161a375aed4be244bd3fe8e3a31b846808ea14ef2c8a2527a6eeab7b3946a67d5979e81694654f939473ae2 + languageName: node + linkType: hard + +"pify@npm:^2.3.0": + version: 2.3.0 + resolution: "pify@npm:2.3.0" + checksum: 10c0/551ff8ab830b1052633f59cb8adc9ae8407a436e06b4a9718bcb27dc5844b83d535c3a8512b388b6062af65a98c49bdc0dd523d8b2617b188f7c8fee457158dc + languageName: node + linkType: hard + +"pify@npm:^4.0.1": + version: 4.0.1 + resolution: "pify@npm:4.0.1" + checksum: 10c0/6f9d404b0d47a965437403c9b90eca8bb2536407f03de165940e62e72c8c8b75adda5516c6b9b23675a5877cc0bcac6bdfb0ef0e39414cd2476d5495da40e7cf + languageName: node + linkType: hard + +"pino-abstract-transport@npm:v0.5.0": + version: 0.5.0 + resolution: "pino-abstract-transport@npm:0.5.0" + dependencies: + duplexify: "npm:^4.1.2" + split2: "npm:^4.0.0" + checksum: 10c0/0d0e30399028ec156642b4cdfe1a040b9022befdc38e8f85935d1837c3da6050691888038433f88190d1a1eff5d90abe17ff7e6edffc09baa2f96e51b6808183 + languageName: node + linkType: hard + +"pino-std-serializers@npm:^4.0.0": + version: 4.0.0 + resolution: "pino-std-serializers@npm:4.0.0" + checksum: 10c0/9e8ccac9ce04a27ccc7aa26481d431b9e037d866b101b89d895c60b925baffb82685e84d5c29b05d8e3d7c146d766a9b08949cb24ab1ec526a16134c9962d649 + languageName: node + linkType: hard + +"pino@npm:7.11.0": + version: 7.11.0 + resolution: "pino@npm:7.11.0" + dependencies: + atomic-sleep: "npm:^1.0.0" + fast-redact: "npm:^3.0.0" + on-exit-leak-free: "npm:^0.2.0" + pino-abstract-transport: "npm:v0.5.0" + pino-std-serializers: "npm:^4.0.0" + process-warning: "npm:^1.0.0" + quick-format-unescaped: "npm:^4.0.3" + real-require: "npm:^0.1.0" + safe-stable-stringify: "npm:^2.1.0" + sonic-boom: "npm:^2.2.1" + thread-stream: "npm:^0.15.1" + bin: + pino: bin.js + checksum: 10c0/4cc1ed9d25a4bc5d61c836a861279fa0039159b8f2f37ec337e50b0a61f3980dab5d2b1393daec26f68a19c423262649f0818654c9ad102c35310544a202c62c + languageName: node + linkType: hard + +"pirates@npm:^4.0.1, pirates@npm:^4.0.4": + version: 4.0.7 + resolution: "pirates@npm:4.0.7" + checksum: 10c0/a51f108dd811beb779d58a76864bbd49e239fa40c7984cd11596c75a121a8cc789f1c8971d8bb15f0dbf9d48b76c05bb62fcbce840f89b688c0fa64b37e8478a + languageName: node + linkType: hard + +"piscina@npm:^4.5.1": + version: 4.9.2 + resolution: "piscina@npm:4.9.2" + dependencies: + "@napi-rs/nice": "npm:^1.0.1" + dependenciesMeta: + "@napi-rs/nice": + optional: true + checksum: 10c0/ab67830065ff41523cd901db41b11045cb00a0be43bf79323ff7b4ef2fbce5e3a56ad440d99d6c3944ce94451a0a69fd175500e3220b21efe54142e601322189 + languageName: node + linkType: hard + +"plist@npm:^3.0.1, plist@npm:^3.0.4": + version: 3.1.0 + resolution: "plist@npm:3.1.0" + dependencies: + "@xmldom/xmldom": "npm:^0.8.8" + base64-js: "npm:^1.5.1" + xmlbuilder: "npm:^15.1.1" + checksum: 10c0/db19ba50faafc4103df8e79bcd6b08004a56db2a9dd30b3e5c8b0ef30398ef44344a674e594d012c8fc39e539a2b72cb58c60a76b4b4401cbbc7c8f6b028d93d + languageName: node + linkType: hard + +"point-in-polygon@npm:^1.1.0": + version: 1.1.0 + resolution: "point-in-polygon@npm:1.1.0" + checksum: 10c0/de00419585ee25555d97585b7a23eeb2464a87ef29404264bee55654ca2ecab5a5a99d33e689c07d045faf80091e838f44a1fd130bdd6134493df53114947343 + languageName: node + linkType: hard + +"possible-typed-array-names@npm:^1.0.0": + version: 1.1.0 + resolution: "possible-typed-array-names@npm:1.1.0" + checksum: 10c0/c810983414142071da1d644662ce4caebce890203eb2bc7bf119f37f3fe5796226e117e6cca146b521921fa6531072674174a3325066ac66fce089a53e1e5196 + languageName: node + linkType: hard + +"postcss-attribute-case-insensitive@npm:^5.0.0": + version: 5.0.2 + resolution: "postcss-attribute-case-insensitive@npm:5.0.2" + dependencies: + postcss-selector-parser: "npm:^6.0.10" + peerDependencies: + postcss: ^8.2 + checksum: 10c0/4efdca69aae9b0fa44b4960bcb3d49e37e9a79acf56534c83f925375007baad4b3560a7b0c244ee9956415a6997f84e0d4bd838281d085023afa9f8f96eeb4d2 + languageName: node + linkType: hard + +"postcss-clamp@npm:^4.1.0": + version: 4.1.0 + resolution: "postcss-clamp@npm:4.1.0" + dependencies: + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.4.6 + checksum: 10c0/701261026b38a4c27b3c3711635fac96005f36d3270adb76dbdb1eebc950fc841db45283ee66068a7121565592e9d7967d5534e15b6e4dd266afcabf9eafa905 + languageName: node + linkType: hard + +"postcss-color-functional-notation@npm:^4.2.2": + version: 4.2.4 + resolution: "postcss-color-functional-notation@npm:4.2.4" + dependencies: + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.2 + checksum: 10c0/e80785d10d252512f290c9d5e9436d8ea9e986a4a3f7ccb57ca9a5c2cd7fbff2498287d907c0e887dc6f69de66f6321ba40ebb8dbb7f47dace2050786b04c55e + languageName: node + linkType: hard + +"postcss-color-hex-alpha@npm:^8.0.3": + version: 8.0.4 + resolution: "postcss-color-hex-alpha@npm:8.0.4" + dependencies: + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.4 + checksum: 10c0/c18e1363e36f29b90e1d62d7da0f7adfd20948de3da46ddc468ddad142db3a782c4e153ada8d283cf011d090498976b1f2072973842dae0c3084eda33c0d1add + languageName: node + linkType: hard + +"postcss-color-rebeccapurple@npm:^7.0.2": + version: 7.1.1 + resolution: "postcss-color-rebeccapurple@npm:7.1.1" + dependencies: + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.2 + checksum: 10c0/2164b2dc8f91788a60180fbf80368851699a78664115fc9905fe8592da9a600930e7d381656e43c45ee2c8fcd9b5d146cd90f640cea75a534e3bc4d6e8b939dd + languageName: node + linkType: hard + +"postcss-custom-media@npm:^8.0.0": + version: 8.0.2 + resolution: "postcss-custom-media@npm:8.0.2" + dependencies: + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.3 + checksum: 10c0/e60a01983499c85e614cf58ddae92d340f8421d53eea080dadfd822d8299469c34114c511498c8158c7b04eae7f1853ede936c17a22582b5434432efb7878aac + languageName: node + linkType: hard + +"postcss-custom-properties@npm:^12.1.7": + version: 12.1.11 + resolution: "postcss-custom-properties@npm:12.1.11" + dependencies: + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.2 + checksum: 10c0/99ad5a9f9a69590141157e447f48d9d6da74f0e83bf552cd5a4e74db7a03222f1e9e37df7ee442a7b97f5c6c824c1018667ee27ac64e0bc6ee7e67e89bc552c5 + languageName: node + linkType: hard + +"postcss-custom-selectors@npm:^6.0.0": + version: 6.0.3 + resolution: "postcss-custom-selectors@npm:6.0.3" + dependencies: + postcss-selector-parser: "npm:^6.0.4" + peerDependencies: + postcss: ^8.3 + checksum: 10c0/f1dd42b269e57382f48c2e71daf233badafd3e161b70b36140e934c87f9c035cec585ae5b124447d8673644f94adeb9348dfbb8ef5225e085d52ee179090fdbd + languageName: node + linkType: hard + +"postcss-dir-pseudo-class@npm:^6.0.4": + version: 6.0.5 + resolution: "postcss-dir-pseudo-class@npm:6.0.5" + dependencies: + postcss-selector-parser: "npm:^6.0.10" + peerDependencies: + postcss: ^8.2 + checksum: 10c0/5b389c3a1e8387a7fb212fb652eb2bc6c2e10a9ebf5bc5917f5bf889779b3dadb64735566a75d16cca3791303e16fb09276b0aebd95c11ef1788120d714c2f95 + languageName: node + linkType: hard + +"postcss-double-position-gradients@npm:^3.1.1": + version: 3.1.2 + resolution: "postcss-double-position-gradients@npm:3.1.2" + dependencies: + "@csstools/postcss-progressive-custom-properties": "npm:^1.1.0" + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.2 + checksum: 10c0/4a2c93c1158773d10a7300e036a323f406e64c082a243ef20bb52d7062c675d754436e5a8b014302a387fc2c2acbee673916f09e4e82287164d13bc032130bf7 + languageName: node + linkType: hard + +"postcss-env-function@npm:^4.0.6": + version: 4.0.6 + resolution: "postcss-env-function@npm:4.0.6" + dependencies: + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.4 + checksum: 10c0/e2dfbfd2c6731a1b482658f6410465f6fa623fc92099c825079c0322d9d68f526cf9c718fe9ac89d166936fb0ed6e14e78028b187f77a27519ac17ed75123f27 + languageName: node + linkType: hard + +"postcss-flexbugs-fixes@npm:5.0.2": + version: 5.0.2 + resolution: "postcss-flexbugs-fixes@npm:5.0.2" + peerDependencies: + postcss: ^8.1.4 + checksum: 10c0/b413f73cc3c005f33479df95e1357467c28183e62ba8b25e06b8590b2a69e60d624f07824c0ff85fb1dfdd5bb7dfa321dad0885d42ec3c8f000669960b30894f + languageName: node + linkType: hard + +"postcss-focus-visible@npm:^6.0.4": + version: 6.0.4 + resolution: "postcss-focus-visible@npm:6.0.4" + dependencies: + postcss-selector-parser: "npm:^6.0.9" + peerDependencies: + postcss: ^8.4 + checksum: 10c0/acc3a2780908d2f4941b1e34ed349a55e965f6dfad066cecad8ad58b6a6ad3576bacb08c0cfa828cea00c2695c8a7b756ec97d40db9104bd9f13b8d172b72698 + languageName: node + linkType: hard + +"postcss-focus-within@npm:^5.0.4": + version: 5.0.4 + resolution: "postcss-focus-within@npm:5.0.4" + dependencies: + postcss-selector-parser: "npm:^6.0.9" + peerDependencies: + postcss: ^8.4 + checksum: 10c0/e8dacdfcad2a24d1c26693156660f96749178564a9b6b27fba6380418a2253c72c66898cdcea15c5f627527148a30e9000edb25a07245b5b032fc61acd6174fd + languageName: node + linkType: hard + +"postcss-font-variant@npm:^5.0.0": + version: 5.0.0 + resolution: "postcss-font-variant@npm:5.0.0" + peerDependencies: + postcss: ^8.1.0 + checksum: 10c0/ccc96460cf6a52b5439c26c9a5ea0589882e46161e3c2331d4353de7574448f5feef667d1a68f7f39b9fe3ee75d85957383ae82bbfcf87c3162c7345df4a444e + languageName: node + linkType: hard + +"postcss-gap-properties@npm:^3.0.3": + version: 3.0.5 + resolution: "postcss-gap-properties@npm:3.0.5" + peerDependencies: + postcss: ^8.2 + checksum: 10c0/402f830aa6661aa5bd01ae227c189124a5c22ba8e6a95ea0c205148a85732b147c6f5f60c2b67d8a971d0223f5579e891fa9543ea7611470d6fd84729ea0f3bb + languageName: node + linkType: hard + +"postcss-image-set-function@npm:^4.0.6": + version: 4.0.7 + resolution: "postcss-image-set-function@npm:4.0.7" + dependencies: + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.2 + checksum: 10c0/ed79dcf62f295c300fce12f09eb498d7016a4ef5739474e6654e454a8627147a4908be56e5316afc2733bf118b95e59bdfedb03c67d0d43c364f76be62806598 + languageName: node + linkType: hard + +"postcss-import@npm:^15.1.0": + version: 15.1.0 + resolution: "postcss-import@npm:15.1.0" + dependencies: + postcss-value-parser: "npm:^4.0.0" + read-cache: "npm:^1.0.0" + resolve: "npm:^1.1.7" + peerDependencies: + postcss: ^8.0.0 + checksum: 10c0/518aee5c83ea6940e890b0be675a2588db68b2582319f48c3b4e06535a50ea6ee45f7e63e4309f8754473245c47a0372632378d1d73d901310f295a92f26f17b + languageName: node + linkType: hard + +"postcss-initial@npm:^4.0.1": + version: 4.0.1 + resolution: "postcss-initial@npm:4.0.1" + peerDependencies: + postcss: ^8.0.0 + checksum: 10c0/a1db8350c31c5a23064c1e0d18cf6530bb96a6532d11e9caf1c632796b4ad48cb58ff17331bf0a5e3a360c4be1819e489cd1faeb3afc77711d333a0ee4f07819 + languageName: node + linkType: hard + +"postcss-js@npm:^4.0.1": + version: 4.1.0 + resolution: "postcss-js@npm:4.1.0" + dependencies: + camelcase-css: "npm:^2.0.1" + peerDependencies: + postcss: ^8.4.21 + checksum: 10c0/a3cf6e725f3e9ecd7209732f8844a0063a1380b718ccbcf93832b6ec2cd7e63ff70dd2fed49eb2483c7482296860a0f7badd3115b5d0fa05ea648eb6d9dfc9c6 + languageName: node + linkType: hard + +"postcss-lab-function@npm:^4.2.0": + version: 4.2.1 + resolution: "postcss-lab-function@npm:4.2.1" + dependencies: + "@csstools/postcss-progressive-custom-properties": "npm:^1.1.0" + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.2 + checksum: 10c0/70744444951d95a06a586634e7fa7c77fe4a42c7d15e556a6e7b9a5a60e03a067d371f6d16e8f58274a5e4ebbd2bd505a4bee0b03974d5571459d72ab9fb157c + languageName: node + linkType: hard + +"postcss-load-config@npm:^4.0.2 || ^5.0 || ^6.0": + version: 6.0.1 + resolution: "postcss-load-config@npm:6.0.1" + dependencies: + lilconfig: "npm:^3.1.1" + peerDependencies: + jiti: ">=1.21.0" + postcss: ">=8.0.9" + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + checksum: 10c0/74173a58816dac84e44853f7afbd283f4ef13ca0b6baeba27701214beec33f9e309b128f8102e2b173e8d45ecba45d279a9be94b46bf48d219626aa9b5730848 + languageName: node + linkType: hard + +"postcss-loader@npm:^8.1.1": + version: 8.2.0 + resolution: "postcss-loader@npm:8.2.0" + dependencies: + cosmiconfig: "npm:^9.0.0" + jiti: "npm:^2.5.1" + semver: "npm:^7.6.2" + peerDependencies: + "@rspack/core": 0.x || 1.x + postcss: ^7.0.0 || ^8.0.1 + webpack: ^5.0.0 + peerDependenciesMeta: + "@rspack/core": + optional: true + webpack: + optional: true + checksum: 10c0/471f9a1c313522580f3385b92ab847cf161c6972bedc73525126a3c0a08733f0f6444d04ca9e0a8b1e36b44123e103dfcd8f53378b7e5afc95fa6d9ab423c480 + languageName: node + linkType: hard + +"postcss-logical@npm:^5.0.4": + version: 5.0.4 + resolution: "postcss-logical@npm:5.0.4" + peerDependencies: + postcss: ^8.4 + checksum: 10c0/1a49e2123357b85d41e679a30b7450165295e945342ddbb88dbcc48ebe7b69afbe34ff69ebdd6d8adaf1293a7bcecae51152d7f44514194bde9b98221780e494 + languageName: node + linkType: hard + +"postcss-media-minmax@npm:^5.0.0": + version: 5.0.0 + resolution: "postcss-media-minmax@npm:5.0.0" + peerDependencies: + postcss: ^8.1.0 + checksum: 10c0/ee04b1b9eb5b003dfea344baf14424cc8b2600c784f37fe9af097252d6e35ed786bbf7ce36d19592d632d238ad15b9128a4247653df0cadcabbe1fbc137295fe + languageName: node + linkType: hard + +"postcss-modules-extract-imports@npm:^3.0.0": + version: 3.1.0 + resolution: "postcss-modules-extract-imports@npm:3.1.0" + peerDependencies: + postcss: ^8.1.0 + checksum: 10c0/402084bcab376083c4b1b5111b48ec92974ef86066f366f0b2d5b2ac2b647d561066705ade4db89875a13cb175b33dd6af40d16d32b2ea5eaf8bac63bd2bf219 + languageName: node + linkType: hard + +"postcss-modules-local-by-default@npm:^4.0.0": + version: 4.2.0 + resolution: "postcss-modules-local-by-default@npm:4.2.0" + dependencies: + icss-utils: "npm:^5.0.0" + postcss-selector-parser: "npm:^7.0.0" + postcss-value-parser: "npm:^4.1.0" + peerDependencies: + postcss: ^8.1.0 + checksum: 10c0/b0b83feb2a4b61f5383979d37f23116c99bc146eba1741ca3cf1acca0e4d0dbf293ac1810a6ab4eccbe1ee76440dd0a9eb2db5b3bba4f99fc1b3ded16baa6358 + languageName: node + linkType: hard + +"postcss-modules-scope@npm:^3.0.0": + version: 3.2.1 + resolution: "postcss-modules-scope@npm:3.2.1" + dependencies: + postcss-selector-parser: "npm:^7.0.0" + peerDependencies: + postcss: ^8.1.0 + checksum: 10c0/bd2d81f79e3da0ef6365b8e2c78cc91469d05b58046b4601592cdeef6c4050ed8fe1478ae000a1608042fc7e692cb51fecbd2d9bce3f4eace4d32e883ffca10b + languageName: node + linkType: hard + +"postcss-modules-values@npm:^4.0.0": + version: 4.0.0 + resolution: "postcss-modules-values@npm:4.0.0" + dependencies: + icss-utils: "npm:^5.0.0" + peerDependencies: + postcss: ^8.1.0 + checksum: 10c0/dd18d7631b5619fb9921b198c86847a2a075f32e0c162e0428d2647685e318c487a2566cc8cc669fc2077ef38115cde7a068e321f46fb38be3ad49646b639dbc + languageName: node + linkType: hard + +"postcss-nested@npm:^6.2.0": + version: 6.2.0 + resolution: "postcss-nested@npm:6.2.0" + dependencies: + postcss-selector-parser: "npm:^6.1.1" + peerDependencies: + postcss: ^8.2.14 + checksum: 10c0/7f9c3f2d764191a39364cbdcec350f26a312431a569c9ef17408021424726b0d67995ff5288405e3724bb7152a4c92f73c027e580ec91e798800ed3c52e2bc6e + languageName: node + linkType: hard + +"postcss-nesting@npm:^10.1.4": + version: 10.2.0 + resolution: "postcss-nesting@npm:10.2.0" + dependencies: + "@csstools/selector-specificity": "npm:^2.0.0" + postcss-selector-parser: "npm:^6.0.10" + peerDependencies: + postcss: ^8.2 + checksum: 10c0/1f44201edeedaab3af8552a7e231cf8530785245ec56e30a7f756076ffa58ec97c12b75a8761327bf278b26aa9903351b2f3324d11784f239b07dc79295e0a77 + languageName: node + linkType: hard + +"postcss-opacity-percentage@npm:^1.1.2": + version: 1.1.3 + resolution: "postcss-opacity-percentage@npm:1.1.3" + peerDependencies: + postcss: ^8.2 + checksum: 10c0/9cd9076561beeadb5c658a17e6fc657396a9497c9e0b0b6267931c6bb729052a150eccbeae33d27db533f5ac3cf806eb068eccb110b65d14a5dfea2e35d0877f + languageName: node + linkType: hard + +"postcss-overflow-shorthand@npm:^3.0.3": + version: 3.0.4 + resolution: "postcss-overflow-shorthand@npm:3.0.4" + dependencies: + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.2 + checksum: 10c0/d95d114fecceb83a2a2385bb073a16824efaa9b2c685d900af22f764c2a8c1de6c267230df870e4d7f98310e92618b86ba6344b76877d6f4d2158c019181f476 + languageName: node + linkType: hard + +"postcss-page-break@npm:^3.0.4": + version: 3.0.4 + resolution: "postcss-page-break@npm:3.0.4" + peerDependencies: + postcss: ^8 + checksum: 10c0/eaaf4d8922b35f2acd637eb059f7e2510b24d65eb8f31424799dd5a98447b6ef010b41880c26e78f818e00f842295638ec75f89d5d489067f53e3dd3db74a00f + languageName: node + linkType: hard + +"postcss-place@npm:^7.0.4": + version: 7.0.5 + resolution: "postcss-place@npm:7.0.5" + dependencies: + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.2 + checksum: 10c0/149941027e6194f166ab5e7bbddc722c0d18e1f5e8117fe0af3689b216c70df9762052484965ab71271ae1d3a0ec0a7f361ce3b3dfd1f28e0bbfd0d554dd1a11 + languageName: node + linkType: hard + +"postcss-prefix-selector@npm:1.16.0": + version: 1.16.0 + resolution: "postcss-prefix-selector@npm:1.16.0" + peerDependencies: + postcss: ">4 <9" + checksum: 10c0/edc78fd0d8885ad77907044cb25afb6b49c81170810025afca47d6984f86479503a63293910cd4e9b296a7e5d876ab7473c481180774e7e7a55a6edf02746469 + languageName: node + linkType: hard + +"postcss-preset-env@npm:7.5.0": + version: 7.5.0 + resolution: "postcss-preset-env@npm:7.5.0" + dependencies: + "@csstools/postcss-color-function": "npm:^1.1.0" + "@csstools/postcss-font-format-keywords": "npm:^1.0.0" + "@csstools/postcss-hwb-function": "npm:^1.0.0" + "@csstools/postcss-ic-unit": "npm:^1.0.0" + "@csstools/postcss-is-pseudo-class": "npm:^2.0.2" + "@csstools/postcss-normalize-display-values": "npm:^1.0.0" + "@csstools/postcss-oklab-function": "npm:^1.1.0" + "@csstools/postcss-progressive-custom-properties": "npm:^1.3.0" + "@csstools/postcss-stepped-value-functions": "npm:^1.0.0" + "@csstools/postcss-unset-value": "npm:^1.0.0" + autoprefixer: "npm:^10.4.6" + browserslist: "npm:^4.20.3" + css-blank-pseudo: "npm:^3.0.3" + css-has-pseudo: "npm:^3.0.4" + css-prefers-color-scheme: "npm:^6.0.3" + cssdb: "npm:^6.6.1" + postcss-attribute-case-insensitive: "npm:^5.0.0" + postcss-clamp: "npm:^4.1.0" + postcss-color-functional-notation: "npm:^4.2.2" + postcss-color-hex-alpha: "npm:^8.0.3" + postcss-color-rebeccapurple: "npm:^7.0.2" + postcss-custom-media: "npm:^8.0.0" + postcss-custom-properties: "npm:^12.1.7" + postcss-custom-selectors: "npm:^6.0.0" + postcss-dir-pseudo-class: "npm:^6.0.4" + postcss-double-position-gradients: "npm:^3.1.1" + postcss-env-function: "npm:^4.0.6" + postcss-focus-visible: "npm:^6.0.4" + postcss-focus-within: "npm:^5.0.4" + postcss-font-variant: "npm:^5.0.0" + postcss-gap-properties: "npm:^3.0.3" + postcss-image-set-function: "npm:^4.0.6" + postcss-initial: "npm:^4.0.1" + postcss-lab-function: "npm:^4.2.0" + postcss-logical: "npm:^5.0.4" + postcss-media-minmax: "npm:^5.0.0" + postcss-nesting: "npm:^10.1.4" + postcss-opacity-percentage: "npm:^1.1.2" + postcss-overflow-shorthand: "npm:^3.0.3" + postcss-page-break: "npm:^3.0.4" + postcss-place: "npm:^7.0.4" + postcss-pseudo-class-any-link: "npm:^7.1.2" + postcss-replace-overflow-wrap: "npm:^4.0.0" + postcss-selector-not: "npm:^5.0.0" + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.4 + checksum: 10c0/075255a53a7fdd9cb4cdbee1ca05ba421704ef04086edd43bd02e1d0bc6d68b861d55f4a46329290cd8ed45ea777d446894876b0bb99113962427e4071c1c4ec + languageName: node + linkType: hard + +"postcss-pseudo-class-any-link@npm:^7.1.2": + version: 7.1.6 + resolution: "postcss-pseudo-class-any-link@npm:7.1.6" + dependencies: + postcss-selector-parser: "npm:^6.0.10" + peerDependencies: + postcss: ^8.2 + checksum: 10c0/3f5cffbe4d5de7958ce220dc361ca1fb3c0985d0c44d007b2bdc7a780c412e57800a366fe9390218948cc0157697ba363ce9542e36a831c537b05b18a44dcecd + languageName: node + linkType: hard + +"postcss-replace-overflow-wrap@npm:^4.0.0": + version: 4.0.0 + resolution: "postcss-replace-overflow-wrap@npm:4.0.0" + peerDependencies: + postcss: ^8.0.3 + checksum: 10c0/451361b714528cd3632951256ef073769cde725a46cda642a6864f666fb144921fa55e614aec1bcf5946f37d6ffdcca3b932b76f3d997c07b076e8db152b128d + languageName: node + linkType: hard + +"postcss-safe-parser@npm:^7.0.1": + version: 7.0.1 + resolution: "postcss-safe-parser@npm:7.0.1" + peerDependencies: + postcss: ^8.4.31 + checksum: 10c0/6957b10b818bd8d4664ec0e548af967f7549abedfb37f844d389571d36af681340f41f9477b9ccf34bcc7599bdef222d1d72e79c64373001fae77089fba6d965 + languageName: node + linkType: hard + +"postcss-selector-not@npm:^5.0.0": + version: 5.0.0 + resolution: "postcss-selector-not@npm:5.0.0" + dependencies: + balanced-match: "npm:^1.0.0" + peerDependencies: + postcss: ^8.1.0 + checksum: 10c0/ee70e92d21f522d39082a640656b7233bd4917f21bcca0ce7e84e26ddf25ea40139c7475b663c7de19781c3a34498ab166d4968a86b2607a23c4310ad5d02acf + languageName: node + linkType: hard + +"postcss-selector-parser@npm:^6.0.10, postcss-selector-parser@npm:^6.0.4, postcss-selector-parser@npm:^6.0.9, postcss-selector-parser@npm:^6.1.1, postcss-selector-parser@npm:^6.1.2": + version: 6.1.2 + resolution: "postcss-selector-parser@npm:6.1.2" + dependencies: + cssesc: "npm:^3.0.0" + util-deprecate: "npm:^1.0.2" + checksum: 10c0/523196a6bd8cf660bdf537ad95abd79e546d54180f9afb165a4ab3e651ac705d0f8b8ce6b3164fb9e3279ce482c5f751a69eb2d3a1e8eb0fd5e82294fb3ef13e + languageName: node + linkType: hard + +"postcss-selector-parser@npm:^7.0.0": + version: 7.1.0 + resolution: "postcss-selector-parser@npm:7.1.0" + dependencies: + cssesc: "npm:^3.0.0" + util-deprecate: "npm:^1.0.2" + checksum: 10c0/0fef257cfd1c0fe93c18a3f8a6e739b4438b527054fd77e9a62730a89b2d0ded1b59314a7e4aaa55bc256204f40830fecd2eb50f20f8cb7ab3a10b52aa06c8aa + languageName: node + linkType: hard + +"postcss-selector-parser@npm:^7.1.1": + version: 7.1.1 + resolution: "postcss-selector-parser@npm:7.1.1" + dependencies: + cssesc: "npm:^3.0.0" + util-deprecate: "npm:^1.0.2" + checksum: 10c0/02d3b1589ddcddceed4b583b098b95a7266dacd5135f041e5d913ebb48e874fd333a36e564cc9a2ec426a464cb18db11cb192ac76247aced5eba8c951bf59507 + languageName: node + linkType: hard + +"postcss-syntax@npm:0.36.2": + version: 0.36.2 + resolution: "postcss-syntax@npm:0.36.2" + peerDependencies: + postcss: ">=5.0.0" + checksum: 10c0/28efff15190403d7ef3dbaad7e6647e2e5bd8aea5bf70fec406a8f60dc858d86bb861947fe9cbfe448ac00aaa4028904264072405cf6c69f8336a6df00b93a97 + languageName: node + linkType: hard + +"postcss-value-parser@npm:^4.0.0, postcss-value-parser@npm:^4.0.2, postcss-value-parser@npm:^4.1.0, postcss-value-parser@npm:^4.2.0": + version: 4.2.0 + resolution: "postcss-value-parser@npm:4.2.0" + checksum: 10c0/f4142a4f56565f77c1831168e04e3effd9ffcc5aebaf0f538eee4b2d465adfd4b85a44257bb48418202a63806a7da7fe9f56c330aebb3cac898e46b4cbf49161 + languageName: node + linkType: hard + +"postcss@npm:8.4.31": + version: 8.4.31 + resolution: "postcss@npm:8.4.31" + dependencies: + nanoid: "npm:^3.3.6" + picocolors: "npm:^1.0.0" + source-map-js: "npm:^1.0.2" + checksum: 10c0/748b82e6e5fc34034dcf2ae88ea3d11fd09f69b6c50ecdd3b4a875cfc7cdca435c958b211e2cb52355422ab6fccb7d8f2f2923161d7a1b281029e4a913d59acf + languageName: node + linkType: hard + +"postcss@npm:8.4.49": + version: 8.4.49 + resolution: "postcss@npm:8.4.49" + dependencies: + nanoid: "npm:^3.3.7" + picocolors: "npm:^1.1.1" + source-map-js: "npm:^1.2.1" + checksum: 10c0/f1b3f17aaf36d136f59ec373459f18129908235e65dbdc3aee5eef8eba0756106f52de5ec4682e29a2eab53eb25170e7e871b3e4b52a8f1de3d344a514306be3 + languageName: node + linkType: hard + +"postcss@npm:^8.2.14, postcss@npm:^8.4.21, postcss@npm:^8.4.27, postcss@npm:^8.4.31, postcss@npm:^8.4.47, postcss@npm:^8.4.7": + version: 8.5.6 + resolution: "postcss@npm:8.5.6" + dependencies: + nanoid: "npm:^3.3.11" + picocolors: "npm:^1.1.1" + source-map-js: "npm:^1.2.1" + checksum: 10c0/5127cc7c91ed7a133a1b7318012d8bfa112da9ef092dddf369ae699a1f10ebbd89b1b9f25f3228795b84585c72aabd5ced5fc11f2ba467eedf7b081a66fad024 + languageName: node + linkType: hard + +"postcss@npm:^8.5.9": + version: 8.5.10 + resolution: "postcss@npm:8.5.10" + dependencies: + nanoid: "npm:^3.3.11" + picocolors: "npm:^1.1.1" + source-map-js: "npm:^1.2.1" + checksum: 10c0/c592dffa0c4873b401f01955b265538d9942f425040df5e2b8f0ad34c83773a792ea0fa5859ccc99cfb5b955b4ebff118ab7056315388dc83b107b0fa8313576 + languageName: node + linkType: hard + +"prelude-ls@npm:^1.2.1": + version: 1.2.1 + resolution: "prelude-ls@npm:1.2.1" + checksum: 10c0/b00d617431e7886c520a6f498a2e14c75ec58f6d93ba48c3b639cf241b54232d90daa05d83a9e9b9fef6baa63cb7e1e4602c2372fea5bc169668401eb127d0cd + languageName: node + linkType: hard + +"prettier-linter-helpers@npm:^1.0.0": + version: 1.0.0 + resolution: "prettier-linter-helpers@npm:1.0.0" + dependencies: + fast-diff: "npm:^1.1.2" + checksum: 10c0/81e0027d731b7b3697ccd2129470ed9913ecb111e4ec175a12f0fcfab0096516373bf0af2fef132af50cafb0a905b74ff57996d615f59512bb9ac7378fcc64ab + languageName: node + linkType: hard + +"prettier-plugin-organize-imports@npm:^2": + version: 2.3.4 + resolution: "prettier-plugin-organize-imports@npm:2.3.4" + peerDependencies: + prettier: ">=2.0" + typescript: ">=2.9" + checksum: 10c0/f558385fe4dc871bde096efd69d1e952540f5ae5070b4163c4a398a4f8a09ea64a5b5dfa55ca7fbaa34d9039e4de2bc56c7ee9d5cbd96992301dc496550f4b0b + languageName: node + linkType: hard + +"prettier-plugin-organize-imports@npm:^3.2.2": + version: 3.2.4 + resolution: "prettier-plugin-organize-imports@npm:3.2.4" + peerDependencies: + "@volar/vue-language-plugin-pug": ^1.0.4 + "@volar/vue-typescript": ^1.0.4 + prettier: ">=2.0" + typescript: ">=2.9" + peerDependenciesMeta: + "@volar/vue-language-plugin-pug": + optional: true + "@volar/vue-typescript": + optional: true + checksum: 10c0/c20afa9b379106839a273d53c83fef70920e8ae86939d4890a06c63da19440de411568793e716bafcdd96e5ba8e34233f2944ea53ecd6ac18ba1ec0fa05bb58b + languageName: node + linkType: hard + +"prettier-plugin-packagejson@npm:2.4.3": + version: 2.4.3 + resolution: "prettier-plugin-packagejson@npm:2.4.3" + dependencies: + sort-package-json: "npm:2.4.1" + synckit: "npm:0.8.5" + peerDependencies: + prettier: ">= 1.16.0" + peerDependenciesMeta: + prettier: + optional: true + checksum: 10c0/81bc010242cc2b24d1d6978491f0bd980dde8548d2ab25341fa4702ef829c32e713534f3f5a46df90161a626ac4bf7ccfc025946871f08981cdbb4a8ed5e36a1 + languageName: node + linkType: hard + +"prettier-plugin-packagejson@npm:^2": + version: 2.5.19 + resolution: "prettier-plugin-packagejson@npm:2.5.19" + dependencies: + sort-package-json: "npm:3.4.0" + synckit: "npm:0.11.11" + peerDependencies: + prettier: ">= 1.16.0" + peerDependenciesMeta: + prettier: + optional: true + checksum: 10c0/288b8658fae8c620d3a7175f64026af8f450f053b8357b86b7d4c697287eae7e706c825d6819959bae1b539103a586f72d8b829574970392d0082f6ec5520d3f + languageName: node + linkType: hard + +"prettier@npm:^2": + version: 2.8.8 + resolution: "prettier@npm:2.8.8" + bin: + prettier: bin-prettier.js + checksum: 10c0/463ea8f9a0946cd5b828d8cf27bd8b567345cf02f56562d5ecde198b91f47a76b7ac9eae0facd247ace70e927143af6135e8cf411986b8cb8478784a4d6d724a + languageName: node + linkType: hard + +"pretty-error@npm:^4.0.0": + version: 4.0.0 + resolution: "pretty-error@npm:4.0.0" + dependencies: + lodash: "npm:^4.17.20" + renderkid: "npm:^3.0.0" + checksum: 10c0/dc292c087e2857b2e7592784ab31e37a40f3fa918caa11eba51f9fb2853e1d4d6e820b219917e35f5721d833cfd20fdf4f26ae931a90fd1ad0cae2125c345138 + languageName: node + linkType: hard + +"pretty-format@npm:^24": + version: 24.9.0 + resolution: "pretty-format@npm:24.9.0" + dependencies: + "@jest/types": "npm:^24.9.0" + ansi-regex: "npm:^4.0.0" + ansi-styles: "npm:^3.2.0" + react-is: "npm:^16.8.4" + checksum: 10c0/1e75c0ae55dab8953a5fe8025aab0a6d6090773561b672a7a00108f6cfb7dace198b27143392382dff913cb71f6fbc10ed23beaddf2117c380588a3b575825f0 + languageName: node + linkType: hard + +"proc-log@npm:^6.0.0": + version: 6.1.0 + resolution: "proc-log@npm:6.1.0" + checksum: 10c0/4f178d4062733ead9d71a9b1ab24ebcecdfe2250916a5b1555f04fe2eda972a0ec76fbaa8df1ad9c02707add6749219d118a4fc46dc56bdfe4dde4b47d80bb82 + languageName: node + linkType: hard + +"process-nextick-args@npm:~2.0.0": + version: 2.0.1 + resolution: "process-nextick-args@npm:2.0.1" + checksum: 10c0/bec089239487833d46b59d80327a1605e1c5287eaad770a291add7f45fda1bb5e28b38e0e061add0a1d0ee0984788ce74fa394d345eed1c420cacf392c554367 + languageName: node + linkType: hard + +"process-okam@npm:^0.11.10": + version: 0.11.10 + resolution: "process-okam@npm:0.11.10" + checksum: 10c0/0ecadafbfed96162f05978ad0f1267656b166072da8a856f7a27bba949d806258c9d3187b357fc49ac52700f961ad1a91c8a8777daf087d96f2dcb0e3e256e85 + languageName: node + linkType: hard + +"process-warning@npm:^1.0.0": + version: 1.0.0 + resolution: "process-warning@npm:1.0.0" + checksum: 10c0/43ec4229d64eb5c58340c8aacade49eb5f6fd513eae54140abf365929ca20987f0a35c5868125e2b583cad4de8cd257beb5667d9cc539d9190a7a4c3014adf22 + languageName: node + linkType: hard + +"process@npm:^0.11.10": + version: 0.11.10 + resolution: "process@npm:0.11.10" + checksum: 10c0/40c3ce4b7e6d4b8c3355479df77aeed46f81b279818ccdc500124e6a5ab882c0cc81ff7ea16384873a95a74c4570b01b120f287abbdd4c877931460eca6084b3 + languageName: node + linkType: hard + +"progress@npm:^2.0.3": + version: 2.0.3 + resolution: "progress@npm:2.0.3" + checksum: 10c0/1697e07cb1068055dbe9fe858d242368ff5d2073639e652b75a7eb1f2a1a8d4afd404d719de23c7b48481a6aa0040686310e2dac2f53d776daa2176d3f96369c + languageName: node + linkType: hard + +"promise-retry@npm:^2.0.1": + version: 2.0.1 + resolution: "promise-retry@npm:2.0.1" + dependencies: + err-code: "npm:^2.0.2" + retry: "npm:^0.12.0" + checksum: 10c0/9c7045a1a2928094b5b9b15336dcd2a7b1c052f674550df63cc3f36cd44028e5080448175b6f6ca32b642de81150f5e7b1a98b728f15cb069f2dd60ac2616b96 + languageName: node + linkType: hard + +"prop-types@npm:^15.0.0, prop-types@npm:^15.5.10, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1": + version: 15.8.1 + resolution: "prop-types@npm:15.8.1" + dependencies: + loose-envify: "npm:^1.4.0" + object-assign: "npm:^4.1.1" + react-is: "npm:^16.13.1" + checksum: 10c0/59ece7ca2fb9838031d73a48d4becb9a7cc1ed10e610517c7d8f19a1e02fa47f7c27d557d8a5702bec3cfeccddc853579832b43f449e54635803f277b1c78077 + languageName: node + linkType: hard + +"property-information@npm:^6.0.0": + version: 6.5.0 + resolution: "property-information@npm:6.5.0" + checksum: 10c0/981e0f9cc2e5acdb414a6fd48a99dd0fd3a4079e7a91ab41cf97a8534cf43e0e0bc1ffada6602a1b3d047a33db8b5fc2ef46d863507eda712d5ceedac443f0ef + languageName: node + linkType: hard + +"proxy-addr@npm:~2.0.7": + version: 2.0.7 + resolution: "proxy-addr@npm:2.0.7" + dependencies: + forwarded: "npm:0.2.0" + ipaddr.js: "npm:1.9.1" + checksum: 10c0/c3eed999781a35f7fd935f398b6d8920b6fb00bbc14287bc6de78128ccc1a02c89b95b56742bf7cf0362cc333c61d138532049c7dedc7a328ef13343eff81210 + languageName: node + linkType: hard + +"proxy-compare@npm:2.5.1": + version: 2.5.1 + resolution: "proxy-compare@npm:2.5.1" + checksum: 10c0/116fc69ae9a6bb3654e6907fb09b73e84aa47c89275ca52648fc1d2ac8b35dbf54daa8bab078d7a735337c928e87eb52059e705434adf14989bbe6c5dcdd08fa + languageName: node + linkType: hard + +"prr@npm:~1.0.1": + version: 1.0.1 + resolution: "prr@npm:1.0.1" + checksum: 10c0/5b9272c602e4f4472a215e58daff88f802923b84bc39c8860376bb1c0e42aaf18c25d69ad974bd06ec6db6f544b783edecd5502cd3d184748d99080d68e4be5f + languageName: node + linkType: hard + +"public-encrypt@npm:^4.0.3": + version: 4.0.3 + resolution: "public-encrypt@npm:4.0.3" + dependencies: + bn.js: "npm:^4.1.0" + browserify-rsa: "npm:^4.0.0" + create-hash: "npm:^1.1.0" + parse-asn1: "npm:^5.0.0" + randombytes: "npm:^2.0.1" + safe-buffer: "npm:^5.1.2" + checksum: 10c0/6c2cc19fbb554449e47f2175065d6b32f828f9b3badbee4c76585ac28ae8641aafb9bb107afc430c33c5edd6b05dbe318df4f7d6d7712b1093407b11c4280700 + languageName: node + linkType: hard + +"pump@npm:^3.0.0": + version: 3.0.3 + resolution: "pump@npm:3.0.3" + dependencies: + end-of-stream: "npm:^1.1.0" + once: "npm:^1.3.1" + checksum: 10c0/ada5cdf1d813065bbc99aa2c393b8f6beee73b5de2890a8754c9f488d7323ffd2ca5f5a0943b48934e3fcbd97637d0337369c3c631aeb9614915db629f1c75c9 + languageName: node + linkType: hard + +"punycode-okam@npm:^1.2.4": + version: 1.4.1 + resolution: "punycode-okam@npm:1.4.1" + checksum: 10c0/51eccb0a154afd3d6e982436908829a6ff4fb5da3a0d769cb17f5b37542536e374fe94e974a3fb7accc6f1a069d731c8a0e26aa4e3c74cc14f1f0172fca8dca1 + languageName: node + linkType: hard + +"punycode@npm:^1.2.4, punycode@npm:^1.4.1": + version: 1.4.1 + resolution: "punycode@npm:1.4.1" + checksum: 10c0/354b743320518aef36f77013be6e15da4db24c2b4f62c5f1eb0529a6ed02fbaf1cb52925785f6ab85a962f2b590d9cd5ad730b70da72b5f180e2556b8bd3ca08 + languageName: node + linkType: hard + +"punycode@npm:^2.1.0": + version: 2.3.1 + resolution: "punycode@npm:2.3.1" + checksum: 10c0/14f76a8206bc3464f794fb2e3d3cc665ae416c01893ad7a02b23766eb07159144ee612ad67af5e84fa4479ccfe67678c4feb126b0485651b302babf66f04f9e9 + languageName: node + linkType: hard + +"qiankun@npm:^2.10.1": + version: 2.10.16 + resolution: "qiankun@npm:2.10.16" + dependencies: + "@babel/runtime": "npm:^7.10.5" + import-html-entry: "npm:^1.15.1" + lodash: "npm:^4.17.11" + single-spa: "npm:^5.9.2" + checksum: 10c0/0662eb80365547fc9f861e942912bbf46760aad672ea1c6328c5d06ab4beb566a083fa087209ead67f8d327629781abc7e502d75e69bc3456680d40151b45d6c + languageName: node + linkType: hard + +"qified@npm:^0.9.0": + version: 0.9.1 + resolution: "qified@npm:0.9.1" + dependencies: + hookified: "npm:^2.1.1" + checksum: 10c0/815cfaefac67a702cd8aec9f7ac946ee7acf985ad13f518d0ec30f3ba5c7e74b72e014cf316b493baa54d81f4fadb2a2cf60a0a197326fd14fa68e6e7cf96832 + languageName: node + linkType: hard + +"qs@npm:6.13.0": + version: 6.13.0 + resolution: "qs@npm:6.13.0" + dependencies: + side-channel: "npm:^1.0.6" + checksum: 10c0/62372cdeec24dc83a9fb240b7533c0fdcf0c5f7e0b83343edd7310f0ab4c8205a5e7c56406531f2e47e1b4878a3821d652be4192c841de5b032ca83619d8f860 + languageName: node + linkType: hard + +"qs@npm:^6.11.0, qs@npm:^6.12.3, qs@npm:^6.9.1": + version: 6.14.0 + resolution: "qs@npm:6.14.0" + dependencies: + side-channel: "npm:^1.1.0" + checksum: 10c0/8ea5d91bf34f440598ee389d4a7d95820e3b837d3fd9f433871f7924801becaa0cd3b3b4628d49a7784d06a8aea9bc4554d2b6d8d584e2d221dc06238a42909c + languageName: node + linkType: hard + +"query-string@npm:^6.13.6": + version: 6.14.1 + resolution: "query-string@npm:6.14.1" + dependencies: + decode-uri-component: "npm:^0.2.0" + filter-obj: "npm:^1.1.0" + split-on-first: "npm:^1.0.0" + strict-uri-encode: "npm:^2.0.0" + checksum: 10c0/900e0fa788000e9dc5f929b6f4141742dcf281f02d3bab9714bc83bea65fab3de75169ea8d61f19cda996bc0dcec72e156efe3c5614c6bce65dcf234ac955b14 + languageName: node + linkType: hard + +"querystring-es3@npm:^0.2.0": + version: 0.2.1 + resolution: "querystring-es3@npm:0.2.1" + checksum: 10c0/476938c1adb45c141f024fccd2ffd919a3746e79ed444d00e670aad68532977b793889648980e7ca7ff5ffc7bfece623118d0fbadcaf217495eeb7059ae51580 + languageName: node + linkType: hard + +"queue-microtask@npm:^1.2.2": + version: 1.2.3 + resolution: "queue-microtask@npm:1.2.3" + checksum: 10c0/900a93d3cdae3acd7d16f642c29a642aea32c2026446151f0778c62ac089d4b8e6c986811076e1ae180a694cedf077d453a11b58ff0a865629a4f82ab558e102 + languageName: node + linkType: hard + +"quick-format-unescaped@npm:^4.0.3": + version: 4.0.4 + resolution: "quick-format-unescaped@npm:4.0.4" + checksum: 10c0/fe5acc6f775b172ca5b4373df26f7e4fd347975578199e7d74b2ae4077f0af05baa27d231de1e80e8f72d88275ccc6028568a7a8c9ee5e7368ace0e18eff93a4 + languageName: node + linkType: hard + +"quick-lru@npm:^5.1.1": + version: 5.1.1 + resolution: "quick-lru@npm:5.1.1" + checksum: 10c0/a24cba5da8cec30d70d2484be37622580f64765fb6390a928b17f60cd69e8dbd32a954b3ff9176fa1b86d86ff2ba05252fae55dc4d40d0291c60412b0ad096da + languageName: node + linkType: hard + +"railroad-diagrams@npm:^1.0.0": + version: 1.0.0 + resolution: "railroad-diagrams@npm:1.0.0" + checksum: 10c0/81bf8f86870a69fb9ed243102db9ad6416d09c4cb83964490d44717690e07dd982f671503236a1f8af28f4cb79d5d7a87613930f10ac08defa845ceb6764e364 + languageName: node + linkType: hard + +"randexp@npm:0.4.6": + version: 0.4.6 + resolution: "randexp@npm:0.4.6" + dependencies: + discontinuous-range: "npm:1.0.0" + ret: "npm:~0.1.10" + checksum: 10c0/14ee14b6d7f5ce69609b51cc914fb7a7c82ad337820a141c5f762c5ad1fe868f5191ea6e82359aee019b625ee1359486628fa833909d12c3b5dd9571908c3345 + languageName: node + linkType: hard + +"randombytes@npm:^2.0.0, randombytes@npm:^2.0.1, randombytes@npm:^2.0.5, randombytes@npm:^2.1.0": + version: 2.1.0 + resolution: "randombytes@npm:2.1.0" + dependencies: + safe-buffer: "npm:^5.1.0" + checksum: 10c0/50395efda7a8c94f5dffab564f9ff89736064d32addf0cc7e8bf5e4166f09f8ded7a0849ca6c2d2a59478f7d90f78f20d8048bca3cdf8be09d8e8a10790388f3 + languageName: node + linkType: hard + +"randomfill@npm:^1.0.4": + version: 1.0.4 + resolution: "randomfill@npm:1.0.4" + dependencies: + randombytes: "npm:^2.0.5" + safe-buffer: "npm:^5.1.0" + checksum: 10c0/11aeed35515872e8f8a2edec306734e6b74c39c46653607f03c68385ab8030e2adcc4215f76b5e4598e028c4750d820afd5c65202527d831d2a5f207fe2bc87c + languageName: node + linkType: hard + +"range-parser@npm:~1.2.1": + version: 1.2.1 + resolution: "range-parser@npm:1.2.1" + checksum: 10c0/96c032ac2475c8027b7a4e9fe22dc0dfe0f6d90b85e496e0f016fbdb99d6d066de0112e680805075bd989905e2123b3b3d002765149294dce0c1f7f01fcc2ea0 + languageName: node + linkType: hard + +"raw-body@npm:2.5.2": + version: 2.5.2 + resolution: "raw-body@npm:2.5.2" + dependencies: + bytes: "npm:3.1.2" + http-errors: "npm:2.0.0" + iconv-lite: "npm:0.4.24" + unpipe: "npm:1.0.0" + checksum: 10c0/b201c4b66049369a60e766318caff5cb3cc5a900efd89bdac431463822d976ad0670912c931fdbdcf5543207daf6f6833bca57aa116e1661d2ea91e12ca692c4 + languageName: node + linkType: hard + +"raw-body@npm:^2.3.0": + version: 2.5.3 + resolution: "raw-body@npm:2.5.3" + dependencies: + bytes: "npm:~3.1.2" + http-errors: "npm:~2.0.1" + iconv-lite: "npm:~0.4.24" + unpipe: "npm:~1.0.0" + checksum: 10c0/449844344fc90547fb994383a494b83300e4f22199f146a79f68d78a199a8f2a923ea9fd29c3be979bfd50291a3884733619ffc15ba02a32e703b612f8d3f74a + languageName: node + linkType: hard + +"rc-cascader@npm:~3.34.0": + version: 3.34.0 + resolution: "rc-cascader@npm:3.34.0" + dependencies: + "@babel/runtime": "npm:^7.25.7" + classnames: "npm:^2.3.1" + rc-select: "npm:~14.16.2" + rc-tree: "npm:~5.13.0" + rc-util: "npm:^5.43.0" + peerDependencies: + react: ">=16.9.0" + react-dom: ">=16.9.0" + checksum: 10c0/1fc8c55e0f78ff2be59e2bcd8faa53aafecebbb28f4bb9982ad39e8f9f9620e15d6119797c7890347e46b05c32b43177ece047e81ef04c22a9f041eb0dd53e0a + languageName: node + linkType: hard + +"rc-checkbox@npm:~3.5.0": + version: 3.5.0 + resolution: "rc-checkbox@npm:3.5.0" + dependencies: + "@babel/runtime": "npm:^7.10.1" + classnames: "npm:^2.3.2" + rc-util: "npm:^5.25.2" + peerDependencies: + react: ">=16.9.0" + react-dom: ">=16.9.0" + checksum: 10c0/53fd419030a8c9e3d08ebb7c51dee79be810ccd92ed177066c2afa8f61a8fe4417232bbc4741ecc0a627d0c4b939a5e945c6f0d6a941c748d65c2ddad71775e3 + languageName: node + linkType: hard + +"rc-collapse@npm:~3.9.0": + version: 3.9.0 + resolution: "rc-collapse@npm:3.9.0" + dependencies: + "@babel/runtime": "npm:^7.10.1" + classnames: "npm:2.x" + rc-motion: "npm:^2.3.4" + rc-util: "npm:^5.27.0" + peerDependencies: + react: ">=16.9.0" + react-dom: ">=16.9.0" + checksum: 10c0/68d2c7a6614fea2bf4a30a39e67d5b74b933fd25e31762cd810ff0f7bcf7e57676db6c3c1389461d5d18be4a68b9cfeda65321a8d1f5978ec2a5aa3d7b9010cc + languageName: node + linkType: hard + +"rc-dialog@npm:~9.6.0": + version: 9.6.0 + resolution: "rc-dialog@npm:9.6.0" + dependencies: + "@babel/runtime": "npm:^7.10.1" + "@rc-component/portal": "npm:^1.0.0-8" + classnames: "npm:^2.2.6" + rc-motion: "npm:^2.3.0" + rc-util: "npm:^5.21.0" + peerDependencies: + react: ">=16.9.0" + react-dom: ">=16.9.0" + checksum: 10c0/19e9acef746baa25c3167a961919123b0b457288188c18bc4d468ae31144bf750d6d6468dd3be43b376eba42ddda26fef1aac8ae9bd016f5d0428ffee0c615e7 + languageName: node + linkType: hard + +"rc-drawer@npm:~7.3.0": + version: 7.3.0 + resolution: "rc-drawer@npm:7.3.0" + dependencies: + "@babel/runtime": "npm:^7.23.9" + "@rc-component/portal": "npm:^1.1.1" + classnames: "npm:^2.2.6" + rc-motion: "npm:^2.6.1" + rc-util: "npm:^5.38.1" + peerDependencies: + react: ">=16.9.0" + react-dom: ">=16.9.0" + checksum: 10c0/e2c3211d6a3790813bf2c1626cebf3fdb3a4c48ab56bee2d208ba07dd0e5058154981563e89e02571d573dd56c2ddc65db33a0cf37c58820ecc4b08785e8d169 + languageName: node + linkType: hard + +"rc-dropdown@npm:~4.2.0, rc-dropdown@npm:~4.2.1": + version: 4.2.1 + resolution: "rc-dropdown@npm:4.2.1" + dependencies: + "@babel/runtime": "npm:^7.18.3" + "@rc-component/trigger": "npm:^2.0.0" + classnames: "npm:^2.2.6" + rc-util: "npm:^5.44.1" + peerDependencies: + react: ">=16.11.0" + react-dom: ">=16.11.0" + checksum: 10c0/ec980e6c9f8bbba53e895002a0c3a28f294ae07f3ebc6c9a9cb80c7e1bb74ba9f0e0c4b9c23f487fdf8c5a4531000e05b5b43744ef506f0fd869165486768817 + languageName: node + linkType: hard + +"rc-field-form@npm:~2.7.1": + version: 2.7.1 + resolution: "rc-field-form@npm:2.7.1" + dependencies: + "@babel/runtime": "npm:^7.18.0" + "@rc-component/async-validator": "npm:^5.0.3" + rc-util: "npm:^5.32.2" + peerDependencies: + react: ">=16.9.0" + react-dom: ">=16.9.0" + checksum: 10c0/2493cb9f26e69e17d55f32ad689da103a325e613d9222bfb332c2dcbdc96d44ce7dc4c8642a9b89a932ad2c2573508c997a4685e7fe6de2e951a027d2837403a + languageName: node + linkType: hard + +"rc-image@npm:~7.12.0": + version: 7.12.0 + resolution: "rc-image@npm:7.12.0" + dependencies: + "@babel/runtime": "npm:^7.11.2" + "@rc-component/portal": "npm:^1.0.2" + classnames: "npm:^2.2.6" + rc-dialog: "npm:~9.6.0" + rc-motion: "npm:^2.6.2" + rc-util: "npm:^5.34.1" + peerDependencies: + react: ">=16.9.0" + react-dom: ">=16.9.0" + checksum: 10c0/229f848725f8cff5b6015eb0468a24a3d92c2aead48dc98abe19e6ff15840defc9b42f1a126b7f8180f678b5380ff99528bb89e972298ad456773e4070f33934 + languageName: node + linkType: hard + +"rc-input-number@npm:~9.5.0": + version: 9.5.0 + resolution: "rc-input-number@npm:9.5.0" + dependencies: + "@babel/runtime": "npm:^7.10.1" + "@rc-component/mini-decimal": "npm:^1.0.1" + classnames: "npm:^2.2.5" + rc-input: "npm:~1.8.0" + rc-util: "npm:^5.40.1" + peerDependencies: + react: ">=16.9.0" + react-dom: ">=16.9.0" + checksum: 10c0/332aef42cd1f0e9eeee08c85db978c9615dfec5c8972e91c37a2ba4e06c3578d84dda05698e98893c6e62620d0d53aa910c0fbee2afac8f54c6f68759c296a58 + languageName: node + linkType: hard + +"rc-input@npm:~1.8.0": + version: 1.8.0 + resolution: "rc-input@npm:1.8.0" + dependencies: + "@babel/runtime": "npm:^7.11.1" + classnames: "npm:^2.2.1" + rc-util: "npm:^5.18.1" + peerDependencies: + react: ">=16.0.0" + react-dom: ">=16.0.0" + checksum: 10c0/fe4e67b6980b22f77d62dcd87177a2381976baeaff265a27c4adb63bab48735f7c89b271c541eb0aab8c9d58af66979f45e79b44b72342838ac038ee5db0ba73 + languageName: node + linkType: hard + +"rc-mentions@npm:~2.20.0": + version: 2.20.0 + resolution: "rc-mentions@npm:2.20.0" + dependencies: + "@babel/runtime": "npm:^7.22.5" + "@rc-component/trigger": "npm:^2.0.0" + classnames: "npm:^2.2.6" + rc-input: "npm:~1.8.0" + rc-menu: "npm:~9.16.0" + rc-textarea: "npm:~1.10.0" + rc-util: "npm:^5.34.1" + peerDependencies: + react: ">=16.9.0" + react-dom: ">=16.9.0" + checksum: 10c0/2b242221772bad982c47916328e0245365134ba48519d171a93a8d79ddbdfb20a98421d7962215867dc9097dd58c307ea9bb6c9590125c0484c01d0b78e207e0 + languageName: node + linkType: hard + +"rc-menu@npm:~9.16.0, rc-menu@npm:~9.16.1": + version: 9.16.1 + resolution: "rc-menu@npm:9.16.1" + dependencies: + "@babel/runtime": "npm:^7.10.1" + "@rc-component/trigger": "npm:^2.0.0" + classnames: "npm:2.x" + rc-motion: "npm:^2.4.3" + rc-overflow: "npm:^1.3.1" + rc-util: "npm:^5.27.0" + peerDependencies: + react: ">=16.9.0" + react-dom: ">=16.9.0" + checksum: 10c0/b61f21013cd679777b673d6e1a9f8429ffd9481a49665fc9a9c78198025d52aa76ae162186c46ca9f332117c8c7ff32f30d72435e1ae1b2a3daec0f86cb48810 + languageName: node + linkType: hard + +"rc-motion@npm:^2.0.0, rc-motion@npm:^2.0.1, rc-motion@npm:^2.3.0, rc-motion@npm:^2.3.4, rc-motion@npm:^2.4.3, rc-motion@npm:^2.4.4, rc-motion@npm:^2.6.1, rc-motion@npm:^2.6.2, rc-motion@npm:^2.9.0, rc-motion@npm:^2.9.5": + version: 2.9.5 + resolution: "rc-motion@npm:2.9.5" + dependencies: + "@babel/runtime": "npm:^7.11.1" + classnames: "npm:^2.2.1" + rc-util: "npm:^5.44.0" + peerDependencies: + react: ">=16.9.0" + react-dom: ">=16.9.0" + checksum: 10c0/84b12b2443dc1b929c8a688e8c9834a44cf88897402e9363fcea80b77f2803b2de83b24dac5873a8695304827e02fb3103c74349d0451ed247cb366553f9d88e + languageName: node + linkType: hard + +"rc-notification@npm:~5.6.4": + version: 5.6.4 + resolution: "rc-notification@npm:5.6.4" + dependencies: + "@babel/runtime": "npm:^7.10.1" + classnames: "npm:2.x" + rc-motion: "npm:^2.9.0" + rc-util: "npm:^5.20.1" + peerDependencies: + react: ">=16.9.0" + react-dom: ">=16.9.0" + checksum: 10c0/ea6a587b6a6057e8e6273d642cac5608b44948374ed636c9a83d104d21731c114b43036e33add05f755ceefb3f9258b881776672d5745c0e5d19f1d66449f37a + languageName: node + linkType: hard + +"rc-overflow@npm:^1.3.1, rc-overflow@npm:^1.3.2": + version: 1.5.0 + resolution: "rc-overflow@npm:1.5.0" + dependencies: + "@babel/runtime": "npm:^7.11.1" + classnames: "npm:^2.2.1" + rc-resize-observer: "npm:^1.0.0" + rc-util: "npm:^5.37.0" + peerDependencies: + react: ">=16.9.0" + react-dom: ">=16.9.0" + checksum: 10c0/7e9ce2fc3db7eb6e960b2848c0da9f67dcdc2a06eadd22baebe9933253424f6243c98fa7f093b765cb355d5fc3a880542748e199bee279dabc790fcf1bb2d34b + languageName: node + linkType: hard + +"rc-pagination@npm:~5.1.0": + version: 5.1.0 + resolution: "rc-pagination@npm:5.1.0" + dependencies: + "@babel/runtime": "npm:^7.10.1" + classnames: "npm:^2.3.2" + rc-util: "npm:^5.38.0" + peerDependencies: + react: ">=16.9.0" + react-dom: ">=16.9.0" + checksum: 10c0/6cc6f0fa591c3d9f1cd0abcc1f918ddf18d6b5c71fefb97a6c3888b8492505e8e8951903de2bae7c64c0947cf1d53bc70f52577a3f6b38bdb3e9140a7bb5a32e + languageName: node + linkType: hard + +"rc-picker@npm:~4.11.3": + version: 4.11.3 + resolution: "rc-picker@npm:4.11.3" + dependencies: + "@babel/runtime": "npm:^7.24.7" + "@rc-component/trigger": "npm:^2.0.0" + classnames: "npm:^2.2.1" + rc-overflow: "npm:^1.3.2" + rc-resize-observer: "npm:^1.4.0" + rc-util: "npm:^5.43.0" + 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 + checksum: 10c0/5136966ae7f9c0fa9acb9620f3d5e12341a4f2d1369d1c0e12697fbd519be47025a427acd63c8c39ec6b0a88acc5408b8318d197d8bf37f8d2b19d6726e6868d + languageName: node + linkType: hard + +"rc-progress@npm:~4.0.0": + version: 4.0.0 + resolution: "rc-progress@npm:4.0.0" + dependencies: + "@babel/runtime": "npm:^7.10.1" + classnames: "npm:^2.2.6" + rc-util: "npm:^5.16.1" + peerDependencies: + react: ">=16.9.0" + react-dom: ">=16.9.0" + checksum: 10c0/d3b47565470c5fec71a16f8d1939f1b1dd7d2dc9260893c6f70cafa84d9ee4231f3466be817db5fb9580932af46e34d52c74a710700ca9391b1901fa06c31f1e + languageName: node + linkType: hard + +"rc-rate@npm:~2.13.1": + version: 2.13.1 + resolution: "rc-rate@npm:2.13.1" + dependencies: + "@babel/runtime": "npm:^7.10.1" + classnames: "npm:^2.2.5" + rc-util: "npm:^5.0.1" + peerDependencies: + react: ">=16.9.0" + react-dom: ">=16.9.0" + checksum: 10c0/b26d4741fffb06e1beebe1aba135ba6ab4ee898faf1f876ce802ed5ddcdc8dabe7a4662be63e60226713fad9b3dd8d4034ed9b8b3e27ba5ef9673d7e8f47d497 + languageName: node + linkType: hard + +"rc-resize-observer@npm:^0.2.3": + version: 0.2.6 + resolution: "rc-resize-observer@npm:0.2.6" + dependencies: + "@babel/runtime": "npm:^7.10.1" + classnames: "npm:^2.2.1" + rc-util: "npm:^5.0.0" + resize-observer-polyfill: "npm:^1.5.1" + peerDependencies: + react: ">=16.9.0" + react-dom: ">=16.9.0" + checksum: 10c0/97fb2652a85ea54b6b33c31575827dcee092188b716d37f09afeeb2878286f9fb86b5b1a209d4eee33b64dc59a7ddbba4a0d8354f97d53cbdd396fc1de97cfa0 + languageName: node + linkType: hard + +"rc-resize-observer@npm:^1.0.0, rc-resize-observer@npm:^1.1.0, rc-resize-observer@npm:^1.3.1, rc-resize-observer@npm:^1.4.0, rc-resize-observer@npm:^1.4.3": + version: 1.4.3 + resolution: "rc-resize-observer@npm:1.4.3" + dependencies: + "@babel/runtime": "npm:^7.20.7" + classnames: "npm:^2.2.1" + rc-util: "npm:^5.44.1" + resize-observer-polyfill: "npm:^1.5.1" + peerDependencies: + react: ">=16.9.0" + react-dom: ">=16.9.0" + checksum: 10c0/93073c9ef5cc704f9d99307f58f8eeccabb953edf4e8a056b090104fc28ed19b77c2a32bd88ca2e0407fbedeb266d1985e655b35b8bc36b04d243e9d0471c911 + languageName: node + linkType: hard + +"rc-segmented@npm:~2.7.0": + version: 2.7.0 + resolution: "rc-segmented@npm:2.7.0" + dependencies: + "@babel/runtime": "npm:^7.11.1" + classnames: "npm:^2.2.1" + rc-motion: "npm:^2.4.4" + rc-util: "npm:^5.17.0" + peerDependencies: + react: ">=16.0.0" + react-dom: ">=16.0.0" + checksum: 10c0/294feac3a7f0f827419d14234d9ab5d39ef1e95acf582b68e3db63c7f9c670ffd1a08f3129f6326447f5c1218552cb738608f035da8199e6fd21ada1ceb3b4d1 + languageName: node + linkType: hard + +"rc-select@npm:~14.16.2, rc-select@npm:~14.16.8": + version: 14.16.8 + resolution: "rc-select@npm:14.16.8" + dependencies: + "@babel/runtime": "npm:^7.10.1" + "@rc-component/trigger": "npm:^2.1.1" + classnames: "npm:2.x" + rc-motion: "npm:^2.0.1" + rc-overflow: "npm:^1.3.1" + rc-util: "npm:^5.16.1" + rc-virtual-list: "npm:^3.5.2" + peerDependencies: + react: "*" + react-dom: "*" + checksum: 10c0/45f93e270c4b5e5ffc4b0ba0ce5e5ea72fff591a9a7a19b460b1ead0517d17327af9a4c32ce3c7f92b765724f4dabd1aa7146f5a06db73be91c884fe13c92774 + languageName: node + linkType: hard + +"rc-slider@npm:~11.1.9": + version: 11.1.9 + resolution: "rc-slider@npm:11.1.9" + dependencies: + "@babel/runtime": "npm:^7.10.1" + classnames: "npm:^2.2.5" + rc-util: "npm:^5.36.0" + peerDependencies: + react: ">=16.9.0" + react-dom: ">=16.9.0" + checksum: 10c0/193c432e2859ba42b2235cc1949de2d929ba70fa4aa5672eaa5da692797f3fe927f8c0b2a75cc45c6b9f666f204f4ed038ccf904273d6cbc39e112f4a00ddd4a + languageName: node + linkType: hard + +"rc-steps@npm:~6.0.1": + version: 6.0.1 + resolution: "rc-steps@npm:6.0.1" + dependencies: + "@babel/runtime": "npm:^7.16.7" + classnames: "npm:^2.2.3" + rc-util: "npm:^5.16.1" + peerDependencies: + react: ">=16.9.0" + react-dom: ">=16.9.0" + checksum: 10c0/0ba1051a469ae95916cafbb6d7fe76f94e6666181129d3012174d8cc25913c6afd691f551ee0dac48a4a86b59cb91699d6a44a1398dcafd661a8a519f70c95e1 + languageName: node + linkType: hard + +"rc-switch@npm:~4.1.0": + version: 4.1.0 + resolution: "rc-switch@npm:4.1.0" + dependencies: + "@babel/runtime": "npm:^7.21.0" + classnames: "npm:^2.2.1" + rc-util: "npm:^5.30.0" + peerDependencies: + react: ">=16.9.0" + react-dom: ">=16.9.0" + checksum: 10c0/5ce5b1cadea6e7cd38c0725456ea15c39748fefc39576f7c9288192e69b7f426c4ac7627e266369ece164f281ae08e14ab8f54d4d7858c8bd20707b296980743 + languageName: node + linkType: hard + +"rc-table@npm:~7.54.0": + version: 7.54.0 + resolution: "rc-table@npm:7.54.0" + dependencies: + "@babel/runtime": "npm:^7.10.1" + "@rc-component/context": "npm:^1.4.0" + classnames: "npm:^2.2.5" + rc-resize-observer: "npm:^1.1.0" + rc-util: "npm:^5.44.3" + rc-virtual-list: "npm:^3.14.2" + peerDependencies: + react: ">=16.9.0" + react-dom: ">=16.9.0" + checksum: 10c0/bf78af66c97aca8437bcf1f88df81b4171afbf647959c9b3600b246c15f9db8763fa50255749390d7f2c0ba0a6aea4f467f64aa4dd6265d56ebf35ffc0d6b94f + languageName: node + linkType: hard + +"rc-tabs@npm:~15.7.0": + version: 15.7.0 + resolution: "rc-tabs@npm:15.7.0" + dependencies: + "@babel/runtime": "npm:^7.11.2" + classnames: "npm:2.x" + rc-dropdown: "npm:~4.2.0" + rc-menu: "npm:~9.16.0" + rc-motion: "npm:^2.6.2" + rc-resize-observer: "npm:^1.0.0" + rc-util: "npm:^5.34.1" + peerDependencies: + react: ">=16.9.0" + react-dom: ">=16.9.0" + checksum: 10c0/472561f2ec611e9f2a396ba9fec6b83138621651aa2e9fb5a1c68b4da8cb6cab01c23ca71b3940876a595f30c2b8324b8899f93486862271be8eb16a95433764 + languageName: node + linkType: hard + +"rc-textarea@npm:~1.10.0, rc-textarea@npm:~1.10.2": + version: 1.10.2 + resolution: "rc-textarea@npm:1.10.2" + dependencies: + "@babel/runtime": "npm:^7.10.1" + classnames: "npm:^2.2.1" + rc-input: "npm:~1.8.0" + rc-resize-observer: "npm:^1.0.0" + rc-util: "npm:^5.27.0" + peerDependencies: + react: ">=16.9.0" + react-dom: ">=16.9.0" + checksum: 10c0/ccfe7bae33187c382e12bc14e9f2617fe183a4d4e8c0d3b9f71455728172f31a6140d0855ff557b6c658daf31c7ff935a1a347a336f8106ddda84e042ab23448 + languageName: node + linkType: hard + +"rc-tooltip@npm:~6.4.0": + version: 6.4.0 + resolution: "rc-tooltip@npm:6.4.0" + dependencies: + "@babel/runtime": "npm:^7.11.2" + "@rc-component/trigger": "npm:^2.0.0" + classnames: "npm:^2.3.1" + rc-util: "npm:^5.44.3" + peerDependencies: + react: ">=16.9.0" + react-dom: ">=16.9.0" + checksum: 10c0/49b9c56fc877b38084b4076edb1b61f0272bdd290c6ef161a0e1cf6426488e948c20439cf4ae31e076f3957b894feb326e4a1d7880400de2c29b1d54f736a342 + languageName: node + linkType: hard + +"rc-tree-select@npm:~5.27.0": + version: 5.27.0 + resolution: "rc-tree-select@npm:5.27.0" + dependencies: + "@babel/runtime": "npm:^7.25.7" + classnames: "npm:2.x" + rc-select: "npm:~14.16.2" + rc-tree: "npm:~5.13.0" + rc-util: "npm:^5.43.0" + peerDependencies: + react: "*" + react-dom: "*" + checksum: 10c0/26aad0e13e5f9fe501574ba50826edda9b67a5bf22adbe1dc8e3a793fb784318b235165d6054a047b5934cdfbbd88ea1a524726edbad9107cd0f1d28782f9cc5 + languageName: node + linkType: hard + +"rc-tree@npm:~5.13.0, rc-tree@npm:~5.13.1": + version: 5.13.1 + resolution: "rc-tree@npm:5.13.1" + dependencies: + "@babel/runtime": "npm:^7.10.1" + classnames: "npm:2.x" + rc-motion: "npm:^2.0.1" + rc-util: "npm:^5.16.1" + rc-virtual-list: "npm:^3.5.1" + peerDependencies: + react: "*" + react-dom: "*" + checksum: 10c0/4a27783d319f9e5367e9d123a2f9a6daa0383e705e055abb47f3ff7fa93249c5c26bbb27b7c6602163faefbfe0f3e923eb3a55d1e1f1d09d04b7bdf37942c2d4 + languageName: node + linkType: hard + +"rc-upload@npm:~4.11.0": + version: 4.11.0 + resolution: "rc-upload@npm:4.11.0" + dependencies: + "@babel/runtime": "npm:^7.18.3" + classnames: "npm:^2.2.5" + rc-util: "npm:^5.2.0" + peerDependencies: + react: ">=16.9.0" + react-dom: ">=16.9.0" + checksum: 10c0/9ce59e22e0f9839e482fd37e63d8489e1a9d418113bfe39cd4d2b1e88f59236d4f7c60a3bb5928d2d85781d6f4cf8c30b6085863c802de605ef339fa415903d3 + languageName: node + linkType: hard + +"rc-util@npm:^4.19.0": + version: 4.21.1 + resolution: "rc-util@npm:4.21.1" + dependencies: + add-dom-event-listener: "npm:^1.1.0" + prop-types: "npm:^15.5.10" + react-is: "npm:^16.12.0" + react-lifecycles-compat: "npm:^3.0.4" + shallowequal: "npm:^1.1.0" + checksum: 10c0/f91fe2ba98658c1bd67d8d3edd5ed5a2425ff44d3cd30f96b71b6058bd6c852bbf82e00716e219c10f6fac20e9b9cbb447e39cd69e12cdcfeda6dcd824adc790 + languageName: node + linkType: hard + +"rc-util@npm:^5.0.0, rc-util@npm:^5.0.1, rc-util@npm:^5.0.6, rc-util@npm:^5.16.1, rc-util@npm:^5.17.0, rc-util@npm:^5.18.1, rc-util@npm:^5.2.0, rc-util@npm:^5.20.1, rc-util@npm:^5.21.0, rc-util@npm:^5.24.4, rc-util@npm:^5.25.2, rc-util@npm:^5.27.0, rc-util@npm:^5.30.0, rc-util@npm:^5.31.1, rc-util@npm:^5.32.2, rc-util@npm:^5.34.1, rc-util@npm:^5.35.0, rc-util@npm:^5.36.0, rc-util@npm:^5.37.0, rc-util@npm:^5.38.0, rc-util@npm:^5.38.1, rc-util@npm:^5.4.0, rc-util@npm:^5.40.1, rc-util@npm:^5.43.0, rc-util@npm:^5.44.0, rc-util@npm:^5.44.1, rc-util@npm:^5.44.3, rc-util@npm:^5.44.4, rc-util@npm:^5.9.4": + version: 5.44.4 + resolution: "rc-util@npm:5.44.4" + dependencies: + "@babel/runtime": "npm:^7.18.3" + react-is: "npm:^18.2.0" + peerDependencies: + react: ">=16.9.0" + react-dom: ">=16.9.0" + checksum: 10c0/748b71a6280ddaaac93d1fb2c92f03818775468e7ccb6c221484687cc0b7e879d083e98e338f75ac0fe2e942dbb9c2405bd32d25e5a804bf1fb7a11f3f897127 + languageName: node + linkType: hard + +"rc-virtual-list@npm:^3.14.2, rc-virtual-list@npm:^3.5.1, rc-virtual-list@npm:^3.5.2": + version: 3.19.2 + resolution: "rc-virtual-list@npm:3.19.2" + dependencies: + "@babel/runtime": "npm:^7.20.0" + classnames: "npm:^2.2.6" + rc-resize-observer: "npm:^1.0.0" + rc-util: "npm:^5.36.0" + peerDependencies: + react: ">=16.9.0" + react-dom: ">=16.9.0" + checksum: 10c0/3778ade183a33d113555fb99465c2f59391c9b5178629cb7bb2947d5ee71a1b166bf9468b063394f63384965166ef368acf78cb5f4b3a23e9393af04543b6626 + languageName: node + linkType: hard + +"react-dom@npm:18.3.1": + version: 18.3.1 + resolution: "react-dom@npm:18.3.1" + dependencies: + loose-envify: "npm:^1.1.0" + scheduler: "npm:^0.23.2" + peerDependencies: + react: ^18.3.1 + checksum: 10c0/a752496c1941f958f2e8ac56239172296fcddce1365ce45222d04a1947e0cc5547df3e8447f855a81d6d39f008d7c32eab43db3712077f09e3f67c4874973e85 + languageName: node + linkType: hard + +"react-error-overlay@npm:6.0.9": + version: 6.0.9 + resolution: "react-error-overlay@npm:6.0.9" + checksum: 10c0/02f51337f34589305f827249acb597446489794cc5b5e721a6260111325b56942a7471b76967cba304e797d7e4ef16dd0bd989c112dd0bb9586270df0d75a4a9 + languageName: node + linkType: hard + +"react-fast-compare@npm:^3.2.0, react-fast-compare@npm:^3.2.2": + version: 3.2.2 + resolution: "react-fast-compare@npm:3.2.2" + checksum: 10c0/0bbd2f3eb41ab2ff7380daaa55105db698d965c396df73e6874831dbafec8c4b5b08ba36ff09df01526caa3c61595247e3269558c284e37646241cba2b90a367 + languageName: node + linkType: hard + +"react-helmet-async@npm:1.3.0": + version: 1.3.0 + resolution: "react-helmet-async@npm:1.3.0" + dependencies: + "@babel/runtime": "npm:^7.12.5" + invariant: "npm:^2.2.4" + prop-types: "npm:^15.7.2" + react-fast-compare: "npm:^3.2.0" + shallowequal: "npm:^1.1.0" + peerDependencies: + react: ^16.6.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.6.0 || ^17.0.0 || ^18.0.0 + checksum: 10c0/8f3e6d26beff61d2ed18f7b41561df3e4d83a7582914c7196aa65158c7f3cce939276547d7a0b8987952d9d44131406df74efba02d1f8fa8a3940b49e6ced70b + languageName: node + linkType: hard + +"react-intl@npm:3.12.1": + version: 3.12.1 + resolution: "react-intl@npm:3.12.1" + dependencies: + "@formatjs/intl-displaynames": "npm:^1.2.0" + "@formatjs/intl-listformat": "npm:^1.4.1" + "@formatjs/intl-relativetimeformat": "npm:^4.5.9" + "@formatjs/intl-unified-numberformat": "npm:^3.2.0" + "@formatjs/intl-utils": "npm:^2.2.0" + "@types/hoist-non-react-statics": "npm:^3.3.1" + "@types/invariant": "npm:^2.2.31" + hoist-non-react-statics: "npm:^3.3.2" + intl-format-cache: "npm:^4.2.21" + intl-messageformat: "npm:^7.8.4" + intl-messageformat-parser: "npm:^3.6.4" + shallow-equal: "npm:^1.2.1" + peerDependencies: + react: ^16.3.0 + checksum: 10c0/d0e173c6e64e1befe5de49760a1cc293d35764f0a1b51902e9c77b5a8abc2f997db293ea51216ac5087f3144390ae4b0afa9f929576ca6a438da4d74b4b19347 + languageName: node + linkType: hard + +"react-is@npm:^16.12.0, react-is@npm:^16.13.1, react-is@npm:^16.7.0, react-is@npm:^16.8.4": + version: 16.13.1 + resolution: "react-is@npm:16.13.1" + checksum: 10c0/33977da7a5f1a287936a0c85639fec6ca74f4f15ef1e59a6bc20338fc73dc69555381e211f7a3529b8150a1f71e4225525b41b60b52965bda53ce7d47377ada1 + languageName: node + linkType: hard + +"react-is@npm:^18.0.0, react-is@npm:^18.2.0": + version: 18.3.1 + resolution: "react-is@npm:18.3.1" + checksum: 10c0/f2f1e60010c683479e74c63f96b09fb41603527cd131a9959e2aee1e5a8b0caf270b365e5ca77d4a6b18aae659b60a86150bb3979073528877029b35aecd2072 + languageName: node + linkType: hard + +"react-lifecycles-compat@npm:^3.0.4": + version: 3.0.4 + resolution: "react-lifecycles-compat@npm:3.0.4" + checksum: 10c0/1d0df3c85af79df720524780f00c064d53a9dd1899d785eddb7264b378026979acbddb58a4b7e06e7d0d12aa1494fd5754562ee55d32907b15601068dae82c27 + languageName: node + linkType: hard + +"react-markdown@npm:^8.0.7": + version: 8.0.7 + resolution: "react-markdown@npm:8.0.7" + dependencies: + "@types/hast": "npm:^2.0.0" + "@types/prop-types": "npm:^15.0.0" + "@types/unist": "npm:^2.0.0" + comma-separated-tokens: "npm:^2.0.0" + hast-util-whitespace: "npm:^2.0.0" + prop-types: "npm:^15.0.0" + property-information: "npm:^6.0.0" + react-is: "npm:^18.0.0" + remark-parse: "npm:^10.0.0" + remark-rehype: "npm:^10.0.0" + space-separated-tokens: "npm:^2.0.0" + style-to-object: "npm:^0.4.0" + unified: "npm:^10.0.0" + unist-util-visit: "npm:^4.0.0" + vfile: "npm:^5.0.0" + peerDependencies: + "@types/react": ">=16" + react: ">=16" + checksum: 10c0/016617fbd2f4c03c5ae017fe39e89202f2ff536b4921dc1a5f7283d4b9d5157f20797adda75a8c59a06787ad0bc8841e2e437915aec645ce528e0a04a6d450ac + languageName: node + linkType: hard + +"react-merge-refs@npm:^1.1.0": + version: 1.1.0 + resolution: "react-merge-refs@npm:1.1.0" + checksum: 10c0/afb937156d834a058e5021535a63fd8a35984365c38b613478c31186da920b3b469e38b0b161a2075fbf5db7118fb8e96bb8a52a563f271d8f8f166fe8ffe2a4 + languageName: node + linkType: hard + +"react-monaco-editor@npm:^0.54.0": + version: 0.54.0 + resolution: "react-monaco-editor@npm:0.54.0" + dependencies: + prop-types: "npm:^15.8.1" + peerDependencies: + "@types/react": ">=16 <= 18" + monaco-editor: ^0.39.0 + react: ">=16 <= 18" + checksum: 10c0/cd08ee09507597fa983a2968786f1d3276f340d4fe941cfdb5fd9870188701076908f811faaf751cd307195009805050121473c36f21d091f224ba1e1e3133a4 + languageName: node + linkType: hard + +"react-redux@npm:^8.0.5": + version: 8.1.3 + resolution: "react-redux@npm:8.1.3" + dependencies: + "@babel/runtime": "npm:^7.12.1" + "@types/hoist-non-react-statics": "npm:^3.3.1" + "@types/use-sync-external-store": "npm:^0.0.3" + hoist-non-react-statics: "npm:^3.3.2" + react-is: "npm:^18.0.0" + use-sync-external-store: "npm:^1.0.0" + peerDependencies: + "@types/react": ^16.8 || ^17.0 || ^18.0 + "@types/react-dom": ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + react-native: ">=0.59" + redux: ^4 || ^5.0.0-beta.0 + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + react-dom: + optional: true + react-native: + optional: true + redux: + optional: true + checksum: 10c0/64c8be2765568dc66a3c442a41dd0ed74fe048d5ceb7a4fe72e5bac3d3687996a7115f57b5156af7406521087065a0e60f9194318c8ca99c55e9ce48558980ce + languageName: node + linkType: hard + +"react-refresh@npm:0.14.0": + version: 0.14.0 + resolution: "react-refresh@npm:0.14.0" + checksum: 10c0/b8ae07ad153357d77830928a7f1fc2df837aabefee907fa273ba04c7643f3b860e986f1d4b7ada9b721c8d79b8c24b5b911a314a1a2398b105f1b13d19ea2b8d + languageName: node + linkType: hard + +"react-refresh@npm:^0.14.0": + version: 0.14.2 + resolution: "react-refresh@npm:0.14.2" + checksum: 10c0/875b72ef56b147a131e33f2abd6ec059d1989854b3ff438898e4f9310bfcc73acff709445b7ba843318a953cb9424bcc2c05af2b3d80011cee28f25aef3e2ebb + languageName: node + linkType: hard + +"react-router-dom@npm:6.3.0": + version: 6.3.0 + resolution: "react-router-dom@npm:6.3.0" + dependencies: + history: "npm:^5.2.0" + react-router: "npm:6.3.0" + peerDependencies: + react: ">=16.8" + react-dom: ">=16.8" + checksum: 10c0/490b0c50d46c32ad1a3264f8bcaf6b423bef86dc3b62e9070d5e81d90ce086a73af55834133920fc4125e7c8661ede901a550d73429c969b303d4dd9ce7bbaf2 + languageName: node + linkType: hard + +"react-router@npm:6.3.0": + version: 6.3.0 + resolution: "react-router@npm:6.3.0" + dependencies: + history: "npm:^5.2.0" + peerDependencies: + react: ">=16.8" + checksum: 10c0/ac8785a0b28d363940763e49119e5160331099d4f0196235b143ba9cdc984048ca44a77497f393b12165c99baf8ae6c11386f1f6f20ef52d99c2e07b31920862 + languageName: node + linkType: hard + +"react-sortablejs@npm:^6.1.4": + version: 6.1.4 + resolution: "react-sortablejs@npm:6.1.4" + dependencies: + classnames: "npm:2.3.1" + tiny-invariant: "npm:1.2.0" + peerDependencies: + "@types/sortablejs": 1 + react: ">=16.9.0" + react-dom: ">=16.9.0" + sortablejs: 1 + checksum: 10c0/72a3e5676d40c4c1d9912d624051caedc95c0d4f0aeb0682d5b803fb0303cecb3fe6ac1faf3e8658b0723bab7934f59196b9722fe2a2ad72454f7fcb947937bd + languageName: node + linkType: hard + +"react@npm:18.3.1": + version: 18.3.1 + resolution: "react@npm:18.3.1" + dependencies: + loose-envify: "npm:^1.1.0" + checksum: 10c0/283e8c5efcf37802c9d1ce767f302dd569dd97a70d9bb8c7be79a789b9902451e0d16334b05d73299b20f048cbc3c7d288bbbde10b701fa194e2089c237dbea3 + languageName: node + linkType: hard + +"reactcss@npm:^1.2.3": + version: 1.2.3 + resolution: "reactcss@npm:1.2.3" + dependencies: + lodash: "npm:^4.0.1" + checksum: 10c0/a3aceb0fbfd58312f0c7fadbe92920e6536ec24d17ebee44fd4a14dd831d413fff5c2df0e85579b440667935e57a06876325cbd1368d3131824a8c2ec43b7978 + languageName: node + linkType: hard + +"read-cache@npm:^1.0.0": + version: 1.0.0 + resolution: "read-cache@npm:1.0.0" + dependencies: + pify: "npm:^2.3.0" + checksum: 10c0/90cb2750213c7dd7c80cb420654344a311fdec12944e81eb912cd82f1bc92aea21885fa6ce442e3336d9fccd663b8a7a19c46d9698e6ca55620848ab932da814 + languageName: node + linkType: hard + +"read-config-file@npm:6.2.0": + version: 6.2.0 + resolution: "read-config-file@npm:6.2.0" + dependencies: + dotenv: "npm:^9.0.2" + dotenv-expand: "npm:^5.1.0" + js-yaml: "npm:^4.1.0" + json5: "npm:^2.2.0" + lazy-val: "npm:^1.0.4" + checksum: 10c0/ea1ffc9dcbd44fcbcfa972f3deb9625725c5eb112ecc1da2fb05e8fc89ef8d21fae10539f4c312b9ed599d1d20ec3a85c66b71701fbd1a126562d8249ecd8f3a + languageName: node + linkType: hard + +"readable-stream@npm:^2.0.1, readable-stream@npm:^2.0.2, readable-stream@npm:^2.3.3, readable-stream@npm:^2.3.6, readable-stream@npm:^2.3.8": + version: 2.3.8 + resolution: "readable-stream@npm:2.3.8" + dependencies: + core-util-is: "npm:~1.0.0" + inherits: "npm:~2.0.3" + isarray: "npm:~1.0.0" + process-nextick-args: "npm:~2.0.0" + safe-buffer: "npm:~5.1.1" + string_decoder: "npm:~1.1.1" + util-deprecate: "npm:~1.0.1" + checksum: 10c0/7efdb01f3853bc35ac62ea25493567bf588773213f5f4a79f9c365e1ad13bab845ac0dae7bc946270dc40c3929483228415e92a3fc600cc7e4548992f41ee3fa + languageName: node + linkType: hard + +"readable-stream@npm:^3.0.6, readable-stream@npm:^3.1.1": + version: 3.6.2 + resolution: "readable-stream@npm:3.6.2" + dependencies: + inherits: "npm:^2.0.3" + string_decoder: "npm:^1.1.1" + util-deprecate: "npm:^1.0.1" + checksum: 10c0/e37be5c79c376fdd088a45fa31ea2e423e5d48854be7a22a58869b4e84d25047b193f6acb54f1012331e1bcd667ffb569c01b99d36b0bd59658fb33f513511b7 + languageName: node + linkType: hard + +"readdirp@npm:~3.6.0": + version: 3.6.0 + resolution: "readdirp@npm:3.6.0" + dependencies: + picomatch: "npm:^2.2.1" + checksum: 10c0/6fa848cf63d1b82ab4e985f4cf72bd55b7dcfd8e0a376905804e48c3634b7e749170940ba77b32804d5fe93b3cc521aa95a8d7e7d725f830da6d93f3669ce66b + languageName: node + linkType: hard + +"real-require@npm:^0.1.0": + version: 0.1.0 + resolution: "real-require@npm:0.1.0" + checksum: 10c0/c0f8ae531d1f51fe6343d47a2a1e5756e19b65a81b4a9642b9ebb4874e0d8b5f3799bc600bf4592838242477edc6f57778593f21b71d90f8ad0d8a317bbfae1c + languageName: node + linkType: hard + +"redux-saga@npm:^0.16.0": + version: 0.16.2 + resolution: "redux-saga@npm:0.16.2" + checksum: 10c0/d1a68969f8a4a86526d9fd5456d00382035e749487d7ed29a8b93179f1d3e665a2ae7eb1c4cfb8bf49c1056931e5fb4c2c325734c89c304a5348a35bae4917cc + languageName: node + linkType: hard + +"redux@npm:^4.2.1": + version: 4.2.1 + resolution: "redux@npm:4.2.1" + dependencies: + "@babel/runtime": "npm:^7.9.2" + checksum: 10c0/136d98b3d5dbed1cd6279c8c18a6a74c416db98b8a432a46836bdd668475de6279a2d4fd9d1363f63904e00f0678a8a3e7fa532c897163340baf1e71bb42c742 + languageName: node + linkType: hard + +"reflect.getprototypeof@npm:^1.0.10, reflect.getprototypeof@npm:^1.0.6, reflect.getprototypeof@npm:^1.0.9": + version: 1.0.10 + resolution: "reflect.getprototypeof@npm:1.0.10" + dependencies: + call-bind: "npm:^1.0.8" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.9" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.0.0" + get-intrinsic: "npm:^1.2.7" + get-proto: "npm:^1.0.1" + which-builtin-type: "npm:^1.2.1" + checksum: 10c0/7facec28c8008876f8ab98e80b7b9cb4b1e9224353fd4756dda5f2a4ab0d30fa0a5074777c6df24e1e0af463a2697513b0a11e548d99cf52f21f7bc6ba48d3ac + languageName: node + linkType: hard + +"regenerate-unicode-properties@npm:10.1.1": + version: 10.1.1 + resolution: "regenerate-unicode-properties@npm:10.1.1" + dependencies: + regenerate: "npm:^1.4.2" + checksum: 10c0/89adb5ee5ba081380c78f9057c02e156a8181969f6fcca72451efc45612e0c3df767b4333f8d8479c274d9c6fe52ec4854f0d8a22ef95dccbe87da8e5f2ac77d + languageName: node + linkType: hard + +"regenerate@npm:^1.4.2": + version: 1.4.2 + resolution: "regenerate@npm:1.4.2" + checksum: 10c0/f73c9eba5d398c818edc71d1c6979eaa05af7a808682749dd079f8df2a6d91a9b913db216c2c9b03e0a8ba2bba8701244a93f45211afbff691c32c7b275db1b8 + languageName: node + linkType: hard + +"regenerator-runtime@npm:0.13.11": + version: 0.13.11 + resolution: "regenerator-runtime@npm:0.13.11" + checksum: 10c0/12b069dc774001fbb0014f6a28f11c09ebfe3c0d984d88c9bced77fdb6fedbacbca434d24da9ae9371bfbf23f754869307fb51a4c98a8b8b18e5ef748677ca24 + languageName: node + linkType: hard + +"regenerator-runtime@npm:^0.14.0": + version: 0.14.1 + resolution: "regenerator-runtime@npm:0.14.1" + checksum: 10c0/1b16eb2c4bceb1665c89de70dcb64126a22bc8eb958feef3cd68fe11ac6d2a4899b5cd1b80b0774c7c03591dc57d16631a7f69d2daa2ec98100e2f29f7ec4cc4 + languageName: node + linkType: hard + +"regex-parser@npm:^2.2.11": + version: 2.3.1 + resolution: "regex-parser@npm:2.3.1" + checksum: 10c0/a256f79c8b465e6765eb65799417200f8ee81f68cc202cc5563a02713e61ad51f6280672f8edee072ef37c5301a90f8d1a71cefb6ec3ed2ca0d1d88587286219 + languageName: node + linkType: hard + +"regexp.prototype.flags@npm:^1.5.3, regexp.prototype.flags@npm:^1.5.4": + version: 1.5.4 + resolution: "regexp.prototype.flags@npm:1.5.4" + dependencies: + call-bind: "npm:^1.0.8" + define-properties: "npm:^1.2.1" + es-errors: "npm:^1.3.0" + get-proto: "npm:^1.0.1" + gopd: "npm:^1.2.0" + set-function-name: "npm:^2.0.2" + checksum: 10c0/83b88e6115b4af1c537f8dabf5c3744032cb875d63bc05c288b1b8c0ef37cbe55353f95d8ca817e8843806e3e150b118bc624e4279b24b4776b4198232735a77 + languageName: node + linkType: hard + +"relateurl@npm:^0.2.7": + version: 0.2.7 + resolution: "relateurl@npm:0.2.7" + checksum: 10c0/c248b4e3b32474f116a804b537fa6343d731b80056fb506dffd91e737eef4cac6be47a65aae39b522b0db9d0b1011d1a12e288d82a109ecd94a5299d82f6573a + languageName: node + linkType: hard + +"remark-gfm@npm:3": + version: 3.0.1 + resolution: "remark-gfm@npm:3.0.1" + dependencies: + "@types/mdast": "npm:^3.0.0" + mdast-util-gfm: "npm:^2.0.0" + micromark-extension-gfm: "npm:^2.0.0" + unified: "npm:^10.0.0" + checksum: 10c0/53c4e82204f82f81949a170efdeb49d3c45137b7bca06a7ff857a483aac1a44b55ef0de8fb1bbe4f1292f2a378058e2e42e644f2c61f3e0cdc3e56afa4ec2a2c + languageName: node + linkType: hard + +"remark-parse@npm:^10.0.0": + version: 10.0.2 + resolution: "remark-parse@npm:10.0.2" + dependencies: + "@types/mdast": "npm:^3.0.0" + mdast-util-from-markdown: "npm:^1.0.0" + unified: "npm:^10.0.0" + checksum: 10c0/30cb8f2790380b1c7370a1c66cda41f33a7dc196b9e440a00e2675037bca55aea868165a8204e0cdbacc27ef4a3bdb7d45879826bd6efa07d9fdf328cb67a332 + languageName: node + linkType: hard + +"remark-rehype@npm:^10.0.0": + version: 10.1.0 + resolution: "remark-rehype@npm:10.1.0" + dependencies: + "@types/hast": "npm:^2.0.0" + "@types/mdast": "npm:^3.0.0" + mdast-util-to-hast: "npm:^12.1.0" + unified: "npm:^10.0.0" + checksum: 10c0/803e658c9b51a9b53ee2ada42ff82e8e570444bb97c873e0d602c2d8dcb69a774fd22bd6f26643dfd5ab4c181059ea6c9fb9a99a2d7f9665f3f11bef1a1489bd + languageName: node + linkType: hard + +"remove-accents@npm:0.5.0": + version: 0.5.0 + resolution: "remove-accents@npm:0.5.0" + checksum: 10c0/a75321aa1b53d9abe82637115a492770bfe42bb38ed258be748bf6795871202bc8b4badff22013494a7029f5a241057ad8d3f72adf67884dbe15a9e37e87adc4 + languageName: node + linkType: hard + +"renderkid@npm:^3.0.0": + version: 3.0.0 + resolution: "renderkid@npm:3.0.0" + dependencies: + css-select: "npm:^4.1.3" + dom-converter: "npm:^0.2.0" + htmlparser2: "npm:^6.1.0" + lodash: "npm:^4.17.21" + strip-ansi: "npm:^6.0.1" + checksum: 10c0/24a9fae4cc50e731d059742d1b3eec163dc9e3872b12010d120c3fcbd622765d9cda41f79a1bbb4bf63c1d3442f18a08f6e1642cb5d7ebf092a0ce3f7a3bd143 + languageName: node + linkType: hard + +"require-directory@npm:^2.1.1": + version: 2.1.1 + resolution: "require-directory@npm:2.1.1" + checksum: 10c0/83aa76a7bc1531f68d92c75a2ca2f54f1b01463cb566cf3fbc787d0de8be30c9dbc211d1d46be3497dac5785fe296f2dd11d531945ac29730643357978966e99 + languageName: node + linkType: hard + +"require-from-string@npm:^2.0.2": + version: 2.0.2 + resolution: "require-from-string@npm:2.0.2" + checksum: 10c0/aaa267e0c5b022fc5fd4eef49d8285086b15f2a1c54b28240fdf03599cbd9c26049fee3eab894f2e1f6ca65e513b030a7c264201e3f005601e80c49fb2937ce2 + languageName: node + linkType: hard + +"resize-observer-polyfill@npm:^1.5.1": + version: 1.5.1 + resolution: "resize-observer-polyfill@npm:1.5.1" + checksum: 10c0/5e882475067f0b97dc07e0f37c3e335ac5bc3520d463f777cec7e894bb273eddbfecb857ae668e6fb6881fd6f6bb7148246967172139302da50fa12ea3a15d95 + languageName: node + linkType: hard + +"resolve-alpn@npm:^1.0.0": + version: 1.2.1 + resolution: "resolve-alpn@npm:1.2.1" + checksum: 10c0/b70b29c1843bc39781ef946c8cd4482e6d425976599c0f9c138cec8209e4e0736161bf39319b01676a847000085dfdaf63583c6fb4427bf751a10635bd2aa0c4 + languageName: node + linkType: hard + +"resolve-from@npm:^4.0.0": + version: 4.0.0 + resolution: "resolve-from@npm:4.0.0" + checksum: 10c0/8408eec31a3112ef96e3746c37be7d64020cda07c03a920f5024e77290a218ea758b26ca9529fd7b1ad283947f34b2291c1c0f6aa0ed34acfdda9c6014c8d190 + languageName: node + linkType: hard + +"resolve-from@npm:^5.0.0": + version: 5.0.0 + resolution: "resolve-from@npm:5.0.0" + checksum: 10c0/b21cb7f1fb746de8107b9febab60095187781137fd803e6a59a76d421444b1531b641bba5857f5dc011974d8a5c635d61cec49e6bd3b7fc20e01f0fafc4efbf2 + languageName: node + linkType: hard + +"resolve-pkg-maps@npm:^1.0.0": + version: 1.0.0 + resolution: "resolve-pkg-maps@npm:1.0.0" + checksum: 10c0/fb8f7bbe2ca281a73b7ef423a1cbc786fb244bd7a95cbe5c3fba25b27d327150beca8ba02f622baea65919a57e061eb5005204daa5f93ed590d9b77463a567ab + languageName: node + linkType: hard + +"resolve-url-loader@npm:^5.0.0": + version: 5.0.0 + resolution: "resolve-url-loader@npm:5.0.0" + dependencies: + adjust-sourcemap-loader: "npm:^4.0.0" + convert-source-map: "npm:^1.7.0" + loader-utils: "npm:^2.0.0" + postcss: "npm:^8.2.14" + source-map: "npm:0.6.1" + checksum: 10c0/53eef3620332f2fc35a4deffaa4395064b2ffd1bc28be380faa3f1e99c2fb7bbf0f705700b4539387d5b6c39586df54a92cd5d031606f19de4bf9e0ff1b6a522 + languageName: node + linkType: hard + +"resolve@npm:^1.1.7, resolve@npm:^1.22.4, resolve@npm:^1.22.8": + version: 1.22.11 + resolution: "resolve@npm:1.22.11" + dependencies: + is-core-module: "npm:^2.16.1" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/f657191507530f2cbecb5815b1ee99b20741ea6ee02a59c57028e9ec4c2c8d7681afcc35febbd554ac0ded459db6f2d8153382c53a2f266cee2575e512674409 + languageName: node + linkType: hard + +"resolve@npm:^2.0.0-next.4, resolve@npm:^2.0.0-next.5": + version: 2.0.0-next.5 + resolution: "resolve@npm:2.0.0-next.5" + dependencies: + is-core-module: "npm:^2.13.0" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/a6c33555e3482ea2ec4c6e3d3bf0d78128abf69dca99ae468e64f1e30acaa318fd267fb66c8836b04d558d3e2d6ed875fe388067e7d8e0de647d3c21af21c43a + languageName: node + linkType: hard + +"resolve@patch:resolve@npm%3A^1.1.7#optional!builtin, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin, resolve@patch:resolve@npm%3A^1.22.8#optional!builtin": + version: 1.22.11 + resolution: "resolve@patch:resolve@npm%3A1.22.11#optional!builtin::version=1.22.11&hash=c3c19d" + dependencies: + is-core-module: "npm:^2.16.1" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/ee5b182f2e37cb1165465e58c6abc797fec0a80b5ba3231607beb4677db0c9291ac010c47cf092b6daa2b7f518d69a0e21888e7e2b633f68d501a874212a8c63 + languageName: node + linkType: hard + +"resolve@patch:resolve@npm%3A^2.0.0-next.4#optional!builtin, resolve@patch:resolve@npm%3A^2.0.0-next.5#optional!builtin": + version: 2.0.0-next.5 + resolution: "resolve@patch:resolve@npm%3A2.0.0-next.5#optional!builtin::version=2.0.0-next.5&hash=c3c19d" + dependencies: + is-core-module: "npm:^2.13.0" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/78ad6edb8309a2bfb720c2c1898f7907a37f858866ce11a5974643af1203a6a6e05b2fa9c53d8064a673a447b83d42569260c306d43628bff5bb101969708355 + languageName: node + linkType: hard + +"responselike@npm:^2.0.0": + version: 2.0.1 + resolution: "responselike@npm:2.0.1" + dependencies: + lowercase-keys: "npm:^2.0.0" + checksum: 10c0/360b6deb5f101a9f8a4174f7837c523c3ec78b7ca8a7c1d45a1062b303659308a23757e318b1e91ed8684ad1205721142dd664d94771cd63499353fd4ee732b5 + languageName: node + linkType: hard + +"ret@npm:~0.1.10": + version: 0.1.15 + resolution: "ret@npm:0.1.15" + checksum: 10c0/01f77cad0f7ea4f955852c03d66982609893edc1240c0c964b4c9251d0f9fb6705150634060d169939b096d3b77f4c84d6b6098a5b5d340160898c8581f1f63f + languageName: node + linkType: hard + +"retry@npm:^0.12.0": + version: 0.12.0 + resolution: "retry@npm:0.12.0" + checksum: 10c0/59933e8501727ba13ad73ef4a04d5280b3717fd650408460c987392efe9d7be2040778ed8ebe933c5cbd63da3dcc37919c141ef8af0a54a6e4fca5a2af177bfe + languageName: node + linkType: hard + +"reusify@npm:^1.0.4": + version: 1.1.0 + resolution: "reusify@npm:1.1.0" + checksum: 10c0/4eff0d4a5f9383566c7d7ec437b671cc51b25963bd61bf127c3f3d3f68e44a026d99b8d2f1ad344afff8d278a8fe70a8ea092650a716d22287e8bef7126bb2fa + languageName: node + linkType: hard + +"rimraf@npm:5.0.1": + version: 5.0.1 + resolution: "rimraf@npm:5.0.1" + dependencies: + glob: "npm:^10.2.5" + bin: + rimraf: dist/cjs/src/bin.js + checksum: 10c0/9e6062c0aea96f384dd937e6bb06b624c881de2eee79a83d3068193183d44eb9b1f3f68a27a54b9ca8cce56bf34c2951ff4239b093b970e0501a091907031f52 + languageName: node + linkType: hard + +"rimraf@npm:^3.0.2": + version: 3.0.2 + resolution: "rimraf@npm:3.0.2" + dependencies: + glob: "npm:^7.1.3" + bin: + rimraf: bin.js + checksum: 10c0/9cb7757acb489bd83757ba1a274ab545eafd75598a9d817e0c3f8b164238dd90eba50d6b848bd4dcc5f3040912e882dc7ba71653e35af660d77b25c381d402e8 + languageName: node + linkType: hard + +"ripemd160@npm:^2.0.0, ripemd160@npm:^2.0.1, ripemd160@npm:^2.0.3": + version: 2.0.3 + resolution: "ripemd160@npm:2.0.3" + dependencies: + hash-base: "npm:^3.1.2" + inherits: "npm:^2.0.4" + checksum: 10c0/3f472fb453241cfe692a77349accafca38dbcdc9d96d5848c088b2932ba41eb968630ecff7b175d291c7487a4945aee5a81e30c064d1f94e36070f7e0c37ed6c + languageName: node + linkType: hard + +"roarr@npm:^2.15.3": + version: 2.15.4 + resolution: "roarr@npm:2.15.4" + dependencies: + boolean: "npm:^3.0.1" + detect-node: "npm:^2.0.4" + globalthis: "npm:^1.0.1" + json-stringify-safe: "npm:^5.0.1" + semver-compare: "npm:^1.0.0" + sprintf-js: "npm:^1.1.2" + checksum: 10c0/7d01d4c14513c461778dd673a8f9e53255221f8d04173aafeb8e11b23d8b659bb83f1c90cfe81af7f9c213b8084b404b918108fd792bda76678f555340cc64ec + languageName: node + linkType: hard + +"rollup-plugin-visualizer@npm:5.9.0": + version: 5.9.0 + resolution: "rollup-plugin-visualizer@npm:5.9.0" + dependencies: + open: "npm:^8.4.0" + picomatch: "npm:^2.3.1" + source-map: "npm:^0.7.4" + yargs: "npm:^17.5.1" + peerDependencies: + rollup: 2.x || 3.x + peerDependenciesMeta: + rollup: + optional: true + bin: + rollup-plugin-visualizer: dist/bin/cli.js + checksum: 10c0/dc706e09c78124b2e05b58779c757e488c76e679b92795f8538f8efffa61845339d75a7f767d5a9eeb5d7e4fd76a835dd93ebf17315bf8a0a0e5fc85a4f59ab5 + languageName: node + linkType: hard + +"rollup@npm:^3.27.1": + version: 3.29.5 + resolution: "rollup@npm:3.29.5" + dependencies: + fsevents: "npm:~2.3.2" + dependenciesMeta: + fsevents: + optional: true + bin: + rollup: dist/bin/rollup + checksum: 10c0/a1fa26f21f0d6cf93b6d05ea284ad5854905b585f28a14c27d439b0f9b859cba13ea25f376303d86770e59b4686bedc52b4706e57442514f0414c6fd3c5b8e71 + languageName: node + linkType: hard + +"run-applescript@npm:^5.0.0": + version: 5.0.0 + resolution: "run-applescript@npm:5.0.0" + dependencies: + execa: "npm:^5.0.0" + checksum: 10c0/f9977db5770929f3f0db434b8e6aa266498c70dec913c84320c0a06add510cf44e3a048c44da088abee312006f9cbf572fd065cdc8f15d7682afda8755f4114c + languageName: node + linkType: hard + +"run-parallel@npm:^1.1.9": + version: 1.2.0 + resolution: "run-parallel@npm:1.2.0" + dependencies: + queue-microtask: "npm:^1.2.2" + checksum: 10c0/200b5ab25b5b8b7113f9901bfe3afc347e19bb7475b267d55ad0eb86a62a46d77510cb0f232507c9e5d497ebda569a08a9867d0d14f57a82ad5564d991588b39 + languageName: node + linkType: hard + +"rxjs@npm:^6.5.4": + version: 6.6.7 + resolution: "rxjs@npm:6.6.7" + dependencies: + tslib: "npm:^1.9.0" + checksum: 10c0/e556a13a9aa89395e5c9d825eabcfa325568d9c9990af720f3f29f04a888a3b854f25845c2b55875d875381abcae2d8100af9cacdc57576e7ed6be030a01d2fe + languageName: node + linkType: hard + +"rxjs@npm:^7.8.1": + version: 7.8.2 + resolution: "rxjs@npm:7.8.2" + dependencies: + tslib: "npm:^2.1.0" + checksum: 10c0/1fcd33d2066ada98ba8f21fcbbcaee9f0b271de1d38dc7f4e256bfbc6ffcdde68c8bfb69093de7eeb46f24b1fb820620bf0223706cff26b4ab99a7ff7b2e2c45 + languageName: node + linkType: hard + +"sade@npm:^1.7.3": + version: 1.8.1 + resolution: "sade@npm:1.8.1" + dependencies: + mri: "npm:^1.1.0" + checksum: 10c0/da8a3a5d667ad5ce3bf6d4f054bbb9f711103e5df21003c5a5c1a8a77ce12b640ed4017dd423b13c2307ea7e645adee7c2ae3afe8051b9db16a6f6d3da3f90b1 + languageName: node + linkType: hard + +"safe-array-concat@npm:^1.1.3": + version: 1.1.3 + resolution: "safe-array-concat@npm:1.1.3" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.2" + get-intrinsic: "npm:^1.2.6" + has-symbols: "npm:^1.1.0" + isarray: "npm:^2.0.5" + checksum: 10c0/43c86ffdddc461fb17ff8a17c5324f392f4868f3c7dd2c6a5d9f5971713bc5fd755667212c80eab9567595f9a7509cc2f83e590ddaebd1bd19b780f9c79f9a8d + languageName: node + linkType: hard + +"safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:^5.1.1, safe-buffer@npm:^5.1.2, safe-buffer@npm:^5.2.1, safe-buffer@npm:~5.2.0": + version: 5.2.1 + resolution: "safe-buffer@npm:5.2.1" + checksum: 10c0/6501914237c0a86e9675d4e51d89ca3c21ffd6a31642efeba25ad65720bce6921c9e7e974e5be91a786b25aa058b5303285d3c15dbabf983a919f5f630d349f3 + languageName: node + linkType: hard + +"safe-buffer@npm:~5.1.0, safe-buffer@npm:~5.1.1": + version: 5.1.2 + resolution: "safe-buffer@npm:5.1.2" + checksum: 10c0/780ba6b5d99cc9a40f7b951d47152297d0e260f0df01472a1b99d4889679a4b94a13d644f7dbc4f022572f09ae9005fa2fbb93bbbd83643316f365a3e9a45b21 + languageName: node + linkType: hard + +"safe-push-apply@npm:^1.0.0": + version: 1.0.0 + resolution: "safe-push-apply@npm:1.0.0" + dependencies: + es-errors: "npm:^1.3.0" + isarray: "npm:^2.0.5" + checksum: 10c0/831f1c9aae7436429e7862c7e46f847dfe490afac20d0ee61bae06108dbf5c745a0de3568ada30ccdd3eeb0864ca8331b2eef703abd69bfea0745b21fd320750 + languageName: node + linkType: hard + +"safe-regex-test@npm:^1.1.0": + version: 1.1.0 + resolution: "safe-regex-test@npm:1.1.0" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + is-regex: "npm:^1.2.1" + checksum: 10c0/f2c25281bbe5d39cddbbce7f86fca5ea9b3ce3354ea6cd7c81c31b006a5a9fff4286acc5450a3b9122c56c33eba69c56b9131ad751457b2b4a585825e6a10665 + languageName: node + linkType: hard + +"safe-stable-stringify@npm:^2.1.0, safe-stable-stringify@npm:^2.4.3": + version: 2.5.0 + resolution: "safe-stable-stringify@npm:2.5.0" + checksum: 10c0/baea14971858cadd65df23894a40588ed791769db21bafb7fd7608397dbdce9c5aac60748abae9995e0fc37e15f2061980501e012cd48859740796bea2987f49 + languageName: node + linkType: hard + +"safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0": + version: 2.1.2 + resolution: "safer-buffer@npm:2.1.2" + checksum: 10c0/7e3c8b2e88a1841c9671094bbaeebd94448111dd90a81a1f606f3f67708a6ec57763b3b47f06da09fc6054193e0e6709e77325415dc8422b04497a8070fa02d4 + languageName: node + linkType: hard + +"sanitize-filename@npm:^1.6.3": + version: 1.6.3 + resolution: "sanitize-filename@npm:1.6.3" + dependencies: + truncate-utf8-bytes: "npm:^1.0.0" + checksum: 10c0/16ff47556a6e54e228c28db096bedd303da67b030d4bea4925fd71324932d6b02c7b0446f00ad33987b25b6414f24ae968e01a1a1679ce599542e82c4b07eb1f + languageName: node + linkType: hard + +"sass-loader@npm:^13.2.0": + version: 13.3.3 + resolution: "sass-loader@npm:13.3.3" + dependencies: + neo-async: "npm:^2.6.2" + peerDependencies: + fibers: ">= 3.1.0" + node-sass: ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 + sass: ^1.3.0 + sass-embedded: "*" + webpack: ^5.0.0 + peerDependenciesMeta: + fibers: + optional: true + node-sass: + optional: true + sass: + optional: true + sass-embedded: + optional: true + checksum: 10c0/5e955a4ffce35ee0a46fce677ce51eaa69587fb5371978588c83af00f49e7edc36dcf3bb559cbae27681c5e24a71284463ebe03a1fb65e6ecafa1db0620e3fc8 + languageName: node + linkType: hard + +"sass-loader@npm:^16.0.5": + version: 16.0.6 + resolution: "sass-loader@npm:16.0.6" + dependencies: + neo-async: "npm:^2.6.2" + peerDependencies: + "@rspack/core": 0.x || 1.x + node-sass: ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 + sass: ^1.3.0 + sass-embedded: "*" + webpack: ^5.0.0 + peerDependenciesMeta: + "@rspack/core": + optional: true + node-sass: + optional: true + sass: + optional: true + sass-embedded: + optional: true + webpack: + optional: true + checksum: 10c0/a66df6ecc01c80011a2bc9356d2b262753ad425382171d120ec5d4b5015d5131e919384a22cd148d48ecc1cb4fa598acaaa6308b260f8951f3558b5785816bb4 + languageName: node + linkType: hard + +"sass@npm:1.54.0": + version: 1.54.0 + resolution: "sass@npm:1.54.0" + dependencies: + chokidar: "npm:>=3.0.0 <4.0.0" + immutable: "npm:^4.0.0" + source-map-js: "npm:>=0.6.2 <2.0.0" + bin: + sass: sass.js + checksum: 10c0/4d9a4b1eb0761918705e47a83d50d62fc94bddc4b9e0d2c231b28df44102d2dad09063eff700da534f3271222185d28e2a80f015e1fc12cad97ed1766f876d1a + languageName: node + linkType: hard + +"sax@npm:^1.2.4": + version: 1.4.3 + resolution: "sax@npm:1.4.3" + checksum: 10c0/45bba07561d93f184a8686e1a543418ced8c844b994fbe45cc49d5cd2fc8ac7ec949dae38565e35e388ad0cca2b75997a29b6857c927bf6553da3f80ed0e4e62 + languageName: node + linkType: hard + +"scheduler@npm:^0.23.2": + version: 0.23.2 + resolution: "scheduler@npm:0.23.2" + dependencies: + loose-envify: "npm:^1.1.0" + checksum: 10c0/26383305e249651d4c58e6705d5f8425f153211aef95f15161c151f7b8de885f24751b377e4a0b3dd42cce09aad3f87a61dab7636859c0d89b7daf1a1e2a5c78 + languageName: node + linkType: hard + +"schema-utils@npm:^3.0.0, schema-utils@npm:^3.1.1": + version: 3.3.0 + resolution: "schema-utils@npm:3.3.0" + dependencies: + "@types/json-schema": "npm:^7.0.8" + ajv: "npm:^6.12.5" + ajv-keywords: "npm:^3.5.2" + checksum: 10c0/fafdbde91ad8aa1316bc543d4b61e65ea86970aebbfb750bfb6d8a6c287a23e415e0e926c2498696b242f63af1aab8e585252637fabe811fd37b604351da6500 + languageName: node + linkType: hard + +"screenfull@npm:^5.0.0": + version: 5.2.0 + resolution: "screenfull@npm:5.2.0" + checksum: 10c0/86fd49983e2edc153ee2e674a570c711cb0961a9cacca659309f79636ccc8ca8a0b830ea4dacdae7403a8bb7ba6affd5bcdce053aa97782961247a49bfd2ba68 + languageName: node + linkType: hard + +"scroll-into-view-if-needed@npm:^3.1.0": + version: 3.1.0 + resolution: "scroll-into-view-if-needed@npm:3.1.0" + dependencies: + compute-scroll-into-view: "npm:^3.0.2" + checksum: 10c0/1f46b090e1e04fcfdef1e384f6d7e615f9f84d4176faf4dbba7347cc0a6e491e5d578eaf4dbe9618dd3d8d38efafde58535b3e00f2a21ce4178c14be364850ff + languageName: node + linkType: hard + +"select-hose@npm:^2.0.0": + version: 2.0.0 + resolution: "select-hose@npm:2.0.0" + checksum: 10c0/01cc52edd29feddaf379efb4328aededa633f0ac43c64b11a8abd075ff34f05b0d280882c4fbcbdf1a0658202c9cd2ea8d5985174dcf9a2dac7e3a4996fa9b67 + languageName: node + linkType: hard + +"semver-compare@npm:^1.0.0": + version: 1.0.0 + resolution: "semver-compare@npm:1.0.0" + checksum: 10c0/9ef4d8b81847556f0865f46ddc4d276bace118c7cb46811867af82e837b7fc473911981d5a0abc561fa2db487065572217e5b06e18701c4281bcdd2a1affaff1 + languageName: node + linkType: hard + +"semver@npm:^5.6.0, semver@npm:^5.7.2": + version: 5.7.2 + resolution: "semver@npm:5.7.2" + bin: + semver: bin/semver + checksum: 10c0/e4cf10f86f168db772ae95d86ba65b3fd6c5967c94d97c708ccb463b778c2ee53b914cd7167620950fc07faf5a564e6efe903836639e512a1aa15fbc9667fa25 + languageName: node + linkType: hard + +"semver@npm:^6.2.0, semver@npm:^6.3.0, semver@npm:^6.3.1": + version: 6.3.1 + resolution: "semver@npm:6.3.1" + bin: + semver: bin/semver.js + checksum: 10c0/e3d79b609071caa78bcb6ce2ad81c7966a46a7431d9d58b8800cfa9cb6a63699b3899a0e4bcce36167a284578212d9ae6942b6929ba4aa5015c079a67751d42d + languageName: node + linkType: hard + +"semver@npm:^7.3.2, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.5.4, semver@npm:^7.6.2, semver@npm:^7.7.1": + version: 7.7.3 + resolution: "semver@npm:7.7.3" + bin: + semver: bin/semver.js + checksum: 10c0/4afe5c986567db82f44c8c6faef8fe9df2a9b1d98098fc1721f57c696c4c21cebd572f297fc21002f81889492345b8470473bc6f4aff5fb032a6ea59ea2bc45e + languageName: node + linkType: hard + +"semver@npm:~7.0.0": + version: 7.0.0 + resolution: "semver@npm:7.0.0" + bin: + semver: bin/semver.js + checksum: 10c0/7fd341680a967a0abfd66f3a7d36ba44e52ff5d3e799e9a6cdb01a68160b64ef09be82b4af05459effeecdd836f002c2462555d2821cd890dfdfe36a0d9f56a5 + languageName: node + linkType: hard + +"send@npm:0.17.1": + version: 0.17.1 + resolution: "send@npm:0.17.1" + dependencies: + debug: "npm:2.6.9" + depd: "npm:~1.1.2" + destroy: "npm:~1.0.4" + encodeurl: "npm:~1.0.2" + escape-html: "npm:~1.0.3" + etag: "npm:~1.8.1" + fresh: "npm:0.5.2" + http-errors: "npm:~1.7.2" + mime: "npm:1.6.0" + ms: "npm:2.1.1" + on-finished: "npm:~2.3.0" + range-parser: "npm:~1.2.1" + statuses: "npm:~1.5.0" + checksum: 10c0/712e27d5d4f38d6097a649bbe8846a30a6f9d1995e78e1c133a7a351ec26508b0d8fb707dadb6e003f3753d3f9310667e04633522883b81300abd9978b28afd2 + languageName: node + linkType: hard + +"send@npm:0.19.0": + version: 0.19.0 + resolution: "send@npm:0.19.0" + dependencies: + debug: "npm:2.6.9" + depd: "npm:2.0.0" + destroy: "npm:1.2.0" + encodeurl: "npm:~1.0.2" + escape-html: "npm:~1.0.3" + etag: "npm:~1.8.1" + fresh: "npm:0.5.2" + http-errors: "npm:2.0.0" + mime: "npm:1.6.0" + ms: "npm:2.1.3" + on-finished: "npm:2.4.1" + range-parser: "npm:~1.2.1" + statuses: "npm:2.0.1" + checksum: 10c0/ea3f8a67a8f0be3d6bf9080f0baed6d2c51d11d4f7b4470de96a5029c598a7011c497511ccc28968b70ef05508675cebff27da9151dd2ceadd60be4e6cf845e3 + languageName: node + linkType: hard + +"serialize-error@npm:^7.0.1": + version: 7.0.1 + resolution: "serialize-error@npm:7.0.1" + dependencies: + type-fest: "npm:^0.13.1" + checksum: 10c0/7982937d578cd901276c8ab3e2c6ed8a4c174137730f1fb0402d005af209a0e84d04acc874e317c936724c7b5b26c7a96ff7e4b8d11a469f4924a4b0ea814c05 + languageName: node + linkType: hard + +"serve-static@npm:1.16.2": + version: 1.16.2 + resolution: "serve-static@npm:1.16.2" + dependencies: + encodeurl: "npm:~2.0.0" + escape-html: "npm:~1.0.3" + parseurl: "npm:~1.3.3" + send: "npm:0.19.0" + checksum: 10c0/528fff6f5e12d0c5a391229ad893910709bc51b5705962b09404a1d813857578149b8815f35d3ee5752f44cd378d0f31669d4b1d7e2d11f41e08283d5134bd1f + languageName: node + linkType: hard + +"set-function-length@npm:^1.2.2": + version: 1.2.2 + resolution: "set-function-length@npm:1.2.2" + dependencies: + define-data-property: "npm:^1.1.4" + es-errors: "npm:^1.3.0" + function-bind: "npm:^1.1.2" + get-intrinsic: "npm:^1.2.4" + gopd: "npm:^1.0.1" + has-property-descriptors: "npm:^1.0.2" + checksum: 10c0/82850e62f412a258b71e123d4ed3873fa9377c216809551192bb6769329340176f109c2eeae8c22a8d386c76739855f78e8716515c818bcaef384b51110f0f3c + languageName: node + linkType: hard + +"set-function-name@npm:^2.0.2": + version: 2.0.2 + resolution: "set-function-name@npm:2.0.2" + dependencies: + define-data-property: "npm:^1.1.4" + es-errors: "npm:^1.3.0" + functions-have-names: "npm:^1.2.3" + has-property-descriptors: "npm:^1.0.2" + checksum: 10c0/fce59f90696c450a8523e754abb305e2b8c73586452619c2bad5f7bf38c7b6b4651895c9db895679c5bef9554339cf3ef1c329b66ece3eda7255785fbe299316 + languageName: node + linkType: hard + +"set-proto@npm:^1.0.0": + version: 1.0.0 + resolution: "set-proto@npm:1.0.0" + dependencies: + dunder-proto: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/ca5c3ccbba479d07c30460e367e66337cec825560b11e8ba9c5ebe13a2a0d6021ae34eddf94ff3dfe17a3104dc1f191519cb6c48378b503e5c3f36393938776a + languageName: node + linkType: hard + +"setimmediate@npm:^1.0.4": + version: 1.0.5 + resolution: "setimmediate@npm:1.0.5" + checksum: 10c0/5bae81bfdbfbd0ce992893286d49c9693c82b1bcc00dcaaf3a09c8f428fdeacf4190c013598b81875dfac2b08a572422db7df779a99332d0fce186d15a3e4d49 + languageName: node + linkType: hard + +"setprototypeof@npm:1.1.1": + version: 1.1.1 + resolution: "setprototypeof@npm:1.1.1" + checksum: 10c0/1084b783f2d77908b0a593619e1214c2118c44c7c3277f6099dd7ca8acfc056c009e5d1b2860eae5e8b0ba9bc0a978c15613ff102ccc1093bb48aa6e0ed75e2f + languageName: node + linkType: hard + +"setprototypeof@npm:1.2.0, setprototypeof@npm:~1.2.0": + version: 1.2.0 + resolution: "setprototypeof@npm:1.2.0" + checksum: 10c0/68733173026766fa0d9ecaeb07f0483f4c2dc70ca376b3b7c40b7cda909f94b0918f6c5ad5ce27a9160bdfb475efaa9d5e705a11d8eaae18f9835d20976028bc + languageName: node + linkType: hard + +"sha.js@npm:^2.4.0, sha.js@npm:^2.4.12, sha.js@npm:^2.4.8": + version: 2.4.12 + resolution: "sha.js@npm:2.4.12" + dependencies: + inherits: "npm:^2.0.4" + safe-buffer: "npm:^5.2.1" + to-buffer: "npm:^1.2.0" + bin: + sha.js: bin.js + checksum: 10c0/9d36bdd76202c8116abbe152a00055ccd8a0099cb28fc17c01fa7bb2c8cffb9ca60e2ab0fe5f274ed6c45dc2633d8c39cf7ab050306c231904512ba9da4d8ab1 + languageName: node + linkType: hard + +"shallow-equal@npm:^1.2.1": + version: 1.2.1 + resolution: "shallow-equal@npm:1.2.1" + checksum: 10c0/51e03abadd97c9ebe590547d92db9148446962a3f23a3a0fb1ba2fccab80af881eef0ff1f8ccefd3f066c0bc5a4c8ca53706194813b95c8835fa66448a843a26 + languageName: node + linkType: hard + +"shallowequal@npm:1.1.0, shallowequal@npm:^1.1.0": + version: 1.1.0 + resolution: "shallowequal@npm:1.1.0" + checksum: 10c0/b926efb51cd0f47aa9bc061add788a4a650550bbe50647962113a4579b60af2abe7b62f9b02314acc6f97151d4cf87033a2b15fc20852fae306d1a095215396c + languageName: node + linkType: hard + +"shebang-command@npm:^2.0.0": + version: 2.0.0 + resolution: "shebang-command@npm:2.0.0" + dependencies: + shebang-regex: "npm:^3.0.0" + checksum: 10c0/a41692e7d89a553ef21d324a5cceb5f686d1f3c040759c50aab69688634688c5c327f26f3ecf7001ebfd78c01f3c7c0a11a7c8bfd0a8bc9f6240d4f40b224e4e + languageName: node + linkType: hard + +"shebang-regex@npm:^3.0.0": + version: 3.0.0 + resolution: "shebang-regex@npm:3.0.0" + checksum: 10c0/1dbed0726dd0e1152a92696c76c7f06084eb32a90f0528d11acd764043aacf76994b2fb30aa1291a21bd019d6699164d048286309a278855ee7bec06cf6fb690 + languageName: node + linkType: hard + +"shell-quote@npm:^1.8.1": + version: 1.8.3 + resolution: "shell-quote@npm:1.8.3" + checksum: 10c0/bee87c34e1e986cfb4c30846b8e6327d18874f10b535699866f368ade11ea4ee45433d97bf5eada22c4320c27df79c3a6a7eb1bf3ecfc47f2c997d9e5e2672fd + languageName: node + linkType: hard + +"side-channel-list@npm:^1.0.0": + version: 1.0.0 + resolution: "side-channel-list@npm:1.0.0" + dependencies: + es-errors: "npm:^1.3.0" + object-inspect: "npm:^1.13.3" + checksum: 10c0/644f4ac893456c9490ff388bf78aea9d333d5e5bfc64cfb84be8f04bf31ddc111a8d4b83b85d7e7e8a7b845bc185a9ad02c052d20e086983cf59f0be517d9b3d + languageName: node + linkType: hard + +"side-channel-map@npm:^1.0.1": + version: 1.0.1 + resolution: "side-channel-map@npm:1.0.1" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.5" + object-inspect: "npm:^1.13.3" + checksum: 10c0/010584e6444dd8a20b85bc926d934424bd809e1a3af941cace229f7fdcb751aada0fb7164f60c2e22292b7fa3c0ff0bce237081fd4cdbc80de1dc68e95430672 + languageName: node + linkType: hard + +"side-channel-weakmap@npm:^1.0.2": + version: 1.0.2 + resolution: "side-channel-weakmap@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.5" + object-inspect: "npm:^1.13.3" + side-channel-map: "npm:^1.0.1" + checksum: 10c0/71362709ac233e08807ccd980101c3e2d7efe849edc51455030327b059f6c4d292c237f94dc0685031dd11c07dd17a68afde235d6cf2102d949567f98ab58185 + languageName: node + linkType: hard + +"side-channel@npm:^1.0.6, side-channel@npm:^1.1.0": + version: 1.1.0 + resolution: "side-channel@npm:1.1.0" + dependencies: + es-errors: "npm:^1.3.0" + object-inspect: "npm:^1.13.3" + side-channel-list: "npm:^1.0.0" + side-channel-map: "npm:^1.0.1" + side-channel-weakmap: "npm:^1.0.2" + checksum: 10c0/cb20dad41eb032e6c24c0982e1e5a24963a28aa6122b4f05b3f3d6bf8ae7fd5474ef382c8f54a6a3ab86e0cac4d41a23bd64ede3970e5bfb50326ba02a7996e6 + languageName: node + linkType: hard + +"signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": + version: 3.0.7 + resolution: "signal-exit@npm:3.0.7" + checksum: 10c0/25d272fa73e146048565e08f3309d5b942c1979a6f4a58a8c59d5fa299728e9c2fcd1a759ec870863b1fd38653670240cd420dad2ad9330c71f36608a6a1c912 + languageName: node + linkType: hard + +"signal-exit@npm:^4.0.1": + version: 4.1.0 + resolution: "signal-exit@npm:4.1.0" + checksum: 10c0/41602dce540e46d599edba9d9860193398d135f7ff72cab629db5171516cfae628d21e7bfccde1bbfdf11c48726bc2a6d1a8fb8701125852fbfda7cf19c6aa83 + languageName: node + linkType: hard + +"simple-update-notifier@npm:^1.0.7": + version: 1.1.0 + resolution: "simple-update-notifier@npm:1.1.0" + dependencies: + semver: "npm:~7.0.0" + checksum: 10c0/3cbbbc71a5d9a2924f0e3f42fbf3cbe1854bfe142203456b00d5233bdbbdeb5091b8067cd34fb00f81dbfbc29fc30dbb6e026b3d58ea0551e3f26c0e64082092 + languageName: node + linkType: hard + +"single-spa@npm:^5.9.2": + version: 5.9.5 + resolution: "single-spa@npm:5.9.5" + checksum: 10c0/920e2fe32b82b2bccc505d84061cc0fb9272410a10d344bcb6422c8cba7247e66e7de1e68b0f81b3a41c91f366bd2373ea65314ae777f12dd2e0aa201c97e9da + languageName: node + linkType: hard + +"size-sensor@npm:^1.0.1": + version: 1.0.2 + resolution: "size-sensor@npm:1.0.2" + checksum: 10c0/2cb020cd54ae7bea76a0600ba82c1a09daf51e8bec6f96355e17b4806eb24003626e3349d6cb091f821ed5fbba193f59ca59f0ea7f6dd8b75d7c20b96c465c26 + languageName: node + linkType: hard + +"slash@npm:^3.0.0": + version: 3.0.0 + resolution: "slash@npm:3.0.0" + checksum: 10c0/e18488c6a42bdfd4ac5be85b2ced3ccd0224773baae6ad42cfbb9ec74fc07f9fa8396bd35ee638084ead7a2a0818eb5e7151111544d4731ce843019dab4be47b + languageName: node + linkType: hard + +"slash@npm:^4.0.0": + version: 4.0.0 + resolution: "slash@npm:4.0.0" + checksum: 10c0/b522ca75d80d107fd30d29df0549a7b2537c83c4c4ecd12cd7d4ea6c8aaca2ab17ada002e7a1d78a9d736a0261509f26ea5b489082ee443a3a810586ef8eff18 + languageName: node + linkType: hard + +"slash@npm:^5.1.0": + version: 5.1.0 + resolution: "slash@npm:5.1.0" + checksum: 10c0/eb48b815caf0bdc390d0519d41b9e0556a14380f6799c72ba35caf03544d501d18befdeeef074bc9c052acf69654bc9e0d79d7f1de0866284137a40805299eb3 + languageName: node + linkType: hard + +"slice-ansi@npm:^3.0.0": + version: 3.0.0 + resolution: "slice-ansi@npm:3.0.0" + dependencies: + ansi-styles: "npm:^4.0.0" + astral-regex: "npm:^2.0.0" + is-fullwidth-code-point: "npm:^3.0.0" + checksum: 10c0/88083c9d0ca67d09f8b4c78f68833d69cabbb7236b74df5d741ad572bbf022deaf243fa54009cd434350622a1174ab267710fcc80a214ecc7689797fe00cb27c + languageName: node + linkType: hard + +"slice-ansi@npm:^4.0.0": + version: 4.0.0 + resolution: "slice-ansi@npm:4.0.0" + dependencies: + ansi-styles: "npm:^4.0.0" + astral-regex: "npm:^2.0.0" + is-fullwidth-code-point: "npm:^3.0.0" + checksum: 10c0/6c25678db1270d4793e0327620f1e0f9f5bea4630123f51e9e399191bc52c87d6e6de53ed33538609e5eacbd1fab769fae00f3705d08d029f02102a540648918 + languageName: node + linkType: hard + +"smart-buffer@npm:^4.0.2, smart-buffer@npm:^4.2.0": + version: 4.2.0 + resolution: "smart-buffer@npm:4.2.0" + checksum: 10c0/a16775323e1404dd43fabafe7460be13a471e021637bc7889468eb45ce6a6b207261f454e4e530a19500cc962c4cc5348583520843b363f4193cee5c00e1e539 + languageName: node + linkType: hard + +"socks-proxy-agent@npm:^8.0.3": + version: 8.0.5 + resolution: "socks-proxy-agent@npm:8.0.5" + dependencies: + agent-base: "npm:^7.1.2" + debug: "npm:^4.3.4" + socks: "npm:^2.8.3" + checksum: 10c0/5d2c6cecba6821389aabf18728325730504bf9bb1d9e342e7987a5d13badd7a98838cc9a55b8ed3cb866ad37cc23e1086f09c4d72d93105ce9dfe76330e9d2a6 + languageName: node + linkType: hard + +"socks@npm:^2.8.3": + version: 2.8.7 + resolution: "socks@npm:2.8.7" + dependencies: + ip-address: "npm:^10.0.1" + smart-buffer: "npm:^4.2.0" + checksum: 10c0/2805a43a1c4bcf9ebf6e018268d87b32b32b06fbbc1f9282573583acc155860dc361500f89c73bfbb157caa1b4ac78059eac0ef15d1811eb0ca75e0bdadbc9d2 + languageName: node + linkType: hard + +"sonic-boom@npm:^2.2.1": + version: 2.8.0 + resolution: "sonic-boom@npm:2.8.0" + dependencies: + atomic-sleep: "npm:^1.0.0" + checksum: 10c0/6b40f2e91a999819b1dc24018a5d1c8b74e66e5d019eabad17d5b43fc309b32255b7c405ed6ec885693c8f2b969099ce96aeefde027180928bc58c034234a86d + languageName: node + linkType: hard + +"sort-object-keys@npm:^1.1.3": + version: 1.1.3 + resolution: "sort-object-keys@npm:1.1.3" + checksum: 10c0/3bf62398658d3ff4bbca0db4ed8f42f98abc41433859f63d02fb0ab953fbe5526be240ec7e5d85aa50fcab6c937f3fa7015abf1ecdeb3045a2281c53953886bf + languageName: node + linkType: hard + +"sort-package-json@npm:2.4.1": + version: 2.4.1 + resolution: "sort-package-json@npm:2.4.1" + dependencies: + detect-indent: "npm:^7.0.1" + detect-newline: "npm:^4.0.0" + git-hooks-list: "npm:^3.0.0" + globby: "npm:^13.1.2" + is-plain-obj: "npm:^4.1.0" + sort-object-keys: "npm:^1.1.3" + bin: + sort-package-json: cli.js + checksum: 10c0/90fdf61b338f90c899c0c09e38de1826171976843889aeac1e34b87724a179140a4e89b967382635e2b0dcb3a4519c7f3dca3b219406a31f8d80e0e78d39df03 + languageName: node + linkType: hard + +"sort-package-json@npm:3.4.0": + version: 3.4.0 + resolution: "sort-package-json@npm:3.4.0" + dependencies: + detect-indent: "npm:^7.0.1" + detect-newline: "npm:^4.0.1" + git-hooks-list: "npm:^4.0.0" + is-plain-obj: "npm:^4.1.0" + semver: "npm:^7.7.1" + sort-object-keys: "npm:^1.1.3" + tinyglobby: "npm:^0.2.12" + bin: + sort-package-json: cli.js + checksum: 10c0/1adb7860eee770fa51ac1c810c2fa2483ab47bf150d1fc2437ef28314ee928142a51245ba22aac8a8c662f431609fc633d404bcdd93acbf54d5a056253741218 + languageName: node + linkType: hard + +"source-map-js@npm:>=0.6.2 <2.0.0, source-map-js@npm:^1.0.2, source-map-js@npm:^1.2.1": + version: 1.2.1 + resolution: "source-map-js@npm:1.2.1" + checksum: 10c0/7bda1fc4c197e3c6ff17de1b8b2c20e60af81b63a52cb32ec5a5d67a20a7d42651e2cb34ebe93833c5a2a084377e17455854fee3e21e7925c64a51b6a52b0faf + languageName: node + linkType: hard + +"source-map-support@npm:^0.5.19, source-map-support@npm:^0.5.21, source-map-support@npm:~0.5.20": + version: 0.5.21 + resolution: "source-map-support@npm:0.5.21" + dependencies: + buffer-from: "npm:^1.0.0" + source-map: "npm:^0.6.0" + checksum: 10c0/9ee09942f415e0f721d6daad3917ec1516af746a8120bba7bb56278707a37f1eb8642bde456e98454b8a885023af81a16e646869975f06afc1a711fb90484e7d + languageName: node + linkType: hard + +"source-map@npm:0.6.1, source-map@npm:^0.6.0, source-map@npm:^0.6.1, source-map@npm:~0.6.0": + version: 0.6.1 + resolution: "source-map@npm:0.6.1" + checksum: 10c0/ab55398007c5e5532957cb0beee2368529618ac0ab372d789806f5718123cc4367d57de3904b4e6a4170eb5a0b0f41373066d02ca0735a0c4d75c7d328d3e011 + languageName: node + linkType: hard + +"source-map@npm:^0.5.0": + version: 0.5.7 + resolution: "source-map@npm:0.5.7" + checksum: 10c0/904e767bb9c494929be013017380cbba013637da1b28e5943b566031e29df04fba57edf3f093e0914be094648b577372bd8ad247fa98cfba9c600794cd16b599 + languageName: node + linkType: hard + +"source-map@npm:^0.7.3, source-map@npm:^0.7.4": + version: 0.7.6 + resolution: "source-map@npm:0.7.6" + checksum: 10c0/59f6f05538539b274ba771d2e9e32f6c65451982510564438e048bc1352f019c6efcdc6dd07909b1968144941c14015c2c7d4369fb7c4d7d53ae769716dcc16c + languageName: node + linkType: hard + +"space-separated-tokens@npm:^2.0.0": + version: 2.0.2 + resolution: "space-separated-tokens@npm:2.0.2" + checksum: 10c0/6173e1d903dca41dcab6a2deed8b4caf61bd13b6d7af8374713500570aa929ff9414ae09a0519f4f8772df993300305a395d4871f35bc4ca72b6db57e1f30af8 + languageName: node + linkType: hard + +"spawn-command@npm:0.0.2": + version: 0.0.2 + resolution: "spawn-command@npm:0.0.2" + checksum: 10c0/b22f2d71239e6e628a400831861ba747750bbb40c0a53323754cf7b84330b73d81e40ff1f9055e6d1971818679510208a9302e13d9ff3b32feb67e74d7a1b3ef + languageName: node + linkType: hard + +"spdy-transport@npm:^3.0.0": + version: 3.0.0 + resolution: "spdy-transport@npm:3.0.0" + dependencies: + debug: "npm:^4.1.0" + detect-node: "npm:^2.0.4" + hpack.js: "npm:^2.1.6" + obuf: "npm:^1.1.2" + readable-stream: "npm:^3.0.6" + wbuf: "npm:^1.7.3" + checksum: 10c0/eaf7440fa90724fffc813c386d4a8a7427d967d6e46d7c51d8f8a533d1a6911b9823ea9218703debbae755337e85f110185d7a00ae22ec5c847077b908ce71bb + languageName: node + linkType: hard + +"spdy@npm:^4.0.2": + version: 4.0.2 + resolution: "spdy@npm:4.0.2" + dependencies: + debug: "npm:^4.1.0" + handle-thing: "npm:^2.0.0" + http-deceiver: "npm:^1.2.7" + select-hose: "npm:^2.0.0" + spdy-transport: "npm:^3.0.0" + checksum: 10c0/983509c0be9d06fd00bb9dff713c5b5d35d3ffd720db869acdd5ad7aa6fc0e02c2318b58f75328957d8ff772acdf1f7d19382b6047df342044ff3e2d6805ccdf + languageName: node + linkType: hard + +"split-on-first@npm:^1.0.0": + version: 1.1.0 + resolution: "split-on-first@npm:1.1.0" + checksum: 10c0/56df8344f5a5de8521898a5c090023df1d8b8c75be6228f56c52491e0fc1617a5236f2ac3a066adb67a73231eac216ccea7b5b4a2423a543c277cb2f48d24c29 + languageName: node + linkType: hard + +"split2@npm:^4.0.0": + version: 4.2.0 + resolution: "split2@npm:4.2.0" + checksum: 10c0/b292beb8ce9215f8c642bb68be6249c5a4c7f332fc8ecadae7be5cbdf1ea95addc95f0459ef2e7ad9d45fd1064698a097e4eb211c83e772b49bc0ee423e91534 + languageName: node + linkType: hard + +"sprintf-js@npm:^1.1.2": + version: 1.1.3 + resolution: "sprintf-js@npm:1.1.3" + checksum: 10c0/09270dc4f30d479e666aee820eacd9e464215cdff53848b443964202bf4051490538e5dd1b42e1a65cf7296916ca17640aebf63dae9812749c7542ee5f288dec + languageName: node + linkType: hard + +"sprintf-js@npm:~1.0.2": + version: 1.0.3 + resolution: "sprintf-js@npm:1.0.3" + checksum: 10c0/ecadcfe4c771890140da5023d43e190b7566d9cf8b2d238600f31bec0fc653f328da4450eb04bd59a431771a8e9cc0e118f0aa3974b683a4981b4e07abc2a5bb + languageName: node + linkType: hard + +"sql-formatter@npm:^13.0.4": + version: 13.1.0 + resolution: "sql-formatter@npm:13.1.0" + dependencies: + argparse: "npm:^2.0.1" + get-stdin: "npm:=8.0.0" + nearley: "npm:^2.20.1" + bin: + sql-formatter: bin/sql-formatter-cli.cjs + checksum: 10c0/96a18f1acdf3f0ad21b8d4e32e5b4de3ade7d39a60f90910027a4b2fd251c01b983b61e7d70bd534ec609bd7c24d98f11cecbfe93375832a87a7352c53e4cfc5 + languageName: node + linkType: hard + +"ssri@npm:^13.0.0": + version: 13.0.0 + resolution: "ssri@npm:13.0.0" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/405f3a531cd98b013cecb355d63555dca42fd12c7bc6671738aaa9a82882ff41cdf0ef9a2b734ca4f9a760338f114c29d01d9238a65db3ccac27929bd6e6d4b2 + languageName: node + linkType: hard + +"stable@npm:^0.1.8": + version: 0.1.8 + resolution: "stable@npm:0.1.8" + checksum: 10c0/df74b5883075076e78f8e365e4068ecd977af6c09da510cfc3148a303d4b87bc9aa8f7c48feb67ed4ef970b6140bd9eabba2129e28024aa88df5ea0114cba39d + languageName: node + linkType: hard + +"stackframe@npm:^1.3.4": + version: 1.3.4 + resolution: "stackframe@npm:1.3.4" + checksum: 10c0/18410f7a1e0c5d211a4effa83bdbf24adbe8faa8c34db52e1cd3e89837518c592be60b60d8b7270ac53eeeb8b807cd11b399a41667f6c9abb41059c3ccc8a989 + languageName: node + linkType: hard + +"stat-mode@npm:^1.0.0": + version: 1.0.0 + resolution: "stat-mode@npm:1.0.0" + checksum: 10c0/89b66a538dbfd45038fefdaf5b2104dc6e911605af1c201793e9629592ed9fdc7bdd1bca42806d0d4167c6d9cacac1f3fda41ddfe334a5c1f898113da38fae74 + languageName: node + linkType: hard + +"statuses@npm:2.0.1": + version: 2.0.1 + resolution: "statuses@npm:2.0.1" + checksum: 10c0/34378b207a1620a24804ce8b5d230fea0c279f00b18a7209646d5d47e419d1cc23e7cbf33a25a1e51ac38973dc2ac2e1e9c647a8e481ef365f77668d72becfd0 + languageName: node + linkType: hard + +"statuses@npm:>= 1.5.0 < 2, statuses@npm:~1.5.0": + version: 1.5.0 + resolution: "statuses@npm:1.5.0" + checksum: 10c0/e433900956357b3efd79b1c547da4d291799ac836960c016d10a98f6a810b1b5c0dcc13b5a7aa609a58239b5190e1ea176ad9221c2157d2fd1c747393e6b2940 + languageName: node + linkType: hard + +"statuses@npm:~2.0.2": + version: 2.0.2 + resolution: "statuses@npm:2.0.2" + checksum: 10c0/a9947d98ad60d01f6b26727570f3bcceb6c8fa789da64fe6889908fe2e294d57503b14bf2b5af7605c2d36647259e856635cd4c49eab41667658ec9d0080ec3f + languageName: node + linkType: hard + +"stop-iteration-iterator@npm:^1.0.0, stop-iteration-iterator@npm:^1.1.0": + version: 1.1.0 + resolution: "stop-iteration-iterator@npm:1.1.0" + dependencies: + es-errors: "npm:^1.3.0" + internal-slot: "npm:^1.1.0" + checksum: 10c0/de4e45706bb4c0354a4b1122a2b8cc45a639e86206807ce0baf390ee9218d3ef181923fa4d2b67443367c491aa255c5fbaa64bb74648e3c5b48299928af86c09 + languageName: node + linkType: hard + +"stream-browserify@npm:^2.0.1": + version: 2.0.2 + resolution: "stream-browserify@npm:2.0.2" + dependencies: + inherits: "npm:~2.0.1" + readable-stream: "npm:^2.0.2" + checksum: 10c0/485562bd5d962d633ae178449029c6fa2611052e356bdb5668f768544aa4daa94c4f9a97de718f3f30ad98f3cb98a5f396252bb3855aff153c138f79c0e8f6ac + languageName: node + linkType: hard + +"stream-http@npm:^2.7.2": + version: 2.8.3 + resolution: "stream-http@npm:2.8.3" + dependencies: + builtin-status-codes: "npm:^3.0.0" + inherits: "npm:^2.0.1" + readable-stream: "npm:^2.3.6" + to-arraybuffer: "npm:^1.0.0" + xtend: "npm:^4.0.0" + checksum: 10c0/fbe7d327a29216bbabe88d3819bb8f7a502f11eeacf3212579e5af1f76fa7283f6ffa66134ab7d80928070051f571d1029e85f65ce3369fffd4c4df3669446c4 + languageName: node + linkType: hard + +"stream-shift@npm:^1.0.2": + version: 1.0.3 + resolution: "stream-shift@npm:1.0.3" + checksum: 10c0/939cd1051ca750d240a0625b106a2b988c45fb5a3be0cebe9a9858cb01bc1955e8c7b9fac17a9462976bea4a7b704e317c5c2200c70f0ca715a3363b9aa4fd3b + languageName: node + linkType: hard + +"strict-uri-encode@npm:^2.0.0": + version: 2.0.0 + resolution: "strict-uri-encode@npm:2.0.0" + checksum: 10c0/010cbc78da0e2cf833b0f5dc769e21ae74cdc5d5f5bd555f14a4a4876c8ad2c85ab8b5bdf9a722dc71a11dcd3184085e1c3c0bd50ec6bb85fffc0f28cf82597d + languageName: node + linkType: hard + +"string-convert@npm:^0.2.0": + version: 0.2.1 + resolution: "string-convert@npm:0.2.1" + checksum: 10c0/00673ed8a3106137395436537ace7d3672c91a3290da73466055daa0134331dc84bc58c54ba2d2ea40711adc5744426d3c8239dbfc30290438fa3e9ff65db528 + languageName: node + linkType: hard + +"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": + version: 4.2.3 + resolution: "string-width@npm:4.2.3" + dependencies: + emoji-regex: "npm:^8.0.0" + is-fullwidth-code-point: "npm:^3.0.0" + strip-ansi: "npm:^6.0.1" + checksum: 10c0/1e525e92e5eae0afd7454086eed9c818ee84374bb80328fc41217ae72ff5f065ef1c9d7f72da41de40c75fa8bb3dee63d92373fd492c84260a552c636392a47b + languageName: node + linkType: hard + +"string-width@npm:^5.0.1, string-width@npm:^5.1.2": + version: 5.1.2 + resolution: "string-width@npm:5.1.2" + dependencies: + eastasianwidth: "npm:^0.2.0" + emoji-regex: "npm:^9.2.2" + strip-ansi: "npm:^7.0.1" + checksum: 10c0/ab9c4264443d35b8b923cbdd513a089a60de339216d3b0ed3be3ba57d6880e1a192b70ae17225f764d7adbf5994e9bb8df253a944736c15a0240eff553c678ca + languageName: node + linkType: hard + +"string-width@npm:^8.2.0": + version: 8.2.0 + resolution: "string-width@npm:8.2.0" + dependencies: + get-east-asian-width: "npm:^1.5.0" + strip-ansi: "npm:^7.1.2" + checksum: 10c0/d8915428b43519b0f494da6590dbe4491857d8a12e40250e50fc01fbb616ffd8400a436bbe25712255ee129511fe0414c49d3b6b9627e2bc3a33dcec1d2eda02 + languageName: node + linkType: hard + +"string.prototype.matchall@npm:^4.0.12, string.prototype.matchall@npm:^4.0.8": + version: 4.0.12 + resolution: "string.prototype.matchall@npm:4.0.12" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.3" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.6" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.0.0" + get-intrinsic: "npm:^1.2.6" + gopd: "npm:^1.2.0" + has-symbols: "npm:^1.1.0" + internal-slot: "npm:^1.1.0" + regexp.prototype.flags: "npm:^1.5.3" + set-function-name: "npm:^2.0.2" + side-channel: "npm:^1.1.0" + checksum: 10c0/1a53328ada73f4a77f1fdf1c79414700cf718d0a8ef6672af5603e709d26a24f2181208144aed7e858b1bcc1a0d08567a570abfb45567db4ae47637ed2c2f85c + languageName: node + linkType: hard + +"string.prototype.repeat@npm:^1.0.0": + version: 1.0.0 + resolution: "string.prototype.repeat@npm:1.0.0" + dependencies: + define-properties: "npm:^1.1.3" + es-abstract: "npm:^1.17.5" + checksum: 10c0/94c7978566cffa1327d470fd924366438af9b04b497c43a9805e476e2e908aa37a1fd34cc0911156c17556dab62159d12c7b92b3cc304c3e1281fe4c8e668f40 + languageName: node + linkType: hard + +"string.prototype.trim@npm:^1.2.10": + version: 1.2.10 + resolution: "string.prototype.trim@npm:1.2.10" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.2" + define-data-property: "npm:^1.1.4" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.5" + es-object-atoms: "npm:^1.0.0" + has-property-descriptors: "npm:^1.0.2" + checksum: 10c0/8a8854241c4b54a948e992eb7dd6b8b3a97185112deb0037a134f5ba57541d8248dd610c966311887b6c2fd1181a3877bffb14d873ce937a344535dabcc648f8 + languageName: node + linkType: hard + +"string.prototype.trimend@npm:^1.0.9": + version: 1.0.9 + resolution: "string.prototype.trimend@npm:1.0.9" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.2" + define-properties: "npm:^1.2.1" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/59e1a70bf9414cb4c536a6e31bef5553c8ceb0cf44d8b4d0ed65c9653358d1c64dd0ec203b100df83d0413bbcde38b8c5d49e14bc4b86737d74adc593a0d35b6 + languageName: node + linkType: hard + +"string.prototype.trimstart@npm:^1.0.8": + version: 1.0.8 + resolution: "string.prototype.trimstart@npm:1.0.8" + dependencies: + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/d53af1899959e53c83b64a5fd120be93e067da740e7e75acb433849aa640782fb6c7d4cd5b84c954c84413745a3764df135a8afeb22908b86a835290788d8366 + languageName: node + linkType: hard + +"string_decoder-okam@npm:^1.0.0": + version: 1.3.0 + resolution: "string_decoder-okam@npm:1.3.0" + dependencies: + safe-buffer: "npm:~5.2.0" + checksum: 10c0/a8da256536f315bfaa1eacb97a83213a21a270c299ede5b9c61c5f80f351f23e322da6720fe00cfd7367187bc5150206ab8c0debc37786384edfd28fb1fdee4d + languageName: node + linkType: hard + +"string_decoder@npm:^1.0.0, string_decoder@npm:^1.1.1": + version: 1.3.0 + resolution: "string_decoder@npm:1.3.0" + dependencies: + safe-buffer: "npm:~5.2.0" + checksum: 10c0/810614ddb030e271cd591935dcd5956b2410dd079d64ff92a1844d6b7588bf992b3e1b69b0f4d34a3e06e0bd73046ac646b5264c1987b20d0601f81ef35d731d + languageName: node + linkType: hard + +"string_decoder@npm:~1.1.1": + version: 1.1.1 + resolution: "string_decoder@npm:1.1.1" + dependencies: + safe-buffer: "npm:~5.1.0" + checksum: 10c0/b4f89f3a92fd101b5653ca3c99550e07bdf9e13b35037e9e2a1c7b47cec4e55e06ff3fc468e314a0b5e80bfbaf65c1ca5a84978764884ae9413bec1fc6ca924e + languageName: node + linkType: hard + +"strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": + version: 6.0.1 + resolution: "strip-ansi@npm:6.0.1" + dependencies: + ansi-regex: "npm:^5.0.1" + checksum: 10c0/1ae5f212a126fe5b167707f716942490e3933085a5ff6c008ab97ab2f272c8025d3aa218b7bd6ab25729ca20cc81cddb252102f8751e13482a5199e873680952 + languageName: node + linkType: hard + +"strip-ansi@npm:^7.0.1": + version: 7.1.2 + resolution: "strip-ansi@npm:7.1.2" + dependencies: + ansi-regex: "npm:^6.0.1" + checksum: 10c0/0d6d7a023de33368fd042aab0bf48f4f4077abdfd60e5393e73c7c411e85e1b3a83507c11af2e656188511475776215df9ca589b4da2295c9455cc399ce1858b + languageName: node + linkType: hard + +"strip-ansi@npm:^7.1.2": + version: 7.2.0 + resolution: "strip-ansi@npm:7.2.0" + dependencies: + ansi-regex: "npm:^6.2.2" + checksum: 10c0/544d13b7582f8254811ea97db202f519e189e59d35740c46095897e254e4f1aa9fe1524a83ad6bc5ad67d4dd6c0281d2e0219ed62b880a6238a16a17d375f221 + languageName: node + linkType: hard + +"strip-bom@npm:^3.0.0": + version: 3.0.0 + resolution: "strip-bom@npm:3.0.0" + checksum: 10c0/51201f50e021ef16672593d7434ca239441b7b760e905d9f33df6e4f3954ff54ec0e0a06f100d028af0982d6f25c35cd5cda2ce34eaebccd0250b8befb90d8f1 + languageName: node + linkType: hard + +"strip-final-newline@npm:^2.0.0": + version: 2.0.0 + resolution: "strip-final-newline@npm:2.0.0" + checksum: 10c0/bddf8ccd47acd85c0e09ad7375409d81653f645fda13227a9d459642277c253d877b68f2e5e4d819fe75733b0e626bac7e954c04f3236f6d196f79c94fa4a96f + languageName: node + linkType: hard + +"strip-final-newline@npm:^3.0.0": + version: 3.0.0 + resolution: "strip-final-newline@npm:3.0.0" + checksum: 10c0/a771a17901427bac6293fd416db7577e2bc1c34a19d38351e9d5478c3c415f523f391003b42ed475f27e33a78233035df183525395f731d3bfb8cdcbd4da08ce + languageName: node + linkType: hard + +"strip-json-comments@npm:^3.1.1": + version: 3.1.1 + resolution: "strip-json-comments@npm:3.1.1" + checksum: 10c0/9681a6257b925a7fa0f285851c0e613cc934a50661fa7bb41ca9cbbff89686bb4a0ee366e6ecedc4daafd01e83eee0720111ab294366fe7c185e935475ebcecd + languageName: node + linkType: hard + +"style-to-object@npm:^0.4.0": + version: 0.4.4 + resolution: "style-to-object@npm:0.4.4" + dependencies: + inline-style-parser: "npm:0.1.1" + checksum: 10c0/3a733080da66952881175b17d65f92985cf94c1ca358a92cf21b114b1260d49b94a404ed79476047fb95698d64c7e366ca7443f0225939e2fb34c38bbc9c7639 + languageName: node + linkType: hard + +"styled-components@npm:6.1.1": + version: 6.1.1 + resolution: "styled-components@npm:6.1.1" + dependencies: + "@emotion/is-prop-valid": "npm:^1.2.1" + "@emotion/unitless": "npm:^0.8.0" + "@types/stylis": "npm:^4.0.2" + css-to-react-native: "npm:^3.2.0" + csstype: "npm:^3.1.2" + postcss: "npm:^8.4.31" + shallowequal: "npm:^1.1.0" + stylis: "npm:^4.3.0" + tslib: "npm:^2.5.0" + peerDependencies: + react: ">= 16.8.0" + react-dom: ">= 16.8.0" + checksum: 10c0/de8fe762fed9ad75b09db0ec7235623ba18e8812bf6fc133c259f933b0bebb658ca94a7e6b1ce1a7a165c44aaaa0c5a5f24965b8fadb52551fa47c1112e52f85 + languageName: node + linkType: hard + +"styled-components@npm:^3.4.10 || ^5.0.1": + version: 5.3.11 + resolution: "styled-components@npm:5.3.11" + dependencies: + "@babel/helper-module-imports": "npm:^7.0.0" + "@babel/traverse": "npm:^7.4.5" + "@emotion/is-prop-valid": "npm:^1.1.0" + "@emotion/stylis": "npm:^0.8.4" + "@emotion/unitless": "npm:^0.7.4" + babel-plugin-styled-components: "npm:>= 1.12.0" + css-to-react-native: "npm:^3.0.0" + hoist-non-react-statics: "npm:^3.0.0" + shallowequal: "npm:^1.1.0" + supports-color: "npm:^5.5.0" + peerDependencies: + react: ">= 16.8.0" + react-dom: ">= 16.8.0" + react-is: ">= 16.8.0" + checksum: 10c0/90b73479770c5d68e22e6366d210119d7203154a3e49dc828f6f6b4c2d5c077f7548210dfddd0af3cb15b0b63fab3eec8dc995c1734e97a313a9b83ba893668e + languageName: node + linkType: hard + +"styled-components@npm:^6.0.1": + version: 6.1.19 + resolution: "styled-components@npm:6.1.19" + dependencies: + "@emotion/is-prop-valid": "npm:1.2.2" + "@emotion/unitless": "npm:0.8.1" + "@types/stylis": "npm:4.2.5" + css-to-react-native: "npm:3.2.0" + csstype: "npm:3.1.3" + postcss: "npm:8.4.49" + shallowequal: "npm:1.1.0" + stylis: "npm:4.3.2" + tslib: "npm:2.6.2" + peerDependencies: + react: ">= 16.8.0" + react-dom: ">= 16.8.0" + checksum: 10c0/8d20427a5debe54bfa3b55f79af2a3577551ed7f1d1cd34df986b73fd01ac519f9081b7737cc1f76e12fbc483fa50551e55be0bc984296e623cc6a2364697cd8 + languageName: node + linkType: hard + +"stylelint-config-recommended@npm:^18.0.0": + version: 18.0.0 + resolution: "stylelint-config-recommended@npm:18.0.0" + peerDependencies: + stylelint: ^17.0.0 + checksum: 10c0/c7f8ff45c76ec23f4c8c0438894726976fd5e872c59d489f959b728d9879bba20dbf0040cd29ad3bbc00eb32befd95f5b6ca150002bb8aea74b0797bc42ccc17 + languageName: node + linkType: hard + +"stylelint-config-recommended@npm:^7.0.0": + version: 7.0.0 + resolution: "stylelint-config-recommended@npm:7.0.0" + peerDependencies: + stylelint: ^14.4.0 + checksum: 10c0/11ec1d5721143fa35451b172ced61660f0f0204954273f59a06d5439075d48414f8257aa3cfc474116bcd56ca42c1485fc3bcf550ef39e5a72cbfdd4ac32acc6 + languageName: node + linkType: hard + +"stylelint-config-standard@npm:25.0.0": + version: 25.0.0 + resolution: "stylelint-config-standard@npm:25.0.0" + dependencies: + stylelint-config-recommended: "npm:^7.0.0" + peerDependencies: + stylelint: ^14.4.0 + checksum: 10c0/fbc1d75a37f3dbd0e93fa14559fe5c1cb6b414369942c1cedb7dd3faefebeaf43fab97f125f7769326c928f4939f2d3c87243c8da90c5c857dbc7fd8ed61ffa7 + languageName: node + linkType: hard + +"stylelint-config-standard@npm:^40.0.0": + version: 40.0.0 + resolution: "stylelint-config-standard@npm:40.0.0" + dependencies: + stylelint-config-recommended: "npm:^18.0.0" + peerDependencies: + stylelint: ^17.0.0 + checksum: 10c0/d8942552d53a3afda59b64d0c49503bb626fe5cef39a9e8c9583fcd60869f21431125ef4480ff27a59f7f2cf0da8af810d377129ef1d670ddc5def4defe2880c + languageName: node + linkType: hard + +"stylelint@npm:^17.8.0": + version: 17.8.0 + resolution: "stylelint@npm:17.8.0" + dependencies: + "@csstools/css-calc": "npm:^3.1.1" + "@csstools/css-parser-algorithms": "npm:^4.0.0" + "@csstools/css-syntax-patches-for-csstree": "npm:^1.1.2" + "@csstools/css-tokenizer": "npm:^4.0.0" + "@csstools/media-query-list-parser": "npm:^5.0.0" + "@csstools/selector-resolve-nested": "npm:^4.0.0" + "@csstools/selector-specificity": "npm:^6.0.0" + colord: "npm:^2.9.3" + cosmiconfig: "npm:^9.0.1" + css-functions-list: "npm:^3.3.3" + css-tree: "npm:^3.2.1" + debug: "npm:^4.4.3" + fast-glob: "npm:^3.3.3" + fastest-levenshtein: "npm:^1.0.16" + file-entry-cache: "npm:^11.1.2" + global-modules: "npm:^2.0.0" + globby: "npm:^16.2.0" + globjoin: "npm:^0.1.4" + html-tags: "npm:^5.1.0" + ignore: "npm:^7.0.5" + import-meta-resolve: "npm:^4.2.0" + is-plain-object: "npm:^5.0.0" + mathml-tag-names: "npm:^4.0.0" + meow: "npm:^14.1.0" + micromatch: "npm:^4.0.8" + normalize-path: "npm:^3.0.0" + picocolors: "npm:^1.1.1" + postcss: "npm:^8.5.9" + postcss-safe-parser: "npm:^7.0.1" + postcss-selector-parser: "npm:^7.1.1" + postcss-value-parser: "npm:^4.2.0" + string-width: "npm:^8.2.0" + supports-hyperlinks: "npm:^4.4.0" + svg-tags: "npm:^1.0.0" + table: "npm:^6.9.0" + write-file-atomic: "npm:^7.0.1" + bin: + stylelint: bin/stylelint.mjs + checksum: 10c0/de822c902944ed094afabb48802e5659ef479e7707cc0a789e11e833fff5d54fb7a82b83052df83882f77de00190c109dbacd0b2d87157857c3ed626eb0fb4d5 + languageName: node + linkType: hard + +"stylis@npm:4.3.2": + version: 4.3.2 + resolution: "stylis@npm:4.3.2" + checksum: 10c0/0410e1404cbeee3388a9e17587875211ce2f014c8379af0d1e24ca55878867c9f1ccc7b0ce9a156ca53f5d6e301391a82b0645522a604674a378b3189a4a1994 + languageName: node + linkType: hard + +"stylis@npm:^4.3.0, stylis@npm:^4.3.4": + version: 4.3.6 + resolution: "stylis@npm:4.3.6" + checksum: 10c0/e736d484983a34f7c65d362c67dc79b7bce388054b261c2b7b23d02eaaf280617033f65d44b1ea341854f4331a5074b885668ac8741f98c13a6cfd6443ae85d0 + languageName: node + linkType: hard + +"sucrase@npm:^3.35.0": + version: 3.35.1 + resolution: "sucrase@npm:3.35.1" + dependencies: + "@jridgewell/gen-mapping": "npm:^0.3.2" + commander: "npm:^4.0.0" + lines-and-columns: "npm:^1.1.6" + mz: "npm:^2.7.0" + pirates: "npm:^4.0.1" + tinyglobby: "npm:^0.2.11" + ts-interface-checker: "npm:^0.1.9" + bin: + sucrase: bin/sucrase + sucrase-node: bin/sucrase-node + checksum: 10c0/6fa22329c261371feb9560630d961ad0d0b9c87dce21ea74557c5f3ffbe5c1ee970ea8bcce9962ae9c90c3c47165ffa7dd41865c7414f5d8ea7a40755d612c5c + languageName: node + linkType: hard + +"sumchecker@npm:^3.0.1": + version: 3.0.1 + resolution: "sumchecker@npm:3.0.1" + dependencies: + debug: "npm:^4.1.0" + checksum: 10c0/43c387be9dfe22dbeaf39dfa4ffb279847aeb37a42a8988c0b066f548bbd209aa8c65e03da29f2b29be1a66b577801bf89fff0007df4183db2f286263a9569e5 + languageName: node + linkType: hard + +"superjson@npm:^1.10.0": + version: 1.13.3 + resolution: "superjson@npm:1.13.3" + dependencies: + copy-anything: "npm:^3.0.2" + checksum: 10c0/389a0a0c86884dd0558361af5d6d7f37102b71dda9595a665fe8b39d1ba0e57c859e39a9bd79b6f1fde6f4dcceac49a1c205f248d292744b2a340ee52846efdb + languageName: node + linkType: hard + +"supports-color@npm:^10.2.2": + version: 10.2.2 + resolution: "supports-color@npm:10.2.2" + checksum: 10c0/fb28dd7e0cdf80afb3f2a41df5e068d60c8b4f97f7140de2eaed5b42e075d82a0e980b20a2c0efd2b6d73cfacb55555285d8cc719fa0472220715aefeaa1da7c + languageName: node + linkType: hard + +"supports-color@npm:^5.3.0, supports-color@npm:^5.5.0": + version: 5.5.0 + resolution: "supports-color@npm:5.5.0" + dependencies: + has-flag: "npm:^3.0.0" + checksum: 10c0/6ae5ff319bfbb021f8a86da8ea1f8db52fac8bd4d499492e30ec17095b58af11f0c55f8577390a749b1c4dde691b6a0315dab78f5f54c9b3d83f8fb5905c1c05 + languageName: node + linkType: hard + +"supports-color@npm:^7.1.0": + version: 7.2.0 + resolution: "supports-color@npm:7.2.0" + dependencies: + has-flag: "npm:^4.0.0" + checksum: 10c0/afb4c88521b8b136b5f5f95160c98dee7243dc79d5432db7efc27efb219385bbc7d9427398e43dd6cc730a0f87d5085ce1652af7efbe391327bc0a7d0f7fc124 + languageName: node + linkType: hard + +"supports-color@npm:^8.0.0, supports-color@npm:^8.1.1": + version: 8.1.1 + resolution: "supports-color@npm:8.1.1" + dependencies: + has-flag: "npm:^4.0.0" + checksum: 10c0/ea1d3c275dd604c974670f63943ed9bd83623edc102430c05adb8efc56ba492746b6e95386e7831b872ec3807fd89dd8eb43f735195f37b5ec343e4234cc7e89 + languageName: node + linkType: hard + +"supports-hyperlinks@npm:^4.4.0": + version: 4.4.0 + resolution: "supports-hyperlinks@npm:4.4.0" + dependencies: + has-flag: "npm:^5.0.1" + supports-color: "npm:^10.2.2" + checksum: 10c0/1172347b736e52f012506e162d5782d5fe9c64e22d286bd6daffbd182ca5696cc341fb0f66a550e2b96b5cdf31fc5217b009c6b92150843585debc6d8f1697bd + languageName: node + linkType: hard + +"supports-preserve-symlinks-flag@npm:^1.0.0": + version: 1.0.0 + resolution: "supports-preserve-symlinks-flag@npm:1.0.0" + checksum: 10c0/6c4032340701a9950865f7ae8ef38578d8d7053f5e10518076e6554a9381fa91bd9c6850193695c141f32b21f979c985db07265a758867bac95de05f7d8aeb39 + languageName: node + linkType: hard + +"svg-parser@npm:^2.0.4": + version: 2.0.4 + resolution: "svg-parser@npm:2.0.4" + checksum: 10c0/02f6cb155dd7b63ebc2f44f36365bc294543bebb81b614b7628f1af3c54ab64f7e1cec20f06e252bf95bdde78441ae295a412c68ad1678f16a6907d924512b7a + languageName: node + linkType: hard + +"svg-tags@npm:^1.0.0": + version: 1.0.0 + resolution: "svg-tags@npm:1.0.0" + checksum: 10c0/5867e29e8f431bf7aecf5a244d1af5725f80a1086187dbc78f26d8433b5e96b8fe9361aeb10d1699ff483b9afec785a10916b9312fe9d734d1a7afd48226c954 + languageName: node + linkType: hard + +"svgo@npm:^2.8.0": + version: 2.8.0 + resolution: "svgo@npm:2.8.0" + dependencies: + "@trysound/sax": "npm:0.2.0" + commander: "npm:^7.2.0" + css-select: "npm:^4.1.3" + css-tree: "npm:^1.1.3" + csso: "npm:^4.2.0" + picocolors: "npm:^1.0.0" + stable: "npm:^0.1.8" + bin: + svgo: bin/svgo + checksum: 10c0/0741f5d5cad63111a90a0ce7a1a5a9013f6d293e871b75efe39addb57f29a263e45294e485a4d2ff9cc260a5d142c8b5937b2234b4ef05efdd2706fb2d360ecc + languageName: node + linkType: hard + +"swr@npm:^2.0.0": + version: 2.3.6 + resolution: "swr@npm:2.3.6" + dependencies: + dequal: "npm:^2.0.3" + use-sync-external-store: "npm:^1.4.0" + peerDependencies: + react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10c0/9534f350982e36a3ae0a13da8c0f7da7011fc979e77f306e60c4e5db0f9b84f17172c44f973441ba56bb684b69b0d9838ab40011a6b6b3e32d0cd7f3d5405f99 + languageName: node + linkType: hard + +"synckit@npm:0.11.11, synckit@npm:^0.11.7": + version: 0.11.11 + resolution: "synckit@npm:0.11.11" + dependencies: + "@pkgr/core": "npm:^0.2.9" + checksum: 10c0/f0761495953d12d94a86edf6326b3a565496c72f9b94c02549b6961fb4d999f4ca316ce6b3eb8ed2e4bfc5056a8de65cda0bd03a233333a35221cd2fdc0e196b + languageName: node + linkType: hard + +"synckit@npm:0.8.5": + version: 0.8.5 + resolution: "synckit@npm:0.8.5" + dependencies: + "@pkgr/utils": "npm:^2.3.1" + tslib: "npm:^2.5.0" + checksum: 10c0/9827f828cabc404b3a147c38f824c8d5b846eb6f65189d965aa0b71ea8ecda5048f8f50b4bdfd8813148844175233cff56c6bc8d87a7118cf10707df870519f4 + languageName: node + linkType: hard + +"systemjs@npm:^6.14.1": + version: 6.15.1 + resolution: "systemjs@npm:6.15.1" + checksum: 10c0/106e5751a49dbe4acb17fa1474a43b27fd26efbee1b322c00c04c08f3e95de756adfba828d743af89bef7fa10888da8a5c5ceb55dae5c42e4909b151168ad192 + languageName: node + linkType: hard + +"table@npm:^6.9.0": + version: 6.9.0 + resolution: "table@npm:6.9.0" + dependencies: + ajv: "npm:^8.0.1" + lodash.truncate: "npm:^4.4.2" + slice-ansi: "npm:^4.0.0" + string-width: "npm:^4.2.3" + strip-ansi: "npm:^6.0.1" + checksum: 10c0/35646185712bb65985fbae5975dda46696325844b78735f95faefae83e86df0a265277819a3e67d189de6e858c509b54e66ca3958ffd51bde56ef1118d455bf4 + languageName: node + linkType: hard + +"tailwindcss@npm:^3": + version: 3.4.18 + resolution: "tailwindcss@npm:3.4.18" + dependencies: + "@alloc/quick-lru": "npm:^5.2.0" + arg: "npm:^5.0.2" + chokidar: "npm:^3.6.0" + didyoumean: "npm:^1.2.2" + dlv: "npm:^1.1.3" + fast-glob: "npm:^3.3.2" + glob-parent: "npm:^6.0.2" + is-glob: "npm:^4.0.3" + jiti: "npm:^1.21.7" + lilconfig: "npm:^3.1.3" + micromatch: "npm:^4.0.8" + normalize-path: "npm:^3.0.0" + object-hash: "npm:^3.0.0" + picocolors: "npm:^1.1.1" + postcss: "npm:^8.4.47" + postcss-import: "npm:^15.1.0" + postcss-js: "npm:^4.0.1" + postcss-load-config: "npm:^4.0.2 || ^5.0 || ^6.0" + postcss-nested: "npm:^6.2.0" + postcss-selector-parser: "npm:^6.1.2" + resolve: "npm:^1.22.8" + sucrase: "npm:^3.35.0" + bin: + tailwind: lib/cli.js + tailwindcss: lib/cli.js + checksum: 10c0/230c0815d0b981f4952d1902e025d7571929e5fc133b4bb4fcbbc3b642e7ab0cecb9687f80f311afd0db07df8f383ce4317b3ca75ae93156c2ddc777e59fc31b + languageName: node + linkType: hard + +"tapable@npm:^0.1.8": + version: 0.1.10 + resolution: "tapable@npm:0.1.10" + checksum: 10c0/a85d8b6068e5925384ab7d567222f59221f0b5156769d8c16ddfc7258624be20813c8fcd8a2096a125485fa9f761582ec82d146165da6eb481acf128bb2899ed + languageName: node + linkType: hard + +"tapable@npm:^2.0.0, tapable@npm:^2.2.0, tapable@npm:^2.2.1": + version: 2.3.0 + resolution: "tapable@npm:2.3.0" + checksum: 10c0/cb9d67cc2c6a74dedc812ef3085d9d681edd2c1fa18e4aef57a3c0605fdbe44e6b8ea00bd9ef21bc74dd45314e39d31227aa031ebf2f5e38164df514136f2681 + languageName: node + linkType: hard + +"tar@npm:^6.1.11": + version: 6.2.1 + resolution: "tar@npm:6.2.1" + dependencies: + chownr: "npm:^2.0.0" + fs-minipass: "npm:^2.0.0" + minipass: "npm:^5.0.0" + minizlib: "npm:^2.1.1" + mkdirp: "npm:^1.0.3" + yallist: "npm:^4.0.0" + checksum: 10c0/a5eca3eb50bc11552d453488344e6507156b9193efd7635e98e867fab275d527af53d8866e2370cd09dfe74378a18111622ace35af6a608e5223a7d27fe99537 + languageName: node + linkType: hard + +"tar@npm:^7.5.2": + version: 7.5.2 + resolution: "tar@npm:7.5.2" + dependencies: + "@isaacs/fs-minipass": "npm:^4.0.0" + chownr: "npm:^3.0.0" + minipass: "npm:^7.1.2" + minizlib: "npm:^3.1.0" + yallist: "npm:^5.0.0" + checksum: 10c0/a7d8b801139b52f93a7e34830db0de54c5aa45487c7cb551f6f3d44a112c67f1cb8ffdae856b05fd4f17b1749911f1c26f1e3a23bbe0279e17fd96077f13f467 + languageName: node + linkType: hard + +"temp-file@npm:^3.4.0": + version: 3.4.0 + resolution: "temp-file@npm:3.4.0" + dependencies: + async-exit-hook: "npm:^2.0.1" + fs-extra: "npm:^10.0.0" + checksum: 10c0/70e441909097346a930ae02278df9b0133cd02dddf0b49e5ddaade735fef1410a50a448a2a812106f97c045294c99cc19f26943eb88f1d728d41fbc445a40298 + languageName: node + linkType: hard + +"terser@npm:^5.10.0": + version: 5.44.1 + resolution: "terser@npm:5.44.1" + dependencies: + "@jridgewell/source-map": "npm:^0.3.3" + acorn: "npm:^8.15.0" + commander: "npm:^2.20.0" + source-map-support: "npm:~0.5.20" + bin: + terser: bin/terser + checksum: 10c0/ee7a76692cb39b1ed22c30ff366c33ff3c977d9bb769575338ff5664676168fcba59192fb5168ef80c7cd901ef5411a1b0351261f5eaa50decf0fc71f63bde75 + languageName: node + linkType: hard + +"test-exclude@npm:^6.0.0": + version: 6.0.0 + resolution: "test-exclude@npm:6.0.0" + dependencies: + "@istanbuljs/schema": "npm:^0.1.2" + glob: "npm:^7.1.4" + minimatch: "npm:^3.0.4" + checksum: 10c0/019d33d81adff3f9f1bfcff18125fb2d3c65564f437d9be539270ee74b994986abb8260c7c2ce90e8f30162178b09dbbce33c6389273afac4f36069c48521f57 + languageName: node + linkType: hard + +"text-table@npm:^0.2.0": + version: 0.2.0 + resolution: "text-table@npm:0.2.0" + checksum: 10c0/02805740c12851ea5982686810702e2f14369a5f4c5c40a836821e3eefc65ffeec3131ba324692a37608294b0fd8c1e55a2dd571ffed4909822787668ddbee5c + languageName: node + linkType: hard + +"thenify-all@npm:^1.0.0": + version: 1.6.0 + resolution: "thenify-all@npm:1.6.0" + dependencies: + thenify: "npm:>= 3.1.0 < 4" + checksum: 10c0/9b896a22735e8122754fe70f1d65f7ee691c1d70b1f116fda04fea103d0f9b356e3676cb789506e3909ae0486a79a476e4914b0f92472c2e093d206aed4b7d6b + languageName: node + linkType: hard + +"thenify@npm:>= 3.1.0 < 4": + version: 3.3.1 + resolution: "thenify@npm:3.3.1" + dependencies: + any-promise: "npm:^1.0.0" + checksum: 10c0/f375aeb2b05c100a456a30bc3ed07ef03a39cbdefe02e0403fb714b8c7e57eeaad1a2f5c4ecfb9ce554ce3db9c2b024eba144843cd9e344566d9fcee73b04767 + languageName: node + linkType: hard + +"thread-stream@npm:^0.15.1": + version: 0.15.2 + resolution: "thread-stream@npm:0.15.2" + dependencies: + real-require: "npm:^0.1.0" + checksum: 10c0/f92f1b5a9f3f35a72c374e3fecbde6f14d69d5325ad9ce88930af6ed9c7c1ec814367716b712205fa4f06242ae5dd97321ae2c00b43586590ed4fa861f3c29ae + languageName: node + linkType: hard + +"throttle-debounce@npm:^5.0.0, throttle-debounce@npm:^5.0.2": + version: 5.0.2 + resolution: "throttle-debounce@npm:5.0.2" + checksum: 10c0/9a10ac51400b353562770721718486847adb5d7287c94a0c0d47df5326e8d47e5d92fcb74dac53d6734efb9344a2d46d68c7f996c2d0aedfd11446522e4bb356 + languageName: node + linkType: hard + +"timers-browserify@npm:^2.0.4": + version: 2.0.12 + resolution: "timers-browserify@npm:2.0.12" + dependencies: + setimmediate: "npm:^1.0.4" + checksum: 10c0/98e84db1a685bc8827c117a8bc62aac811ad56a995d07938fc7ed8cdc5bf3777bfe2d4e5da868847194e771aac3749a20f6cdd22091300fe889a76fe214a4641 + languageName: node + linkType: hard + +"tiny-invariant@npm:1.2.0": + version: 1.2.0 + resolution: "tiny-invariant@npm:1.2.0" + checksum: 10c0/a7dd29c5256fdc4901e3adadaa203da62bd23c6a79830f7aa99ea2df5e2e82f84051550dcafb82af18b2d61d75dcc17993f01f938e9ad8f20cf4c514fff88d47 + languageName: node + linkType: hard + +"tinycolor2@npm:^1.4.2": + version: 1.6.0 + resolution: "tinycolor2@npm:1.6.0" + checksum: 10c0/9aa79a36ba2c2a87cb221453465cabacd04b9e35f9694373e846fdc78b1c768110f81e581ea41440106c0f24d9a023891d0887e8075885e790ac40eb0e74a5c1 + languageName: node + linkType: hard + +"tinyglobby@npm:^0.2.11, tinyglobby@npm:^0.2.12": + version: 0.2.15 + resolution: "tinyglobby@npm:0.2.15" + dependencies: + fdir: "npm:^6.5.0" + picomatch: "npm:^4.0.3" + checksum: 10c0/869c31490d0d88eedb8305d178d4c75e7463e820df5a9b9d388291daf93e8b1eb5de1dad1c1e139767e4269fe75f3b10d5009b2cc14db96ff98986920a186844 + languageName: node + linkType: hard + +"titleize@npm:^3.0.0": + version: 3.0.0 + resolution: "titleize@npm:3.0.0" + checksum: 10c0/5ae6084ba299b5782f95e3fe85ea9f0fa4d74b8ae722b6b3208157e975589fbb27733aeba4e5080fa9314a856044ef52caa61b87caea4b1baade951a55c06336 + languageName: node + linkType: hard + +"tmp-promise@npm:^3.0.2": + version: 3.0.3 + resolution: "tmp-promise@npm:3.0.3" + dependencies: + tmp: "npm:^0.2.0" + checksum: 10c0/23b47dcb2e82b14bbd8f61ed7a9d9353cdb6a6f09d7716616cfd27d0087040cd40152965a518e598d7aabe1489b9569bf1eebde0c5fadeaf3ec8098adcebea4e + languageName: node + linkType: hard + +"tmp@npm:^0.2.0": + version: 0.2.5 + resolution: "tmp@npm:0.2.5" + checksum: 10c0/cee5bb7d674bb4ba3ab3f3841c2ca7e46daeb2109eec395c1ec7329a91d52fcb21032b79ac25161a37b2565c4858fefab927af9735926a113ef7bac9091a6e0e + languageName: node + linkType: hard + +"tmpl@npm:1.0.5": + version: 1.0.5 + resolution: "tmpl@npm:1.0.5" + checksum: 10c0/f935537799c2d1922cb5d6d3805f594388f75338fe7a4a9dac41504dd539704ca4db45b883b52e7b0aa5b2fd5ddadb1452bf95cd23a69da2f793a843f9451cc9 + languageName: node + linkType: hard + +"to-arraybuffer@npm:^1.0.0": + version: 1.0.1 + resolution: "to-arraybuffer@npm:1.0.1" + checksum: 10c0/2460bd95524f4845a751e4f8bf9937f9f3dcd1651f104e1512868782f858f8302c1cf25bbc30794bc1b3ff65c4e135158377302f2abaff43a2d8e3c38dfe098c + languageName: node + linkType: hard + +"to-buffer@npm:^1.2.0, to-buffer@npm:^1.2.1, to-buffer@npm:^1.2.2": + version: 1.2.2 + resolution: "to-buffer@npm:1.2.2" + dependencies: + isarray: "npm:^2.0.5" + safe-buffer: "npm:^5.2.1" + typed-array-buffer: "npm:^1.0.3" + checksum: 10c0/56bc56352f14a2c4a0ab6277c5fc19b51e9534882b98eb068b39e14146591e62fa5b06bf70f7fed1626230463d7e60dca81e815096656e5e01c195c593873d12 + languageName: node + linkType: hard + +"to-regex-range@npm:^5.0.1": + version: 5.0.1 + resolution: "to-regex-range@npm:5.0.1" + dependencies: + is-number: "npm:^7.0.0" + checksum: 10c0/487988b0a19c654ff3e1961b87f471702e708fa8a8dd02a298ef16da7206692e8552a0250e8b3e8759270f62e9d8314616f6da274734d3b558b1fc7b7724e892 + languageName: node + linkType: hard + +"toggle-selection@npm:^1.0.6": + version: 1.0.6 + resolution: "toggle-selection@npm:1.0.6" + checksum: 10c0/f2cf1f2c70f374fd87b0cdc8007453ba9e981c4305a8bf4eac10a30e62ecdfd28bca7d18f8f15b15a506bf8a7bfb20dbe3539f0fcf2a2c8396c1a78d53e1f179 + languageName: node + linkType: hard + +"toidentifier@npm:1.0.0": + version: 1.0.0 + resolution: "toidentifier@npm:1.0.0" + checksum: 10c0/27a37b8b21126e7216d40c02f410065b1de35b0f844368d0ccaabba7987595703006d45e5c094b086220cbbc5864d4b99766b460110e4bc15b9db574c5c58be2 + languageName: node + linkType: hard + +"toidentifier@npm:1.0.1, toidentifier@npm:~1.0.1": + version: 1.0.1 + resolution: "toidentifier@npm:1.0.1" + checksum: 10c0/93937279934bd66cc3270016dd8d0afec14fb7c94a05c72dc57321f8bd1fa97e5bea6d1f7c89e728d077ca31ea125b78320a616a6c6cd0e6b9cb94cb864381c1 + languageName: node + linkType: hard + +"tree-kill@npm:^1.2.2": + version: 1.2.2 + resolution: "tree-kill@npm:1.2.2" + bin: + tree-kill: cli.js + checksum: 10c0/7b1b7c7f17608a8f8d20a162e7957ac1ef6cd1636db1aba92f4e072dc31818c2ff0efac1e3d91064ede67ed5dc57c565420531a8134090a12ac10cf792ab14d2 + languageName: node + linkType: hard + +"trim-lines@npm:^3.0.0": + version: 3.0.1 + resolution: "trim-lines@npm:3.0.1" + checksum: 10c0/3a1611fa9e52aa56a94c69951a9ea15b8aaad760eaa26c56a65330dc8adf99cb282fc07cc9d94968b7d4d88003beba220a7278bbe2063328eb23fb56f9509e94 + languageName: node + linkType: hard + +"trim-right@npm:^1.0.1": + version: 1.0.1 + resolution: "trim-right@npm:1.0.1" + checksum: 10c0/71989ec179c6b42a56e03db68e60190baabf39d32d4e1252fa1501c4e478398ae29d7191beffe015b9d9dc76f04f4b3a946bdb9949ad6b0c0b0c5db65f3eb672 + languageName: node + linkType: hard + +"trough@npm:^2.0.0": + version: 2.2.0 + resolution: "trough@npm:2.2.0" + checksum: 10c0/58b671fc970e7867a48514168894396dd94e6d9d6456aca427cc299c004fe67f35ed7172a36449086b2edde10e78a71a284ec0076809add6834fb8f857ccb9b0 + languageName: node + linkType: hard + +"truncate-utf8-bytes@npm:^1.0.0": + version: 1.0.2 + resolution: "truncate-utf8-bytes@npm:1.0.2" + dependencies: + utf8-byte-length: "npm:^1.0.1" + checksum: 10c0/af2b431fc4314f119b551e5fccfad49d4c0ef82e13ba9ca61be6567801195b08e732ce9643542e8ad1b3df44f3df2d7345b3dd34f723954b6bb43a14584d6b3c + languageName: node + linkType: hard + +"ts-api-utils@npm:^1.0.1": + version: 1.4.3 + resolution: "ts-api-utils@npm:1.4.3" + peerDependencies: + typescript: ">=4.2.0" + checksum: 10c0/e65dc6e7e8141140c23e1dc94984bf995d4f6801919c71d6dc27cf0cd51b100a91ffcfe5217626193e5bea9d46831e8586febdc7e172df3f1091a7384299e23a + languageName: node + linkType: hard + +"ts-interface-checker@npm:^0.1.9": + version: 0.1.13 + resolution: "ts-interface-checker@npm:0.1.13" + checksum: 10c0/232509f1b84192d07b81d1e9b9677088e590ac1303436da1e92b296e9be8e31ea042e3e1fd3d29b1742ad2c959e95afe30f63117b8f1bc3a3850070a5142fea7 + languageName: node + linkType: hard + +"tsconfig-paths@npm:^3.15.0": + version: 3.15.0 + resolution: "tsconfig-paths@npm:3.15.0" + dependencies: + "@types/json5": "npm:^0.0.29" + json5: "npm:^1.0.2" + minimist: "npm:^1.2.6" + strip-bom: "npm:^3.0.0" + checksum: 10c0/5b4f301a2b7a3766a986baf8fc0e177eb80bdba6e396792ff92dc23b5bca8bb279fc96517dcaaef63a3b49bebc6c4c833653ec58155780bc906bdbcf7dda0ef5 + languageName: node + linkType: hard + +"tslib@npm:2.3.0": + version: 2.3.0 + resolution: "tslib@npm:2.3.0" + checksum: 10c0/a845aed84e7e7dbb4c774582da60d7030ea39d67307250442d35c4c5dd77e4b44007098c37dd079e100029c76055f2a362734b8442ba828f8cc934f15ed9be61 + languageName: node + linkType: hard + +"tslib@npm:2.6.2": + version: 2.6.2 + resolution: "tslib@npm:2.6.2" + checksum: 10c0/e03a8a4271152c8b26604ed45535954c0a45296e32445b4b87f8a5abdb2421f40b59b4ca437c4346af0f28179780d604094eb64546bee2019d903d01c6c19bdb + languageName: node + linkType: hard + +"tslib@npm:^1.8.1, tslib@npm:^1.9.0": + version: 1.14.1 + resolution: "tslib@npm:1.14.1" + checksum: 10c0/69ae09c49eea644bc5ebe1bca4fa4cc2c82b7b3e02f43b84bd891504edf66dbc6b2ec0eef31a957042de2269139e4acff911e6d186a258fb14069cd7f6febce2 + languageName: node + linkType: hard + +"tslib@npm:^2, tslib@npm:^2.0.0, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.3.0, tslib@npm:^2.4.0, tslib@npm:^2.4.1, tslib@npm:^2.5.0, tslib@npm:^2.6.0, tslib@npm:^2.8.0": + version: 2.8.1 + resolution: "tslib@npm:2.8.1" + checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62 + languageName: node + linkType: hard + +"tsutils@npm:^3.21.0": + version: 3.21.0 + resolution: "tsutils@npm:3.21.0" + dependencies: + tslib: "npm:^1.8.1" + peerDependencies: + typescript: ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + checksum: 10c0/02f19e458ec78ead8fffbf711f834ad8ecd2cc6ade4ec0320790713dccc0a412b99e7fd907c4cda2a1dc602c75db6f12e0108e87a5afad4b2f9e90a24cabd5a2 + languageName: node + linkType: hard + +"tsx@npm:3.12.2": + version: 3.12.2 + resolution: "tsx@npm:3.12.2" + dependencies: + "@esbuild-kit/cjs-loader": "npm:^2.4.1" + "@esbuild-kit/core-utils": "npm:^3.0.0" + "@esbuild-kit/esm-loader": "npm:^2.5.4" + fsevents: "npm:~2.3.2" + dependenciesMeta: + fsevents: + optional: true + bin: + tsx: dist/cli.js + checksum: 10c0/ad12cbe9eb882b833359b125b7c325360ffc0adc26c30c7d452fd8212f243de5fffecccff50db9474c6ddb38b2ce7bae54d8f3ecd4b80f8695e0a19856247189 + languageName: node + linkType: hard + +"tty-browserify@npm:0.0.0": + version: 0.0.0 + resolution: "tty-browserify@npm:0.0.0" + checksum: 10c0/c0c68206565f1372e924d5cdeeff1a0d9cc729833f1da98c03d78be8f939e5f61a107bd0ab77d1ef6a47d62bb0e48b1081fbea273acf404959e22fd3891439c5 + languageName: node + linkType: hard + +"type-check@npm:^0.4.0, type-check@npm:~0.4.0": + version: 0.4.0 + resolution: "type-check@npm:0.4.0" + dependencies: + prelude-ls: "npm:^1.2.1" + checksum: 10c0/7b3fd0ed43891e2080bf0c5c504b418fbb3e5c7b9708d3d015037ba2e6323a28152ec163bcb65212741fa5d2022e3075ac3c76440dbd344c9035f818e8ecee58 + languageName: node + linkType: hard + +"type-fest@npm:^0.13.1": + version: 0.13.1 + resolution: "type-fest@npm:0.13.1" + checksum: 10c0/0c0fa07ae53d4e776cf4dac30d25ad799443e9eef9226f9fddbb69242db86b08584084a99885cfa5a9dfe4c063ebdc9aa7b69da348e735baede8d43f1aeae93b + languageName: node + linkType: hard + +"type-fest@npm:^0.20.2": + version: 0.20.2 + resolution: "type-fest@npm:0.20.2" + checksum: 10c0/dea9df45ea1f0aaa4e2d3bed3f9a0bfe9e5b2592bddb92eb1bf06e50bcf98dbb78189668cd8bc31a0511d3fc25539b4cd5c704497e53e93e2d40ca764b10bfc3 + languageName: node + linkType: hard + +"type-is@npm:~1.6.18": + version: 1.6.18 + resolution: "type-is@npm:1.6.18" + dependencies: + media-typer: "npm:0.3.0" + mime-types: "npm:~2.1.24" + checksum: 10c0/a23daeb538591b7efbd61ecf06b6feb2501b683ffdc9a19c74ef5baba362b4347e42f1b4ed81f5882a8c96a3bfff7f93ce3ffaf0cbbc879b532b04c97a55db9d + languageName: node + linkType: hard + +"type@npm:^2.7.2": + version: 2.7.3 + resolution: "type@npm:2.7.3" + checksum: 10c0/dec6902c2c42fcb86e3adf8cdabdf80e5ef9de280872b5fd547351e9cca2fe58dd2aa6d2547626ddff174145db272f62d95c7aa7038e27c11315657d781a688d + languageName: node + linkType: hard + +"typed-array-buffer@npm:^1.0.3": + version: 1.0.3 + resolution: "typed-array-buffer@npm:1.0.3" + dependencies: + call-bound: "npm:^1.0.3" + es-errors: "npm:^1.3.0" + is-typed-array: "npm:^1.1.14" + checksum: 10c0/1105071756eb248774bc71646bfe45b682efcad93b55532c6ffa4518969fb6241354e4aa62af679ae83899ec296d69ef88f1f3763657cdb3a4d29321f7b83079 + languageName: node + linkType: hard + +"typed-array-byte-length@npm:^1.0.3": + version: 1.0.3 + resolution: "typed-array-byte-length@npm:1.0.3" + dependencies: + call-bind: "npm:^1.0.8" + for-each: "npm:^0.3.3" + gopd: "npm:^1.2.0" + has-proto: "npm:^1.2.0" + is-typed-array: "npm:^1.1.14" + checksum: 10c0/6ae083c6f0354f1fce18b90b243343b9982affd8d839c57bbd2c174a5d5dc71be9eb7019ffd12628a96a4815e7afa85d718d6f1e758615151d5f35df841ffb3e + languageName: node + linkType: hard + +"typed-array-byte-offset@npm:^1.0.4": + version: 1.0.4 + resolution: "typed-array-byte-offset@npm:1.0.4" + dependencies: + available-typed-arrays: "npm:^1.0.7" + call-bind: "npm:^1.0.8" + for-each: "npm:^0.3.3" + gopd: "npm:^1.2.0" + has-proto: "npm:^1.2.0" + is-typed-array: "npm:^1.1.15" + reflect.getprototypeof: "npm:^1.0.9" + checksum: 10c0/3d805b050c0c33b51719ee52de17c1cd8e6a571abdf0fffb110e45e8dd87a657e8b56eee94b776b13006d3d347a0c18a730b903cf05293ab6d92e99ff8f77e53 + languageName: node + linkType: hard + +"typed-array-length@npm:^1.0.7": + version: 1.0.7 + resolution: "typed-array-length@npm:1.0.7" + dependencies: + call-bind: "npm:^1.0.7" + for-each: "npm:^0.3.3" + gopd: "npm:^1.0.1" + is-typed-array: "npm:^1.1.13" + possible-typed-array-names: "npm:^1.0.0" + reflect.getprototypeof: "npm:^1.0.6" + checksum: 10c0/e38f2ae3779584c138a2d8adfa8ecf749f494af3cd3cdafe4e688ce51418c7d2c5c88df1bd6be2bbea099c3f7cea58c02ca02ed438119e91f162a9de23f61295 + languageName: node + linkType: hard + +"typescript@npm:^5.0.3": + version: 5.9.3 + resolution: "typescript@npm:5.9.3" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/6bd7552ce39f97e711db5aa048f6f9995b53f1c52f7d8667c1abdc1700c68a76a308f579cd309ce6b53646deb4e9a1be7c813a93baaf0a28ccd536a30270e1c5 + languageName: node + linkType: hard + +"typescript@patch:typescript@npm%3A^5.0.3#optional!builtin": + version: 5.9.3 + resolution: "typescript@patch:typescript@npm%3A5.9.3#optional!builtin::version=5.9.3&hash=5786d5" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/ad09fdf7a756814dce65bc60c1657b40d44451346858eea230e10f2e95a289d9183b6e32e5c11e95acc0ccc214b4f36289dcad4bf1886b0adb84d711d336a430 + languageName: node + linkType: hard + +"umi-request@npm:^1.4.0": + version: 1.4.0 + resolution: "umi-request@npm:1.4.0" + dependencies: + isomorphic-fetch: "npm:^2.2.1" + qs: "npm:^6.9.1" + checksum: 10c0/7f67a2fc788bc66b8ebce12ffa65c475d890302c5d7caa92d3b549718f4e9ea91a6f3a9e6c204ffeaa03665a93241f0b0ea1025e65d4f7aeb715fa261b184ba5 + languageName: node + linkType: hard + +"umi@npm:^4.0.87": + version: 4.6.0 + resolution: "umi@npm:4.6.0" + dependencies: + "@babel/runtime": "npm:7.23.6" + "@umijs/bundler-utils": "npm:4.6.0" + "@umijs/bundler-webpack": "npm:4.6.0" + "@umijs/core": "npm:4.6.0" + "@umijs/lint": "npm:4.6.0" + "@umijs/preset-umi": "npm:4.6.0" + "@umijs/renderer-react": "npm:4.6.0" + "@umijs/server": "npm:4.6.0" + "@umijs/test": "npm:4.6.0" + "@umijs/utils": "npm:4.6.0" + prettier-plugin-organize-imports: "npm:^3.2.2" + prettier-plugin-packagejson: "npm:2.4.3" + bin: + umi: bin/umi.js + checksum: 10c0/7cecd3978c282887f530bf7f10d1d9c7733947a097e0295707c254d3e559f1e00e3243823f68ab09badb497232cddd0f592b8f0f09ab1673e3eb06a2a0927c00 + languageName: node + linkType: hard + +"unbox-primitive@npm:^1.1.0": + version: 1.1.0 + resolution: "unbox-primitive@npm:1.1.0" + dependencies: + call-bound: "npm:^1.0.3" + has-bigints: "npm:^1.0.2" + has-symbols: "npm:^1.1.0" + which-boxed-primitive: "npm:^1.1.1" + checksum: 10c0/7dbd35ab02b0e05fe07136c72cb9355091242455473ec15057c11430129bab38b7b3624019b8778d02a881c13de44d63cd02d122ee782fb519e1de7775b5b982 + languageName: node + linkType: hard + +"undici-types@npm:~7.16.0": + version: 7.16.0 + resolution: "undici-types@npm:7.16.0" + checksum: 10c0/3033e2f2b5c9f1504bdc5934646cb54e37ecaca0f9249c983f7b1fc2e87c6d18399ebb05dc7fd5419e02b2e915f734d872a65da2e3eeed1813951c427d33cc9a + languageName: node + linkType: hard + +"unfetch@npm:^5.0.0": + version: 5.0.0 + resolution: "unfetch@npm:5.0.0" + checksum: 10c0/ccbbf648a384d57aeaf3bd4972761327a6cf60c84a3edb8e2f9d18aed0df6214576fc8fcd444ea87672e8e32f4a74590bc5c07756f053f57f492c6d8363045c9 + languageName: node + linkType: hard + +"unicorn-magic@npm:^0.4.0": + version: 0.4.0 + resolution: "unicorn-magic@npm:0.4.0" + checksum: 10c0/cd6eff90967a5528dfa2016bdb5b38b0cd64c8558f9ba04fb5c2c23f3a232a67dfe2bfa4c45af3685d5f1a40dbac6a36d48e053f80f97ae4da1e0f6a55431685 + languageName: node + linkType: hard + +"unified@npm:^10.0.0": + version: 10.1.2 + resolution: "unified@npm:10.1.2" + dependencies: + "@types/unist": "npm:^2.0.0" + bail: "npm:^2.0.0" + extend: "npm:^3.0.0" + is-buffer: "npm:^2.0.0" + is-plain-obj: "npm:^4.0.0" + trough: "npm:^2.0.0" + vfile: "npm:^5.0.0" + checksum: 10c0/da9195e3375a74ab861a65e1d7b0454225d17a61646697911eb6b3e97de41091930ed3d167eb11881d4097c51deac407091d39ddd1ee8bf1fde3f946844a17a7 + languageName: node + linkType: hard + +"unique-filename@npm:^5.0.0": + version: 5.0.0 + resolution: "unique-filename@npm:5.0.0" + dependencies: + unique-slug: "npm:^6.0.0" + checksum: 10c0/afb897e9cf4c2fb622ea716f7c2bb462001928fc5f437972213afdf1cc32101a230c0f1e9d96fc91ee5185eca0f2feb34127145874975f347be52eb91d6ccc2c + languageName: node + linkType: hard + +"unique-slug@npm:^6.0.0": + version: 6.0.0 + resolution: "unique-slug@npm:6.0.0" + dependencies: + imurmurhash: "npm:^0.1.4" + checksum: 10c0/da7ade4cb04eb33ad0499861f82fe95ce9c7c878b7139dc54d140ecfb6a6541c18a5c8dac16188b8b379fe62c0c1f1b710814baac910cde5f4fec06212126c6a + languageName: node + linkType: hard + +"unist-util-generated@npm:^2.0.0": + version: 2.0.1 + resolution: "unist-util-generated@npm:2.0.1" + checksum: 10c0/6f052dd47a7280785f3787f52cdfe8819e1de50317a1bcf7c9346c63268cf2cebc61a5980e7ca734a54735e27dbb73091aa0361a98504ab7f9409fb75f1b16bb + languageName: node + linkType: hard + +"unist-util-is@npm:^5.0.0": + version: 5.2.1 + resolution: "unist-util-is@npm:5.2.1" + dependencies: + "@types/unist": "npm:^2.0.0" + checksum: 10c0/a2376910b832bb10653d2167c3cd85b3610a5fd53f5169834c08b3c3a720fae9043d75ad32d727eedfc611491966c26a9501d428ec62467edc17f270feb5410b + languageName: node + linkType: hard + +"unist-util-position@npm:^4.0.0": + version: 4.0.4 + resolution: "unist-util-position@npm:4.0.4" + dependencies: + "@types/unist": "npm:^2.0.0" + checksum: 10c0/e506d702e25a0fb47a64502054f709a6ff5db98993bf139eec868cd11eb7de34392b781c6c2002e2c24d97aa398c14b32a47076129f36e4b894a2c1351200888 + languageName: node + linkType: hard + +"unist-util-stringify-position@npm:^3.0.0": + version: 3.0.3 + resolution: "unist-util-stringify-position@npm:3.0.3" + dependencies: + "@types/unist": "npm:^2.0.0" + checksum: 10c0/14550027825230528f6437dad7f2579a841780318569851291be6c8a970bae6f65a7feb24dabbcfce0e5e68cacae85bf12cbda3f360f7c873b4db602bdf7bb21 + languageName: node + linkType: hard + +"unist-util-visit-parents@npm:^5.0.0, unist-util-visit-parents@npm:^5.1.1": + version: 5.1.3 + resolution: "unist-util-visit-parents@npm:5.1.3" + dependencies: + "@types/unist": "npm:^2.0.0" + unist-util-is: "npm:^5.0.0" + checksum: 10c0/f6829bfd8f2eddf63a32e2c302cd50978ef0c194b792c6fe60c2b71dfd7232415a3c5941903972543e9d34e6a8ea69dee9ccd95811f4a795495ed2ae855d28d0 + languageName: node + linkType: hard + +"unist-util-visit@npm:^4.0.0": + version: 4.1.2 + resolution: "unist-util-visit@npm:4.1.2" + dependencies: + "@types/unist": "npm:^2.0.0" + unist-util-is: "npm:^5.0.0" + unist-util-visit-parents: "npm:^5.1.1" + checksum: 10c0/56a1f49a4d8e321e75b3c7821d540a45165a031dd06324bb0e8c75e7737bc8d73bdddbf0b0ca82000f9708a4c36861c6ebe88d01f7cf00e925f5d75f13a3a017 + languageName: node + linkType: hard + +"universalify@npm:^0.1.0": + version: 0.1.2 + resolution: "universalify@npm:0.1.2" + checksum: 10c0/e70e0339f6b36f34c9816f6bf9662372bd241714dc77508d231d08386d94f2c4aa1ba1318614f92015f40d45aae1b9075cd30bd490efbe39387b60a76ca3f045 + languageName: node + linkType: hard + +"universalify@npm:^2.0.0": + version: 2.0.1 + resolution: "universalify@npm:2.0.1" + checksum: 10c0/73e8ee3809041ca8b818efb141801a1004e3fc0002727f1531f4de613ea281b494a40909596dae4a042a4fb6cd385af5d4db2e137b1362e0e91384b828effd3a + languageName: node + linkType: hard + +"unpipe@npm:1.0.0, unpipe@npm:~1.0.0": + version: 1.0.0 + resolution: "unpipe@npm:1.0.0" + checksum: 10c0/193400255bd48968e5c5383730344fbb4fa114cdedfab26e329e50dd2d81b134244bb8a72c6ac1b10ab0281a58b363d06405632c9d49ca9dfd5e90cbd7d0f32c + languageName: node + linkType: hard + +"untildify@npm:^4.0.0": + version: 4.0.0 + resolution: "untildify@npm:4.0.0" + checksum: 10c0/d758e624c707d49f76f7511d75d09a8eda7f2020d231ec52b67ff4896bcf7013be3f9522d8375f57e586e9a2e827f5641c7e06ee46ab9c435fc2b2b2e9de517a + languageName: node + linkType: hard + +"update-browserslist-db@npm:^1.1.4": + version: 1.1.4 + resolution: "update-browserslist-db@npm:1.1.4" + dependencies: + escalade: "npm:^3.2.0" + picocolors: "npm:^1.1.1" + peerDependencies: + browserslist: ">= 4.21.0" + bin: + update-browserslist-db: cli.js + checksum: 10c0/db0c9aaecf1258a6acda5e937fc27a7996ccca7a7580a1b4aa8bba6a9b0e283e5e65c49ebbd74ec29288ef083f1b88d4da13e3d4d326c1e5fc55bf72d7390702 + languageName: node + linkType: hard + +"uri-js@npm:^4.2.2": + version: 4.4.1 + resolution: "uri-js@npm:4.4.1" + dependencies: + punycode: "npm:^2.1.0" + checksum: 10c0/4ef57b45aa820d7ac6496e9208559986c665e49447cb072744c13b66925a362d96dd5a46c4530a6b8e203e5db5fe849369444440cb22ecfc26c679359e5dfa3c + languageName: node + linkType: hard + +"url-okam@npm:^0.11.0": + version: 0.11.1 + resolution: "url-okam@npm:0.11.1" + dependencies: + punycode: "npm:^1.4.1" + qs: "npm:^6.11.0" + checksum: 10c0/5853b219c2f8fc4a3ed99d6a56519a64a786a9d91f3444d5ce68d5d9c84fa5c1e5bd7092ade397ae79418e226a909c14bc0dd335a61db21a993b4e4a23ad6bbf + languageName: node + linkType: hard + +"url@npm:^0.11.0": + version: 0.11.4 + resolution: "url@npm:0.11.4" + dependencies: + punycode: "npm:^1.4.1" + qs: "npm:^6.12.3" + checksum: 10c0/cc93405ae4a9b97a2aa60ca67f1cb1481c0221cb4725a7341d149be5e2f9cfda26fd432d64dbbec693d16593b68b8a46aad8e5eab21f814932134c9d8620c662 + languageName: node + linkType: hard + +"use-isomorphic-layout-effect@npm:^1.1.1": + version: 1.2.1 + resolution: "use-isomorphic-layout-effect@npm:1.2.1" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/4d3c1124d630fbe09c1d2a16af0cd78ac2fe1d22eb24a178363e3d84a897659cc04e8e8cd71f66ff78ff75ef8287fa72e746cb213b96c1097e70e4b4ed69f63f + languageName: node + linkType: hard + +"use-sync-external-store@npm:1.2.0": + version: 1.2.0 + resolution: "use-sync-external-store@npm:1.2.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: 10c0/ac4814e5592524f242921157e791b022efe36e451fe0d4fd4d204322d5433a4fc300d63b0ade5185f8e0735ded044c70bcf6d2352db0f74d097a238cebd2da02 + languageName: node + linkType: hard + +"use-sync-external-store@npm:^1.0.0, use-sync-external-store@npm:^1.2.0, use-sync-external-store@npm:^1.2.2, use-sync-external-store@npm:^1.4.0": + version: 1.6.0 + resolution: "use-sync-external-store@npm:1.6.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10c0/35e1179f872a53227bdf8a827f7911da4c37c0f4091c29b76b1e32473d1670ebe7bcd880b808b7549ba9a5605c233350f800ffab963ee4a4ee346ee983b6019b + languageName: node + linkType: hard + +"utf8-byte-length@npm:^1.0.1": + version: 1.0.5 + resolution: "utf8-byte-length@npm:1.0.5" + checksum: 10c0/e69bda3299608f4cc75976da9fb74ac94801a58b9ca29fdad03a20ec952e7477d7f226c12716b5f36bd4cff8151d1d152d02ee1df3752f017d4b2c725ce3e47a + languageName: node + linkType: hard + +"util-deprecate@npm:^1.0.1, util-deprecate@npm:^1.0.2, util-deprecate@npm:~1.0.1": + version: 1.0.2 + resolution: "util-deprecate@npm:1.0.2" + checksum: 10c0/41a5bdd214df2f6c3ecf8622745e4a366c4adced864bc3c833739791aeeeb1838119af7daed4ba36428114b5c67dcda034a79c882e97e43c03e66a4dd7389942 + languageName: node + linkType: hard + +"util-okam@npm:^0.11.0": + version: 0.11.1 + resolution: "util-okam@npm:0.11.1" + dependencies: + inherits: "npm:2.0.3" + checksum: 10c0/d55b9548ff673c44ef3b722c7b113aeab753e32aa12906d25310ef3b991b6727c3dc8b4702a786bf8bc9fd375974632e4293050905b3b0cddd3ced7aa6197fe6 + languageName: node + linkType: hard + +"util@npm:0.10.3": + version: 0.10.3 + resolution: "util@npm:0.10.3" + dependencies: + inherits: "npm:2.0.1" + checksum: 10c0/88bb58fec3b1f5f43dea27795f61f24b3b505bbba6f3ad6e91b32db0cd0928b2acb54ebe21603a75743c6e21a52f954cd2ffb6cddafed5a01169dd1287db3ff3 + languageName: node + linkType: hard + +"util@npm:^0.10.4": + version: 0.10.4 + resolution: "util@npm:0.10.4" + dependencies: + inherits: "npm:2.0.3" + checksum: 10c0/d29f6893e406b63b088ce9924da03201df89b31490d4d011f1c07a386ea4b3dbe907464c274023c237da470258e1805d806c7e4009a5974cd6b1d474b675852a + languageName: node + linkType: hard + +"util@npm:^0.11.0": + version: 0.11.1 + resolution: "util@npm:0.11.1" + dependencies: + inherits: "npm:2.0.3" + checksum: 10c0/8e9d1a85e661c8a8d9883d821aedbff3f8d9c3accd85357020905386ada5653b20389fc3591901e2a0bde64f8dc86b28c3f990114aa5a38eaaf30b455fa3cdf6 + languageName: node + linkType: hard + +"utila@npm:~0.4": + version: 0.4.0 + resolution: "utila@npm:0.4.0" + checksum: 10c0/2791604e09ca4f77ae314df83e80d1805f867eb5c7e13e7413caee01273c278cf2c9a3670d8d25c889a877f7b149d892fe61b0181a81654b425e9622ab23d42e + languageName: node + linkType: hard + +"utils-merge@npm:1.0.1": + version: 1.0.1 + resolution: "utils-merge@npm:1.0.1" + checksum: 10c0/02ba649de1b7ca8854bfe20a82f1dfbdda3fb57a22ab4a8972a63a34553cf7aa51bc9081cf7e001b035b88186d23689d69e71b510e610a09a4c66f68aa95b672 + languageName: node + linkType: hard + +"uuid@npm:^9.0.0": + version: 9.0.1 + resolution: "uuid@npm:9.0.1" + bin: + uuid: dist/bin/uuid + checksum: 10c0/1607dd32ac7fc22f2d8f77051e6a64845c9bce5cd3dd8aa0070c074ec73e666a1f63c7b4e0f4bf2bc8b9d59dc85a15e17807446d9d2b17c8485fbc2147b27f9b + languageName: node + linkType: hard + +"uvu@npm:^0.5.0": + version: 0.5.6 + resolution: "uvu@npm:0.5.6" + dependencies: + dequal: "npm:^2.0.0" + diff: "npm:^5.0.0" + kleur: "npm:^4.0.3" + sade: "npm:^1.7.3" + bin: + uvu: bin.js + checksum: 10c0/ad32eb5f7d94bdeb71f80d073003f0138e24f61ed68cecc8e15d2f30838f44c9670577bb1775c8fac894bf93d1bc1583d470a9195e49bfa6efa14cc6f4942bff + languageName: node + linkType: hard + +"valtio@npm:1.11.2": + version: 1.11.2 + resolution: "valtio@npm:1.11.2" + dependencies: + proxy-compare: "npm:2.5.1" + use-sync-external-store: "npm:1.2.0" + peerDependencies: + "@types/react": ">=16.8" + react: ">=16.8" + peerDependenciesMeta: + "@types/react": + optional: true + react: + optional: true + checksum: 10c0/9ed337d1da4a3730d429b3415c2cb63340998000e62fb3e545e2fc05d27f55fc510abc89046d6719b4cae02742cdb733fe235bade90bfae50a0e13ece2287106 + languageName: node + linkType: hard + +"vary@npm:^1, vary@npm:~1.1.2": + version: 1.1.2 + resolution: "vary@npm:1.1.2" + checksum: 10c0/f15d588d79f3675135ba783c91a4083dcd290a2a5be9fcb6514220a1634e23df116847b1cc51f66bfb0644cf9353b2abb7815ae499bab06e46dd33c1a6bf1f4f + languageName: node + linkType: hard + +"verror@npm:^1.10.0": + version: 1.10.1 + resolution: "verror@npm:1.10.1" + dependencies: + assert-plus: "npm:^1.0.0" + core-util-is: "npm:1.0.2" + extsprintf: "npm:^1.2.0" + checksum: 10c0/293fb060a4c9b07965569a0c3e45efa954127818707995a8a4311f691b5d6687be99f972c759838ba6eecae717f9af28e3c49d2afc7bbdf5f0b675238f1426e8 + languageName: node + linkType: hard + +"vfile-message@npm:^3.0.0": + version: 3.1.4 + resolution: "vfile-message@npm:3.1.4" + dependencies: + "@types/unist": "npm:^2.0.0" + unist-util-stringify-position: "npm:^3.0.0" + checksum: 10c0/c4ccf9c0ced92d657846fd067fefcf91c5832cdbe2ecc431bb67886e8c959bf7fc05a9dbbca5551bc34c9c87a0a73854b4249f65c64ddfebc4d59ea24a18b996 + languageName: node + linkType: hard + +"vfile@npm:^5.0.0": + version: 5.3.7 + resolution: "vfile@npm:5.3.7" + dependencies: + "@types/unist": "npm:^2.0.0" + is-buffer: "npm:^2.0.0" + unist-util-stringify-position: "npm:^3.0.0" + vfile-message: "npm:^3.0.0" + checksum: 10c0/c36bd4c3f16ec0c6cbad0711ca99200316bbf849d6b07aa4cb5d9062cc18ae89249fe62af9521926e9659c0e6bc5c2c1da0fe26b41fb71e757438297e1a41da4 + languageName: node + linkType: hard + +"vite@npm:4.5.2": + version: 4.5.2 + resolution: "vite@npm:4.5.2" + dependencies: + esbuild: "npm:^0.18.10" + fsevents: "npm:~2.3.2" + postcss: "npm:^8.4.27" + rollup: "npm:^3.27.1" + peerDependencies: + "@types/node": ">= 14" + less: "*" + lightningcss: ^1.21.0 + sass: "*" + stylus: "*" + sugarss: "*" + terser: ^5.4.0 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + bin: + vite: bin/vite.js + checksum: 10c0/68969ccf72ad2078aec7d9e023fce6de03746a4761f9308924212fff7bd42487145b270166cec66cddacfd7b1315ec5aa39ead174fbd7fcd463637a96ff4c9d1 + languageName: node + linkType: hard + +"vm-browserify@npm:^1.0.1": + version: 1.1.2 + resolution: "vm-browserify@npm:1.1.2" + checksum: 10c0/0cc1af6e0d880deb58bc974921320c187f9e0a94f25570fca6b1bd64e798ce454ab87dfd797551b1b0cc1849307421aae0193cedf5f06bdb5680476780ee344b + languageName: node + linkType: hard + +"walker@npm:^1.0.8": + version: 1.0.8 + resolution: "walker@npm:1.0.8" + dependencies: + makeerror: "npm:1.0.12" + checksum: 10c0/a17e037bccd3ca8a25a80cb850903facdfed0de4864bd8728f1782370715d679fa72e0a0f5da7c1c1379365159901e5935f35be531229da53bbfc0efdabdb48e + languageName: node + linkType: hard + +"warning@npm:^3.0.0": + version: 3.0.0 + resolution: "warning@npm:3.0.0" + dependencies: + loose-envify: "npm:^1.0.0" + checksum: 10c0/6a2a56ab3139d3927193d926a027e74e1449fa47cc692feea95f8a81a4bb5b7f10c312def94cce03f3b58cb26ba3247858e75d17d596451d2c483a62e8204705 + languageName: node + linkType: hard + +"warning@npm:^4.0.3": + version: 4.0.3 + resolution: "warning@npm:4.0.3" + dependencies: + loose-envify: "npm:^1.0.0" + checksum: 10c0/aebab445129f3e104c271f1637fa38e55eb25f968593e3825bd2f7a12bd58dc3738bb70dc8ec85826621d80b4acfed5a29ebc9da17397c6125864d72301b937e + languageName: node + linkType: hard + +"wbuf@npm:^1.1.0, wbuf@npm:^1.7.3": + version: 1.7.3 + resolution: "wbuf@npm:1.7.3" + dependencies: + minimalistic-assert: "npm:^1.0.0" + checksum: 10c0/56edcc5ef2b3d30913ba8f1f5cccc364d180670b24d5f3f8849c1e6fb514e5c7e3a87548ae61227a82859eba6269c11393ae24ce12a2ea1ecb9b465718ddced7 + languageName: node + linkType: hard + +"web-streams-polyfill@npm:^3.0.3": + version: 3.3.3 + resolution: "web-streams-polyfill@npm:3.3.3" + checksum: 10c0/64e855c47f6c8330b5436147db1c75cb7e7474d924166800e8e2aab5eb6c76aac4981a84261dd2982b3e754490900b99791c80ae1407a9fa0dcff74f82ea3a7f + languageName: node + linkType: hard + +"webpack-5-chain@npm:8.0.1": + version: 8.0.1 + resolution: "webpack-5-chain@npm:8.0.1" + dependencies: + deepmerge: "npm:^1.5.2" + javascript-stringify: "npm:^2.0.1" + checksum: 10c0/f4dc5bbdbbe3590daeb5c0567020bd00395a3ee8db786ac4abb9fed25c80bf7e6f3e1bb6e11292c52297c5515d88272a5fd53da97e72b8cb96494a1a895953e6 + languageName: node + linkType: hard + +"whatwg-fetch@npm:>=0.10.0": + version: 3.6.20 + resolution: "whatwg-fetch@npm:3.6.20" + checksum: 10c0/fa972dd14091321d38f36a4d062298df58c2248393ef9e8b154493c347c62e2756e25be29c16277396046d6eaa4b11bd174f34e6403fff6aaca9fb30fa1ff46d + languageName: node + linkType: hard + +"which-boxed-primitive@npm:^1.0.2, which-boxed-primitive@npm:^1.1.0, which-boxed-primitive@npm:^1.1.1": + version: 1.1.1 + resolution: "which-boxed-primitive@npm:1.1.1" + dependencies: + is-bigint: "npm:^1.1.0" + is-boolean-object: "npm:^1.2.1" + is-number-object: "npm:^1.1.1" + is-string: "npm:^1.1.1" + is-symbol: "npm:^1.1.1" + checksum: 10c0/aceea8ede3b08dede7dce168f3883323f7c62272b49801716e8332ff750e7ae59a511ae088840bc6874f16c1b7fd296c05c949b0e5b357bfe3c431b98c417abe + languageName: node + linkType: hard + +"which-builtin-type@npm:^1.2.1": + version: 1.2.1 + resolution: "which-builtin-type@npm:1.2.1" + dependencies: + call-bound: "npm:^1.0.2" + function.prototype.name: "npm:^1.1.6" + has-tostringtag: "npm:^1.0.2" + is-async-function: "npm:^2.0.0" + is-date-object: "npm:^1.1.0" + is-finalizationregistry: "npm:^1.1.0" + is-generator-function: "npm:^1.0.10" + is-regex: "npm:^1.2.1" + is-weakref: "npm:^1.0.2" + isarray: "npm:^2.0.5" + which-boxed-primitive: "npm:^1.1.0" + which-collection: "npm:^1.0.2" + which-typed-array: "npm:^1.1.16" + checksum: 10c0/8dcf323c45e5c27887800df42fbe0431d0b66b1163849bb7d46b5a730ad6a96ee8bfe827d078303f825537844ebf20c02459de41239a0a9805e2fcb3cae0d471 + languageName: node + linkType: hard + +"which-collection@npm:^1.0.1, which-collection@npm:^1.0.2": + version: 1.0.2 + resolution: "which-collection@npm:1.0.2" + dependencies: + is-map: "npm:^2.0.3" + is-set: "npm:^2.0.3" + is-weakmap: "npm:^2.0.2" + is-weakset: "npm:^2.0.3" + checksum: 10c0/3345fde20964525a04cdf7c4a96821f85f0cc198f1b2ecb4576e08096746d129eb133571998fe121c77782ac8f21cbd67745a3d35ce100d26d4e684c142ea1f2 + languageName: node + linkType: hard + +"which-typed-array@npm:^1.1.16, which-typed-array@npm:^1.1.19": + version: 1.1.19 + resolution: "which-typed-array@npm:1.1.19" + dependencies: + available-typed-arrays: "npm:^1.0.7" + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" + for-each: "npm:^0.3.5" + get-proto: "npm:^1.0.1" + gopd: "npm:^1.2.0" + has-tostringtag: "npm:^1.0.2" + checksum: 10c0/702b5dc878addafe6c6300c3d0af5983b175c75fcb4f2a72dfc3dd38d93cf9e89581e4b29c854b16ea37e50a7d7fca5ae42ece5c273d8060dcd603b2404bbb3f + languageName: node + linkType: hard + +"which@npm:^1.3.1": + version: 1.3.1 + resolution: "which@npm:1.3.1" + dependencies: + isexe: "npm:^2.0.0" + bin: + which: ./bin/which + checksum: 10c0/e945a8b6bbf6821aaaef7f6e0c309d4b615ef35699576d5489b4261da9539f70393c6b2ce700ee4321c18f914ebe5644bc4631b15466ffbaad37d83151f6af59 + languageName: node + linkType: hard + +"which@npm:^2.0.1": + version: 2.0.2 + resolution: "which@npm:2.0.2" + dependencies: + isexe: "npm:^2.0.0" + bin: + node-which: ./bin/node-which + checksum: 10c0/66522872a768b60c2a65a57e8ad184e5372f5b6a9ca6d5f033d4b0dc98aff63995655a7503b9c0a2598936f532120e81dd8cc155e2e92ed662a2b9377cc4374f + languageName: node + linkType: hard + +"which@npm:^6.0.0": + version: 6.0.0 + resolution: "which@npm:6.0.0" + dependencies: + isexe: "npm:^3.1.1" + bin: + node-which: bin/which.js + checksum: 10c0/fe9d6463fe44a76232bb6e3b3181922c87510a5b250a98f1e43a69c99c079b3f42ddeca7e03d3e5f2241bf2d334f5a7657cfa868b97c109f3870625842f4cc15 + languageName: node + linkType: hard + +"word-wrap@npm:^1.2.5": + version: 1.2.5 + resolution: "word-wrap@npm:1.2.5" + checksum: 10c0/e0e4a1ca27599c92a6ca4c32260e8a92e8a44f4ef6ef93f803f8ed823f486e0889fc0b93be4db59c8d51b3064951d25e43d434e95dc8c960cc3a63d65d00ba20 + languageName: node + linkType: hard + +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0": + version: 7.0.0 + resolution: "wrap-ansi@npm:7.0.0" + dependencies: + ansi-styles: "npm:^4.0.0" + string-width: "npm:^4.1.0" + strip-ansi: "npm:^6.0.0" + checksum: 10c0/d15fc12c11e4cbc4044a552129ebc75ee3f57aa9c1958373a4db0292d72282f54373b536103987a4a7594db1ef6a4f10acf92978f79b98c49306a4b58c77d4da + languageName: node + linkType: hard + +"wrap-ansi@npm:^8.1.0": + version: 8.1.0 + resolution: "wrap-ansi@npm:8.1.0" + dependencies: + ansi-styles: "npm:^6.1.0" + string-width: "npm:^5.0.1" + strip-ansi: "npm:^7.0.1" + checksum: 10c0/138ff58a41d2f877eae87e3282c0630fc2789012fc1af4d6bd626eeb9a2f9a65ca92005e6e69a75c7b85a68479fe7443c7dbe1eb8fbaa681a4491364b7c55c60 + languageName: node + linkType: hard + +"wrappy@npm:1": + version: 1.0.2 + resolution: "wrappy@npm:1.0.2" + checksum: 10c0/56fece1a4018c6a6c8e28fbc88c87e0fbf4ea8fd64fc6c63b18f4acc4bd13e0ad2515189786dd2c30d3eec9663d70f4ecf699330002f8ccb547e4a18231fc9f0 + languageName: node + linkType: hard + +"write-file-atomic@npm:^4.0.2": + version: 4.0.2 + resolution: "write-file-atomic@npm:4.0.2" + dependencies: + imurmurhash: "npm:^0.1.4" + signal-exit: "npm:^3.0.7" + checksum: 10c0/a2c282c95ef5d8e1c27b335ae897b5eca00e85590d92a3fd69a437919b7b93ff36a69ea04145da55829d2164e724bc62202cdb5f4b208b425aba0807889375c7 + languageName: node + linkType: hard + +"write-file-atomic@npm:^7.0.1": + version: 7.0.1 + resolution: "write-file-atomic@npm:7.0.1" + dependencies: + signal-exit: "npm:^4.0.1" + checksum: 10c0/69cebb64945e22928a24ae7e2a55bf54438c92d6f657c1fa5e96b7c7a50f6c022e7454ab5c259079bb35f60296242f3a21234c79320d64a8ad57675b56a2098d + languageName: node + linkType: hard + +"ws@npm:^8.18.1": + version: 8.18.3 + resolution: "ws@npm:8.18.3" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 10c0/eac918213de265ef7cb3d4ca348b891a51a520d839aa51cdb8ca93d4fa7ff9f6ccb339ccee89e4075324097f0a55157c89fa3f7147bde9d8d7e90335dc087b53 + languageName: node + linkType: hard + +"xmlbuilder@npm:>=11.0.1, xmlbuilder@npm:^15.1.1": + version: 15.1.1 + resolution: "xmlbuilder@npm:15.1.1" + checksum: 10c0/665266a8916498ff8d82b3d46d3993913477a254b98149ff7cff060d9b7cc0db7cf5a3dae99aed92355254a808c0e2e3ec74ad1b04aa1061bdb8dfbea26c18b8 + languageName: node + linkType: hard + +"xtend@npm:^4.0.0": + version: 4.0.2 + resolution: "xtend@npm:4.0.2" + checksum: 10c0/366ae4783eec6100f8a02dff02ac907bf29f9a00b82ac0264b4d8b832ead18306797e283cf19de776538babfdcb2101375ec5646b59f08c52128ac4ab812ed0e + languageName: node + linkType: hard + +"y18n@npm:^5.0.5": + version: 5.0.8 + resolution: "y18n@npm:5.0.8" + checksum: 10c0/4df2842c36e468590c3691c894bc9cdbac41f520566e76e24f59401ba7d8b4811eb1e34524d57e54bc6d864bcb66baab7ffd9ca42bf1eda596618f9162b91249 + languageName: node + linkType: hard + +"yallist@npm:^3.0.2": + version: 3.1.1 + resolution: "yallist@npm:3.1.1" + checksum: 10c0/c66a5c46bc89af1625476f7f0f2ec3653c1a1791d2f9407cfb4c2ba812a1e1c9941416d71ba9719876530e3340a99925f697142989371b72d93b9ee628afd8c1 + languageName: node + linkType: hard + +"yallist@npm:^4.0.0": + version: 4.0.0 + resolution: "yallist@npm:4.0.0" + checksum: 10c0/2286b5e8dbfe22204ab66e2ef5cc9bbb1e55dfc873bbe0d568aa943eb255d131890dfd5bf243637273d31119b870f49c18fcde2c6ffbb7a7a092b870dc90625a + languageName: node + linkType: hard + +"yallist@npm:^5.0.0": + version: 5.0.0 + resolution: "yallist@npm:5.0.0" + checksum: 10c0/a499c81ce6d4a1d260d4ea0f6d49ab4da09681e32c3f0472dee16667ed69d01dae63a3b81745a24bd78476ec4fcf856114cb4896ace738e01da34b2c42235416 + languageName: node + linkType: hard + +"yaml@npm:^1.10.0": + version: 1.10.2 + resolution: "yaml@npm:1.10.2" + checksum: 10c0/5c28b9eb7adc46544f28d9a8d20c5b3cb1215a886609a2fd41f51628d8aaa5878ccd628b755dbcd29f6bb4921bd04ffbc6dcc370689bb96e594e2f9813d2605f + languageName: node + linkType: hard + +"yargs-parser@npm:^21.1.1": + version: 21.1.1 + resolution: "yargs-parser@npm:21.1.1" + checksum: 10c0/f84b5e48169479d2f402239c59f084cfd1c3acc197a05c59b98bab067452e6b3ea46d4dd8ba2985ba7b3d32a343d77df0debd6b343e5dae3da2aab2cdf5886b2 + languageName: node + linkType: hard + +"yargs@npm:^17.5.1, yargs@npm:^17.7.2": + version: 17.7.2 + resolution: "yargs@npm:17.7.2" + dependencies: + cliui: "npm:^8.0.1" + escalade: "npm:^3.1.1" + get-caller-file: "npm:^2.0.5" + require-directory: "npm:^2.1.1" + string-width: "npm:^4.2.3" + y18n: "npm:^5.0.5" + yargs-parser: "npm:^21.1.1" + checksum: 10c0/ccd7e723e61ad5965fffbb791366db689572b80cca80e0f96aad968dfff4156cd7cd1ad18607afe1046d8241e6fb2d6c08bf7fa7bfb5eaec818735d8feac8f05 + languageName: node + linkType: hard + +"yauzl@npm:^2.10.0": + version: 2.10.0 + resolution: "yauzl@npm:2.10.0" + dependencies: + buffer-crc32: "npm:~0.2.3" + fd-slicer: "npm:~1.1.0" + checksum: 10c0/f265002af7541b9ec3589a27f5fb8f11cf348b53cc15e2751272e3c062cd73f3e715bc72d43257de71bbaecae446c3f1b14af7559e8ab0261625375541816422 + languageName: node + linkType: hard + +"yocto-queue@npm:^0.1.0": + version: 0.1.0 + resolution: "yocto-queue@npm:0.1.0" + checksum: 10c0/dceb44c28578b31641e13695d200d34ec4ab3966a5729814d5445b194933c096b7ced71494ce53a0e8820685d1d010df8b2422e5bf2cdea7e469d97ffbea306f + languageName: node + linkType: hard + +"zod-validation-error@npm:^2.1.0": + version: 2.1.0 + resolution: "zod-validation-error@npm:2.1.0" + peerDependencies: + zod: ^3.18.0 + checksum: 10c0/e8e8a0af64092dfb3388d759bf10fb7cf5358bc1bdb365771b8ac1944b1fb014ccbc8e60fbd69627961ea5873c5694e5c3fe730341c9842312fbb91661a1f451 + languageName: node + linkType: hard + +"zod@npm:^3.22.4": + version: 3.25.76 + resolution: "zod@npm:3.25.76" + checksum: 10c0/5718ec35e3c40b600316c5b4c5e4976f7fee68151bc8f8d90ec18a469be9571f072e1bbaace10f1e85cf8892ea12d90821b200e980ab46916a6166a4260a983c + languageName: node + linkType: hard + +"zrender@npm:5.6.1": + version: 5.6.1 + resolution: "zrender@npm:5.6.1" + dependencies: + tslib: "npm:2.3.0" + checksum: 10c0/dc1cc570054640cbd8fbb7b92e6252f225319522bfe3e8dc8bf02cc02d414e00a4c8d0a6f89bfc9d96e5e9511fdca94dd3d06bf53690df2b2f12b0fc560ac307 + languageName: node + linkType: hard + +"zustand@npm:^4.4.0, zustand@npm:^4.4.4": + version: 4.5.7 + resolution: "zustand@npm:4.5.7" + dependencies: + use-sync-external-store: "npm:^1.2.2" + peerDependencies: + "@types/react": ">=16.8" + immer: ">=9.0.6" + react: ">=16.8" + peerDependenciesMeta: + "@types/react": + optional: true + immer: + optional: true + react: + optional: true + checksum: 10c0/55559e37a82f0c06cadc61cb08f08314c0fe05d6a93815e41e3376130c13db22a5017cbb0cd1f018c82f2dad0051afe3592561d40f980bd4082e32005e8a950c + languageName: node + linkType: hard + +"zwitch@npm:^2.0.0": + version: 2.0.4 + resolution: "zwitch@npm:2.0.4" + checksum: 10c0/3c7830cdd3378667e058ffdb4cf2bb78ac5711214e2725900873accb23f3dfe5f9e7e5a06dcdc5f29605da976fc45c26d9a13ca334d6eea2245a15e77b8fc06e + languageName: node + linkType: hard diff --git a/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/DB2MetaData.java b/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/DB2MetaData.java index 7f1446a64..f70186ba6 100644 --- a/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/DB2MetaData.java +++ b/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/DB2MetaData.java @@ -147,7 +147,7 @@ private TableIndexColumn getTableIndexColumn(ResultSet resultSet) throws SQLExce } @Override - public TableMeta getTableMeta(String databaseName, String schemaName, String tableName) { + public TableMeta getTableMeta(Connection connection, String databaseName, String schemaName) { return TableMeta.builder() .columnTypes(DB2ColumnTypeEnum.getTypes()) .charsets(Lists.newArrayList()) diff --git a/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/builder/DB2SqlBuilder.java b/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/builder/DB2SqlBuilder.java index 20dc5cfbe..fc4c1b640 100644 --- a/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/builder/DB2SqlBuilder.java +++ b/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/builder/DB2SqlBuilder.java @@ -18,10 +18,10 @@ public String buildCreateTableSql(Table table) { script.append("CREATE TABLE ").append("\"").append(table.getSchemaName()).append("\".\"").append(table.getName()).append("\" (").append("\n"); for (TableColumn column : table.getColumnList()) { - if (StringUtils.isBlank(column.getName()) || StringUtils.isBlank(column.getColumnType())) { + if (StringUtils.isBlank(column.getName()) || StringUtils.isBlank(column.getDataType())) { continue; } - DB2ColumnTypeEnum typeEnum = DB2ColumnTypeEnum.getByType(column.getColumnType()); + DB2ColumnTypeEnum typeEnum = DB2ColumnTypeEnum.getByType(column.getDataType()); script.append("\t").append(typeEnum.buildCreateColumnSql(column)).append(",\n"); } diff --git a/chat2db-server/chat2db-plugins/chat2db-dlc/pom.xml b/chat2db-server/chat2db-plugins/chat2db-dlc/pom.xml new file mode 100644 index 000000000..a45b079cf --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-dlc/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + ai.chat2db + chat2db-plugins + ${revision} + ../pom.xml + + + chat2db-dlc + + + + ai.chat2db + chat2db-spi + + + + + + + src/main/java + + **/*.json + + + + src/main/resources + + + + diff --git a/chat2db-server/chat2db-plugins/chat2db-dlc/src/main/java/ai/chat2db/plugin/dlc/DlcDBManage.java b/chat2db-server/chat2db-plugins/chat2db-dlc/src/main/java/ai/chat2db/plugin/dlc/DlcDBManage.java new file mode 100644 index 000000000..c50bb8699 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-dlc/src/main/java/ai/chat2db/plugin/dlc/DlcDBManage.java @@ -0,0 +1,7 @@ +package ai.chat2db.plugin.dlc; + +import ai.chat2db.spi.DBManage; +import ai.chat2db.spi.jdbc.DefaultDBManage; + +public class DlcDBManage extends DefaultDBManage implements DBManage { +} diff --git a/chat2db-server/chat2db-plugins/chat2db-dlc/src/main/java/ai/chat2db/plugin/dlc/DlcMetaData.java b/chat2db-server/chat2db-plugins/chat2db-dlc/src/main/java/ai/chat2db/plugin/dlc/DlcMetaData.java new file mode 100644 index 000000000..139d14cc8 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-dlc/src/main/java/ai/chat2db/plugin/dlc/DlcMetaData.java @@ -0,0 +1,7 @@ +package ai.chat2db.plugin.dlc; + +import ai.chat2db.spi.MetaData; +import ai.chat2db.spi.jdbc.DefaultMetaService; + +public class DlcMetaData extends DefaultMetaService implements MetaData { +} diff --git a/chat2db-server/chat2db-plugins/chat2db-dlc/src/main/java/ai/chat2db/plugin/dlc/DlcPlugin.java b/chat2db-server/chat2db-plugins/chat2db-dlc/src/main/java/ai/chat2db/plugin/dlc/DlcPlugin.java new file mode 100644 index 000000000..d6ae9a555 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-dlc/src/main/java/ai/chat2db/plugin/dlc/DlcPlugin.java @@ -0,0 +1,25 @@ +package ai.chat2db.plugin.dlc; + +import ai.chat2db.spi.DBManage; +import ai.chat2db.spi.MetaData; +import ai.chat2db.spi.Plugin; +import ai.chat2db.spi.config.DBConfig; +import ai.chat2db.spi.util.FileUtils; + +public class DlcPlugin implements Plugin { + + @Override + public DBConfig getDBConfig() { + return FileUtils.readJsonValue(this.getClass(), "dlc.json", DBConfig.class); + } + + @Override + public MetaData getMetaData() { + return new DlcMetaData(); + } + + @Override + public DBManage getDBManage() { + return new DlcDBManage(); + } +} diff --git a/chat2db-server/chat2db-plugins/chat2db-dlc/src/main/java/ai/chat2db/plugin/dlc/dlc.json b/chat2db-server/chat2db-plugins/chat2db-dlc/src/main/java/ai/chat2db/plugin/dlc/dlc.json new file mode 100644 index 000000000..9a20be4f2 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-dlc/src/main/java/ai/chat2db/plugin/dlc/dlc.json @@ -0,0 +1,18 @@ +{ + "dbType": "DLC", + "supportDatabase": false, + "supportSchema": true, + "driverConfigList": [ + { + "url": "jdbc:dlc:dlc.tencentcloudapi.com?task_type=SQLTask&database_name=&datasource_connection=®ion=ap-guangzhou&data_engine_name=&spark_app_name=", + "custom": false, + "defaultDriver": true, + "downloadJdbcDriverUrls": [ + "https://dlc-jdbc-1304028854.cos.ap-beijing.myqcloud.com/dlc-jdbc-2.5.3-jar-with-dependencies.jar" + ], + "jdbcDriver": "dlc-jdbc-2.5.3-jar-with-dependencies.jar", + "jdbcDriverClass": "com.tencent.cloud.dlc.jdbc.DlcDriver" + } + ], + "name": "Tencent DLC" +} diff --git a/chat2db-server/chat2db-plugins/chat2db-dlc/src/main/resources/META-INF/services/ai.chat2db.spi.Plugin b/chat2db-server/chat2db-plugins/chat2db-dlc/src/main/resources/META-INF/services/ai.chat2db.spi.Plugin new file mode 100644 index 000000000..c5616f7e4 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-dlc/src/main/resources/META-INF/services/ai.chat2db.spi.Plugin @@ -0,0 +1 @@ +ai.chat2db.plugin.dlc.DlcPlugin diff --git a/chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/DMMetaData.java b/chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/DMMetaData.java index a2e514fad..33ab1336d 100644 --- a/chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/DMMetaData.java +++ b/chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/DMMetaData.java @@ -30,6 +30,7 @@ public List schemas(Connection connection, String databaseName) { List schemas = SQLExecutor.getInstance().schemas(connection, databaseName, null); return SortUtils.sortSchema(schemas, systemSchemas); } + @Override public String tableDDL(Connection connection, String databaseName, String schemaName, String tableName) { String selectObjectDDLSQL = String.format( "select dbms_metadata.get_ddl(%s, %s, %s) AS \"sql\" from dual", @@ -63,7 +64,7 @@ public Function function(Connection connection, @NotEmpty String databaseName, S Function function = new Function(); function.setDatabaseName(databaseName); function.setSchemaName(schemaName); - function.setFunctionName(functionName); + function.setName(functionName); function.setFunctionBody(sb.toString()); return function; @@ -83,7 +84,7 @@ public Procedure procedure(Connection connection, @NotEmpty String databaseName, Procedure procedure = new Procedure(); procedure.setDatabaseName(databaseName); procedure.setSchemaName(schemaName); - procedure.setProcedureName(procedureName); + procedure.setName(procedureName); procedure.setProcedureBody(sb.toString()); return procedure; }); @@ -102,7 +103,7 @@ public List triggers(Connection connection, String databaseName, String return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { while (resultSet.next()) { Trigger trigger = new Trigger(); - trigger.setTriggerName(resultSet.getString("TRIGGER_NAME")); + trigger.setName(resultSet.getString("TRIGGER_NAME")); trigger.setSchemaName(schemaName); trigger.setDatabaseName(databaseName); triggers.add(trigger); @@ -120,7 +121,7 @@ public Trigger trigger(Connection connection, @NotEmpty String databaseName, Str Trigger trigger = new Trigger(); trigger.setDatabaseName(databaseName); trigger.setSchemaName(schemaName); - trigger.setTriggerName(triggerName); + trigger.setName(triggerName); if (resultSet.next()) { trigger.setTriggerBody(resultSet.getString("TRIGGER_BODY")); } @@ -213,7 +214,7 @@ public SqlBuilder getSqlBuilder() { } @Override - public TableMeta getTableMeta(String databaseName, String schemaName, String tableName) { + public TableMeta getTableMeta(Connection connection, String databaseName, String schemaName) { return TableMeta.builder() .columnTypes(DMColumnTypeEnum.getTypes()) .charsets(Lists.newArrayList()) diff --git a/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2Meta.java b/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2Meta.java index f5c725614..70b08ad6a 100644 --- a/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2Meta.java +++ b/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2Meta.java @@ -109,7 +109,7 @@ public Function function(Connection connection, @NotEmpty String databaseName, S Function function = new Function(); function.setDatabaseName(databaseName); function.setSchemaName(schemaName); - function.setFunctionName(functionName); + function.setName(functionName); if (resultSet.next()) { function.setSpecificName(resultSet.getString("SPECIFIC_NAME")); function.setFunctionBody(resultSet.getString("ROUTINE_DEFINITION")); @@ -134,7 +134,7 @@ public List triggers(Connection connection, String databaseName, String return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { while (resultSet.next()) { Trigger trigger = new Trigger(); - trigger.setTriggerName(resultSet.getString("TRIGGER_NAME")); + trigger.setName(resultSet.getString("TRIGGER_NAME")); trigger.setSchemaName(schemaName); trigger.setDatabaseName(databaseName); triggers.add(trigger); @@ -152,7 +152,7 @@ public Trigger trigger(Connection connection, @NotEmpty String databaseName, Str Trigger trigger = new Trigger(); trigger.setDatabaseName(databaseName); trigger.setSchemaName(schemaName); - trigger.setTriggerName(triggerName); + trigger.setName(triggerName); if (resultSet.next()) { trigger.setTriggerBody(resultSet.getString("JAVA_CLASS")); } @@ -168,7 +168,7 @@ public Procedure procedure(Connection connection, @NotEmpty String databaseName, Procedure procedure = new Procedure(); procedure.setDatabaseName(databaseName); procedure.setSchemaName(schemaName); - procedure.setProcedureName(procedureName); + procedure.setName(procedureName); if (resultSet.next()) { procedure.setSpecificName(resultSet.getString("SPECIFIC_NAME")); procedure.setProcedureBody(resultSet.getString("ROUTINE_DEFINITION")); diff --git a/chat2db-server/chat2db-plugins/chat2db-hive/src/main/java/ai/chat2db/plugin/hive/hive.json b/chat2db-server/chat2db-plugins/chat2db-hive/src/main/java/ai/chat2db/plugin/hive/hive.json index 967bdd80c..0a98a7bff 100644 --- a/chat2db-server/chat2db-plugins/chat2db-hive/src/main/java/ai/chat2db/plugin/hive/hive.json +++ b/chat2db-server/chat2db-plugins/chat2db-hive/src/main/java/ai/chat2db/plugin/hive/hive.json @@ -1,7 +1,7 @@ { "dbType": "HIVE", - "supportDatabase": true, - "supportSchema": false, + "supportDatabase": false, + "supportSchema": true, "driverConfigList": [ { "url": "jdbc:hive2://localhost:10000/", diff --git a/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/KingBaseMetaData.java b/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/KingBaseMetaData.java index 5d85cd231..8f829b4a7 100644 --- a/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/KingBaseMetaData.java +++ b/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/KingBaseMetaData.java @@ -160,7 +160,7 @@ public Function function(Connection connection, @NotEmpty String databaseName, S Function function = new Function(); function.setDatabaseName(databaseName); function.setSchemaName(schemaName); - function.setFunctionName(functionName); + function.setName(functionName); if (resultSet.next()) { function.setFunctionBody(resultSet.getString("code")); } @@ -176,7 +176,7 @@ public Procedure procedure(Connection connection, @NotEmpty String databaseName, Procedure procedure = new Procedure(); procedure.setDatabaseName(databaseName); procedure.setSchemaName(schemaName); - procedure.setProcedureName(procedureName); + procedure.setName(procedureName); if (resultSet.next()) { procedure.setProcedureBody(resultSet.getString("code")); } @@ -190,7 +190,7 @@ public SqlBuilder getSqlBuilder() { } @Override - public TableMeta getTableMeta(String databaseName, String schemaName, String tableName) { + public TableMeta getTableMeta(Connection connection, String databaseName, String schemaName) { return TableMeta.builder() .columnTypes(KingBaseColumnTypeEnum.getTypes()) //.charsets(PostgreSQLCharsetEnum.getCharsets()) diff --git a/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/builder/KingBaseSqlBuilder.java b/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/builder/KingBaseSqlBuilder.java index bccae8957..fd82b0a1c 100644 --- a/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/builder/KingBaseSqlBuilder.java +++ b/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/builder/KingBaseSqlBuilder.java @@ -22,10 +22,14 @@ public String buildCreateTableSql(Table table) { script.append("\"").append(table.getName()).append("\"").append(" (").append(" ").append("\n"); // append column for (TableColumn column : table.getColumnList()) { - if (StringUtils.isBlank(column.getName()) || StringUtils.isBlank(column.getColumnType())) { + String columnType = column.getDataType(); + if (StringUtils.isBlank(columnType)) { + columnType = column.getColumnType(); + } + if (StringUtils.isBlank(column.getName()) || StringUtils.isBlank(columnType)) { continue; } - KingBaseColumnTypeEnum typeEnum = KingBaseColumnTypeEnum.getByType(column.getColumnType()); + KingBaseColumnTypeEnum typeEnum = KingBaseColumnTypeEnum.getByType(columnType); script.append("\t").append(typeEnum.buildCreateColumnSql(column)).append(",\n"); } Map> tableIndexMap = table.getIndexList().stream() @@ -69,7 +73,11 @@ public String buildCreateTableSql(Table table) { } List tableColumnList = table.getColumnList().stream().filter(v -> StringUtils.isNotBlank(v.getComment())).toList(); for (TableColumn tableColumn : tableColumnList) { - KingBaseColumnTypeEnum typeEnum = KingBaseColumnTypeEnum.getByType(tableColumn.getColumnType()); + String columnType = tableColumn.getDataType(); + if (StringUtils.isBlank(columnType)) { + columnType = tableColumn.getColumnType(); + } + KingBaseColumnTypeEnum typeEnum = KingBaseColumnTypeEnum.getByType(columnType); script.append(typeEnum.buildComment(tableColumn, typeEnum)).append("\n"); ; } @@ -108,7 +116,11 @@ public String buildModifyTaleSql(Table oldTable, Table newTable) { scriptModify.append("ALTER TABLE ").append("\"").append(newTable.getName()).append("\" \n"); // append modify column for (TableColumn tableColumn : newTable.getColumnList()) { - KingBaseColumnTypeEnum typeEnum = KingBaseColumnTypeEnum.getByType(tableColumn.getColumnType()); + String columnType = tableColumn.getDataType(); + if (StringUtils.isBlank(columnType)) { + columnType = tableColumn.getColumnType(); + } + KingBaseColumnTypeEnum typeEnum = KingBaseColumnTypeEnum.getByType(columnType); scriptModify.append("\t").append(typeEnum.buildModifyColumn(tableColumn)).append(",\n"); modify = true; @@ -144,7 +156,11 @@ public String buildModifyTaleSql(Table oldTable, Table newTable) { .append(newTable.getComment()).append("';\n"); } for (TableColumn tableColumn : newTable.getColumnList()) { - KingBaseColumnTypeEnum typeEnum = KingBaseColumnTypeEnum.getByType(tableColumn.getColumnType()); + String columnType = tableColumn.getDataType(); + if (StringUtils.isBlank(columnType)) { + columnType = tableColumn.getColumnType(); + } + KingBaseColumnTypeEnum typeEnum = KingBaseColumnTypeEnum.getByType(columnType); script.append(typeEnum.buildComment(tableColumn, typeEnum)).append("\n"); ; } diff --git a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlDBManage.java b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlDBManage.java index 2322dd669..b18a5af85 100644 --- a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlDBManage.java +++ b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlDBManage.java @@ -23,6 +23,12 @@ public void connectDatabase(Connection connection, String database) { + @Override + public void dropDatabase(Connection connection, String databaseName) { + String sql = "DROP DATABASE " + format(databaseName); + SQLExecutor.getInstance().execute(connection, sql, resultSet -> null); + } + @Override public void dropTable(Connection connection, String databaseName, String schemaName, String tableName) { String sql = "DROP TABLE "+ format(tableName); diff --git a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlMetaData.java b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlMetaData.java index 40a291955..6ef0b2fe7 100644 --- a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlMetaData.java +++ b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlMetaData.java @@ -5,7 +5,6 @@ import java.sql.SQLException; import java.util.*; import java.util.stream.Collectors; -import java.util.stream.Stream; import ai.chat2db.plugin.mysql.builder.MysqlSqlBuilder; import ai.chat2db.plugin.mysql.type.*; @@ -16,25 +15,83 @@ import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.SQLExecutor; import jakarta.validation.constraints.NotEmpty; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import static ai.chat2db.spi.util.SortUtils.sortDatabase; +@Slf4j public class MysqlMetaData extends DefaultMetaService implements MetaData { private List systemDatabases = Arrays.asList("information_schema", "performance_schema", "mysql", "sys"); + + private static final String SELECT_TABLES_SQL = "SELECT TABLE_NAME, TABLE_COMMENT, TABLE_ROWS, ENGINE, CREATE_TIME, UPDATE_TIME, AUTO_INCREMENT " + + "FROM information_schema.tables WHERE TABLE_SCHEMA = '%s' AND TABLE_TYPE IN ('BASE TABLE', 'SYSTEM TABLE')"; + + @Override + public List

tables(Connection connection, @NotEmpty String databaseName, String schemaName, String tableName) { + List
tables = new ArrayList<>(); + + String sql = String.format(SELECT_TABLES_SQL, databaseName); + if (StringUtils.isNotBlank(tableName)) { + sql += String.format(" AND TABLE_NAME = '%s'", tableName); + } + sql += " ORDER BY TABLE_NAME"; + + try { + SQLExecutor.getInstance().execute(connection, sql, resultSet -> { + while (resultSet.next()) { + Table table = Table.builder() + .name(resultSet.getString("TABLE_NAME")) + .comment(resultSet.getString("TABLE_COMMENT")) + .databaseName(databaseName) + .schemaName(schemaName) + .type("BASE TABLE") + .engine(resultSet.getString("ENGINE")) + .build(); + + // 设置预估行数(InnoDB 等引擎可能返回 NULL) + long rowCount = resultSet.getLong("TABLE_ROWS"); + if (!resultSet.wasNull()) { + table.setRowCount(rowCount); + } + + // 设置自增列的下一个自增值(可能为 NULL) + long autoIncrement = resultSet.getLong("AUTO_INCREMENT"); + if (!resultSet.wasNull()) { + table.setIncrementValue(autoIncrement); + } + + tables.add(table); + } + return null; + }); + } catch (Exception e) { + // 如果查询失败,回退到 JDBC 元数据方式 + return SQLExecutor.getInstance().tables(connection, databaseName, schemaName, tableName, + new String[]{"TABLE", "SYSTEM TABLE"}); + } + + return tables; + } + @Override public List databases(Connection connection) { List databases = SQLExecutor.getInstance().databases(connection); - return sortDatabase(databases,systemDatabases,connection); + return sortDatabase(databases, systemDatabases, connection); } @Override public String tableDDL(Connection connection, @NotEmpty String databaseName, String schemaName, @NotEmpty String tableName) { - String sql = "SHOW CREATE TABLE " + format(databaseName) + "." - + format(tableName); + String sql; + if (StringUtils.isEmpty(databaseName)) { + sql = "SHOW CREATE TABLE " + format(tableName); + } else { + sql = "SHOW CREATE TABLE " + format(databaseName) + "." + + format(tableName); + } return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { if (resultSet.next()) { return resultSet.getString("Create Table"); @@ -57,20 +114,55 @@ public static String format(String tableName) { public Function function(Connection connection, @NotEmpty String databaseName, String schemaName, String functionName) { + Function function = new Function(); + function.setDatabaseName(databaseName); + function.setSchemaName(schemaName); + function.setName(functionName); + + // 首先尝试使用 information_schema.routines 获取信息 String sql = String.format(ROUTINES_SQL, "FUNCTION", databaseName, functionName); - return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { - Function function = new Function(); - function.setDatabaseName(databaseName); - function.setSchemaName(schemaName); - function.setFunctionName(functionName); - if (resultSet.next()) { - function.setSpecificName(resultSet.getString("SPECIFIC_NAME")); - function.setRemarks(resultSet.getString("ROUTINE_COMMENT")); - function.setFunctionBody(resultSet.getString("ROUTINE_DEFINITION")); + log.info("[MySQL] Querying function detail: {}", sql); + + try { + SQLExecutor.getInstance().execute(connection, sql, resultSet -> { + if (resultSet.next()) { + function.setSpecificName(resultSet.getString("SPECIFIC_NAME")); + function.setComment(resultSet.getString("ROUTINE_COMMENT")); + function.setFunctionBody(resultSet.getString("ROUTINE_DEFINITION")); + log.info("[MySQL] Function {} found, body length: {}", functionName, + function.getFunctionBody() != null ? function.getFunctionBody().length() : 0); + } else { + log.warn("[MySQL] Function {} not found in information_schema.routines", functionName); + } + return null; + }); + } catch (Exception e) { + log.error("[MySQL] Failed to query function from information_schema: {}", e.getMessage()); + } + + // 如果 ROUTINE_DEFINITION 为空,尝试使用 SHOW CREATE FUNCTION + if (StringUtils.isBlank(function.getFunctionBody())) { + String showCreateSql = "SHOW CREATE FUNCTION `" + databaseName + "`.`" + functionName + "`"; + log.info("[MySQL] Trying SHOW CREATE FUNCTION: {}", showCreateSql); + + try { + SQLExecutor.getInstance().execute(connection, showCreateSql, resultSet -> { + if (resultSet.next()) { + String createFunc = resultSet.getString("Create Function"); + if (StringUtils.isNotBlank(createFunc)) { + function.setFunctionBody(createFunc); + log.info("[MySQL] Got function body from SHOW CREATE FUNCTION, length: {}", + createFunc.length()); + } + } + return null; + }); + } catch (Exception e) { + log.error("[MySQL] SHOW CREATE FUNCTION failed: {}", e.getMessage()); } - return function; - }); + } + return function; } private static String TRIGGER_SQL @@ -87,7 +179,7 @@ public List triggers(Connection connection, String databaseName, String return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { while (resultSet.next()) { Trigger trigger = new Trigger(); - trigger.setTriggerName(resultSet.getString("TRIGGER_NAME")); + trigger.setName(resultSet.getString("TRIGGER_NAME")); trigger.setSchemaName(schemaName); trigger.setDatabaseName(databaseName); triggers.add(trigger); @@ -96,59 +188,190 @@ public List triggers(Connection connection, String databaseName, String }); } + /** + * MySQL 的 JDBC 驱动 getProcedures() 会同时返回 FUNCTION 和 PROCEDURE + * 这里使用自定义 SQL 只返回 PROCEDURE 类型 + */ + @Override + public List procedures(Connection connection, String databaseName, String schemaName) { + String sql = "SELECT SPECIFIC_NAME, ROUTINE_COMMENT, ROUTINE_SCHEMA " + + "FROM information_schema.routines " + + "WHERE ROUTINE_TYPE = 'PROCEDURE' AND ROUTINE_SCHEMA = '" + databaseName + "' " + + "ORDER BY ROUTINE_NAME"; + + log.info("[MySQL] Querying procedures: {}", sql); + + final List resultHolder = new ArrayList<>(); + try { + SQLExecutor.getInstance().execute(connection, sql, resultSet -> { + while (resultSet.next()) { + Procedure procedure = new Procedure(); + procedure.setName(resultSet.getString("SPECIFIC_NAME")); + procedure.setComment(resultSet.getString("ROUTINE_COMMENT")); + procedure.setDatabaseName(databaseName); + procedure.setSchemaName(schemaName); + resultHolder.add(procedure); + } + log.info("[MySQL] Found {} procedures", resultHolder.size()); + return null; + }); + } catch (Exception e) { + log.error("[MySQL] Failed to query procedures: {}", e.getMessage()); + // 如果查询失败,回退到 JDBC 元数据方式 + List allProcedures = super.procedures(connection, databaseName, schemaName); + if (allProcedures != null) { + return allProcedures; + } + } + + return resultHolder; + } + + /** + * MySQL 的 JDBC 驱动 getFunctions() 可能返回不准确 + * 这里使用自定义 SQL 只返回 FUNCTION 类型 + */ + @Override + public List functions(Connection connection, String databaseName, String schemaName) { + String sql = "SELECT SPECIFIC_NAME, ROUTINE_COMMENT, ROUTINE_SCHEMA " + + "FROM information_schema.routines " + + "WHERE ROUTINE_TYPE = 'FUNCTION' AND ROUTINE_SCHEMA = '" + databaseName + "' " + + "ORDER BY ROUTINE_NAME"; + + log.info("[MySQL] Querying functions: {}", sql); + + final List resultHolder = new ArrayList<>(); + try { + SQLExecutor.getInstance().execute(connection, sql, resultSet -> { + while (resultSet.next()) { + ai.chat2db.spi.model.Function function = new ai.chat2db.spi.model.Function(); + function.setName(resultSet.getString("SPECIFIC_NAME")); + function.setComment(resultSet.getString("ROUTINE_COMMENT")); + function.setDatabaseName(databaseName); + function.setSchemaName(schemaName); + resultHolder.add(function); + } + log.info("[MySQL] Found {} functions", resultHolder.size()); + return null; + }); + } catch (Exception e) { + log.error("[MySQL] Failed to query functions: {}", e.getMessage()); + // 如果查询失败,回退到 JDBC 元数据方式 + List allFunctions = super.functions(connection, databaseName, schemaName); + if (allFunctions != null) { + return allFunctions; + } + } + + return resultHolder; + } + @Override public Trigger trigger(Connection connection, @NotEmpty String databaseName, String schemaName, String triggerName) { + Trigger trigger = new Trigger(); + trigger.setDatabaseName(databaseName); + trigger.setSchemaName(schemaName); + trigger.setName(triggerName); + String sql = String.format(TRIGGER_SQL, databaseName, triggerName); - return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { - Trigger trigger = new Trigger(); - trigger.setDatabaseName(databaseName); - trigger.setSchemaName(schemaName); - trigger.setTriggerName(triggerName); - if (resultSet.next()) { - trigger.setTriggerBody(resultSet.getString("ACTION_STATEMENT")); - } - return trigger; - }); + log.info("[MySQL] Querying trigger detail: {}", sql); + + try { + SQLExecutor.getInstance().execute(connection, sql, resultSet -> { + if (resultSet.next()) { + trigger.setEventManipulation(resultSet.getString("EVENT_MANIPULATION")); + trigger.setTriggerBody(resultSet.getString("ACTION_STATEMENT")); + log.info("[MySQL] Trigger {} found, body length: {}", triggerName, + trigger.getTriggerBody() != null ? trigger.getTriggerBody().length() : 0); + } else { + log.warn("[MySQL] Trigger {} not found in information_schema.triggers", triggerName); + } + return null; + }); + } catch (Exception e) { + log.error("[MySQL] Failed to query trigger from information_schema: {}", e.getMessage()); + } + + return trigger; } @Override public Procedure procedure(Connection connection, @NotEmpty String databaseName, String schemaName, String procedureName) { + Procedure procedure = new Procedure(); + procedure.setDatabaseName(databaseName); + procedure.setSchemaName(schemaName); + procedure.setName(procedureName); + + // 首先尝试使用 information_schema.routines 获取信息 String sql = String.format(ROUTINES_SQL, "PROCEDURE", databaseName, procedureName); - return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { - Procedure procedure = new Procedure(); - procedure.setDatabaseName(databaseName); - procedure.setSchemaName(schemaName); - procedure.setProcedureName(procedureName); - if (resultSet.next()) { - procedure.setSpecificName(resultSet.getString("SPECIFIC_NAME")); - procedure.setRemarks(resultSet.getString("ROUTINE_COMMENT")); - procedure.setProcedureBody(resultSet.getString("ROUTINE_DEFINITION")); + log.info("[MySQL] Querying procedure detail: {}", sql); + + try { + SQLExecutor.getInstance().execute(connection, sql, resultSet -> { + if (resultSet.next()) { + procedure.setSpecificName(resultSet.getString("SPECIFIC_NAME")); + procedure.setComment(resultSet.getString("ROUTINE_COMMENT")); + procedure.setProcedureBody(resultSet.getString("ROUTINE_DEFINITION")); + log.info("[MySQL] Procedure {} found, body length: {}", procedureName, + procedure.getProcedureBody() != null ? procedure.getProcedureBody().length() : 0); + } else { + log.warn("[MySQL] Procedure {} not found in information_schema.routines", procedureName); + } + return null; + }); + } catch (Exception e) { + log.error("[MySQL] Failed to query procedure from information_schema: {}", e.getMessage()); + } + + // 如果 ROUTINE_DEFINITION 为空,尝试使用 SHOW CREATE PROCEDURE + if (StringUtils.isBlank(procedure.getProcedureBody())) { + String showCreateSql = "SHOW CREATE PROCEDURE `" + databaseName + "`.`" + procedureName + "`"; + log.info("[MySQL] Trying SHOW CREATE PROCEDURE: {}", showCreateSql); + + try { + SQLExecutor.getInstance().execute(connection, showCreateSql, resultSet -> { + if (resultSet.next()) { + String createProc = resultSet.getString("Create Procedure"); + if (StringUtils.isNotBlank(createProc)) { + procedure.setProcedureBody(createProc); + log.info("[MySQL] Got procedure body from SHOW CREATE PROCEDURE, length: {}", + createProc.length()); + } + } + return null; + }); + } catch (Exception e) { + log.error("[MySQL] SHOW CREATE PROCEDURE failed: {}", e.getMessage()); } - return procedure; - }); + } + + return procedure; } - private static String SELECT_TABLE_COLUMNS = "SELECT * FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s' order by ORDINAL_POSITION"; + private static final String SELECT_TABLE_COLUMNS_TEMPLATE = "SELECT * FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '%s' %s ORDER BY ORDINAL_POSITION"; @Override public List columns(Connection connection, String databaseName, String schemaName, String tableName) { - String sql = String.format(SELECT_TABLE_COLUMNS, databaseName, tableName); + // 构建 SQL 查询语句 + String tableCondition = (tableName != null) ? String.format("AND TABLE_NAME = '%s'", tableName) : ""; + String sql = String.format(SELECT_TABLE_COLUMNS_TEMPLATE, databaseName, tableCondition); + List tableColumns = new ArrayList<>(); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { while (resultSet.next()) { TableColumn column = new TableColumn(); column.setDatabaseName(databaseName); - column.setTableName(tableName); + column.setTableName(resultSet.getString("TABLE_NAME")); column.setOldName(resultSet.getString("COLUMN_NAME")); column.setName(resultSet.getString("COLUMN_NAME")); - //column.setColumnType(resultSet.getString("COLUMN_TYPE")); - column.setColumnType(resultSet.getString("DATA_TYPE").toUpperCase()); - //column.setDataType(resultSet.getInt("DATA_TYPE")); + column.setColumnType(resultSet.getString("COLUMN_TYPE")); + column.setDataType(resultSet.getString("DATA_TYPE")); column.setDefaultValue(resultSet.getString("COLUMN_DEFAULT")); - column.setAutoIncrement(resultSet.getString("EXTRA").contains("auto_increment")); + String extra = resultSet.getString("EXTRA"); + column.setAutoIncrement(extra != null && extra.contains("auto_increment")); column.setComment(resultSet.getString("COLUMN_COMMENT")); column.setPrimaryKey("PRI".equalsIgnoreCase(resultSet.getString("COLUMN_KEY"))); column.setNullable("YES".equalsIgnoreCase(resultSet.getString("IS_NULLABLE")) ? 1 : 0); @@ -163,6 +386,7 @@ public List columns(Connection connection, String databaseName, Str }); } + private void setColumnSize(TableColumn column, String columnType) { try { if (columnType.contains("(")) { @@ -208,52 +432,188 @@ public Table view(Connection connection, String databaseName, String schemaName, } + /** + * 批量查询索引的 SQL 模板 + * 使用 information_schema.STATISTICS 可以一次性获取所有表的索引信息 + */ + private static final String SELECT_INDEXES_SQL = + "SELECT TABLE_NAME, INDEX_NAME, NON_UNIQUE, INDEX_TYPE, COLUMN_NAME, SEQ_IN_INDEX, " + + "COLLATION, CARDINALITY, SUB_PART, INDEX_COMMENT " + + "FROM information_schema.STATISTICS " + + "WHERE TABLE_SCHEMA = '%s' %s " + + "ORDER BY TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX"; + @Override public List indexes(Connection connection, String databaseName, String schemaName, String tableName) { - StringBuilder queryBuf = new StringBuilder("SHOW INDEX FROM "); - queryBuf.append("`").append(tableName).append("`"); - queryBuf.append(" FROM "); - queryBuf.append("`").append(databaseName).append("`"); - return SQLExecutor.getInstance().execute(connection, queryBuf.toString(), resultSet -> { - LinkedHashMap map = new LinkedHashMap(); + // 构建 SQL 查询:支持单表查询和批量查询 + String tableCondition = (tableName != null) + ? String.format("AND TABLE_NAME = '%s'", tableName) + : ""; + String sql = String.format(SELECT_INDEXES_SQL, databaseName, tableCondition); + + return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { + // 使用嵌套 Map:外层 key 为 tableName,内层 key 为 indexName + Map> tableIndexMap = new HashMap<>(); + while (resultSet.next()) { - String keyName = resultSet.getString("Key_name"); - TableIndex tableIndex = map.get(keyName); + String currentTableName = resultSet.getString("TABLE_NAME"); + String keyName = resultSet.getString("INDEX_NAME"); + + // 获取或创建当前表的索引映射 + LinkedHashMap indexMap = tableIndexMap.computeIfAbsent( + currentTableName, k -> new LinkedHashMap<>()); + + TableIndex tableIndex = indexMap.get(keyName); if (tableIndex != null) { + // 索引已存在,添加列信息 List columnList = tableIndex.getColumnList(); columnList.add(getTableIndexColumn(resultSet)); - columnList = columnList.stream().sorted(Comparator.comparing(TableIndexColumn::getOrdinalPosition)) - .collect(Collectors.toList()); - tableIndex.setColumnList(columnList); + // 保持按 Seq_in_index 排序 + columnList.sort(Comparator.comparing(TableIndexColumn::getOrdinalPosition)); } else { - TableIndex index = new TableIndex(); - index.setDatabaseName(databaseName); - index.setSchemaName(schemaName); - index.setTableName(tableName); - index.setName(keyName); - index.setUnique(!resultSet.getBoolean("Non_unique")); - index.setType(resultSet.getString("Index_type")); - index.setComment(resultSet.getString("Index_comment")); - List tableIndexColumns = new ArrayList<>(); - tableIndexColumns.add(getTableIndexColumn(resultSet)); - index.setColumnList(tableIndexColumns); - if ("PRIMARY".equalsIgnoreCase(keyName)) { - index.setType(MysqlIndexTypeEnum.PRIMARY_KEY.getName()); - } else if (index.getUnique()) { - index.setType(MysqlIndexTypeEnum.UNIQUE.getName()); - } else if ("SPATIAL".equalsIgnoreCase(index.getType())) { - index.setType(MysqlIndexTypeEnum.SPATIAL.getName()); - } else if ("FULLTEXT".equalsIgnoreCase(index.getType())) { - index.setType(MysqlIndexTypeEnum.FULLTEXT.getName()); - } else { - index.setType(MysqlIndexTypeEnum.NORMAL.getName()); - } - map.put(keyName, index); + // 新索引,创建并初始化 + TableIndex index = createTableIndex(resultSet, databaseName, schemaName, currentTableName, keyName); + indexMap.put(keyName, index); + } + } + + // 如果指定了 tableName,只返回该表的索引 + if (tableName != null) { + LinkedHashMap indexMap = tableIndexMap.get(tableName); + return indexMap != null + ? new ArrayList<>(indexMap.values()) + : Collections.emptyList(); + } + + // 否则返回所有表的索引 + List allIndexes = new ArrayList<>(); + tableIndexMap.values().forEach(indexMap -> allIndexes.addAll(indexMap.values())); + return allIndexes; + }); + } + + /** + * 从 ResultSet 创建 TableIndex 对象 + */ + private TableIndex createTableIndex(ResultSet resultSet, String databaseName, String schemaName, + String tableName, String keyName) throws SQLException { + TableIndex index = new TableIndex(); + index.setDatabaseName(databaseName); + index.setSchemaName(schemaName); + index.setTableName(tableName); + index.setName(keyName); + index.setUnique(!resultSet.getBoolean("NON_UNIQUE")); + + String indexType = resultSet.getString("INDEX_TYPE"); + index.setComment(resultSet.getString("INDEX_COMMENT")); + + List tableIndexColumns = new ArrayList<>(); + tableIndexColumns.add(getTableIndexColumn(resultSet)); + index.setColumnList(tableIndexColumns); + + // 根据索引名称和属性判断索引类型 + if ("PRIMARY".equalsIgnoreCase(keyName)) { + index.setType(MysqlIndexTypeEnum.PRIMARY_KEY.getName()); + } else if (index.getUnique()) { + index.setType(MysqlIndexTypeEnum.UNIQUE.getName()); + } else if ("SPATIAL".equalsIgnoreCase(indexType)) { + index.setType(MysqlIndexTypeEnum.SPATIAL.getName()); + } else if ("FULLTEXT".equalsIgnoreCase(indexType)) { + index.setType(MysqlIndexTypeEnum.FULLTEXT.getName()); + } else { + index.setType(MysqlIndexTypeEnum.NORMAL.getName()); + } + + return index; + } + + /** + * 批量查询外键的 SQL 模板 + * 使用 information_schema.KEY_COLUMN_USAGE 和 REFERENTIAL_CONSTRAINTS 可以一次性获取所有表的外键信息 + */ + private static final String SELECT_FOREIGN_KEYS_SQL = + "SELECT kcu.TABLE_NAME, kcu.CONSTRAINT_NAME, kcu.COLUMN_NAME, " + + "kcu.REFERENCED_TABLE_NAME, kcu.REFERENCED_COLUMN_NAME, " + + "kcu.ORDINAL_POSITION, rc.UPDATE_RULE, rc.DELETE_RULE " + + "FROM information_schema.KEY_COLUMN_USAGE kcu " + + "JOIN information_schema.REFERENTIAL_CONSTRAINTS rc " + + " ON kcu.CONSTRAINT_NAME = rc.CONSTRAINT_NAME " + + " AND kcu.TABLE_SCHEMA = rc.CONSTRAINT_SCHEMA " + + "WHERE kcu.TABLE_SCHEMA = '%s' %s " + + "AND kcu.REFERENCED_TABLE_NAME IS NOT NULL " + + "ORDER BY kcu.TABLE_NAME, kcu.CONSTRAINT_NAME, kcu.ORDINAL_POSITION"; + + @Override + public List foreignKeys(Connection connection, String databaseName, String schemaName, String tableName) { + // 构建 SQL 查询:支持单表查询和批量查询 + String tableCondition = (tableName != null) + ? String.format("AND kcu.TABLE_NAME = '%s'", tableName) + : ""; + String sql = String.format(SELECT_FOREIGN_KEYS_SQL, databaseName, tableCondition); + + return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { + // 使用 Map 存储外键:key 为 "tableName.constraintName" + Map foreignKeyMap = new LinkedHashMap<>(); + + while (resultSet.next()) { + String currentTableName = resultSet.getString("TABLE_NAME"); + String constraintName = resultSet.getString("CONSTRAINT_NAME"); + String columnName = resultSet.getString("COLUMN_NAME"); + String referencedTableName = resultSet.getString("REFERENCED_TABLE_NAME"); + String referencedColumnName = resultSet.getString("REFERENCED_COLUMN_NAME"); + + // 构建唯一键 + String key = currentTableName + "." + constraintName; + + ForeignKey foreignKey = foreignKeyMap.get(key); + if (foreignKey == null) { + // 创建新的外键记录 + foreignKey = new ForeignKey(); + foreignKey.setDatabaseName(databaseName); + foreignKey.setSchemaName(schemaName); + foreignKey.setTableName(currentTableName); + foreignKey.setName(constraintName); + foreignKey.setReferencedTable(referencedTableName); + foreignKey.setColumn(columnName); + foreignKey.setReferencedColumn(referencedColumnName); + + // 获取更新和删除规则 + String updateRule = resultSet.getString("UPDATE_RULE"); + String deleteRule = resultSet.getString("DELETE_RULE"); + foreignKey.setUpdateRule(convertRuleToInt(updateRule)); + foreignKey.setDeleteRule(convertRuleToInt(deleteRule)); + + foreignKeyMap.put(key, foreignKey); + } else { + // 如果是复合外键(多列),只添加第一列的信息 + // 注意:当前 ForeignKey 模型只支持单列,多列情况需要扩展模型 + // 这里保持与原有实现兼容 } } - return map.values().stream().collect(Collectors.toList()); + + return new ArrayList<>(foreignKeyMap.values()); }); + } + /** + * 将规则名称转换为整数常量 + * 对应 java.sql.DatabaseMetaData 中的常量 + */ + private int convertRuleToInt(String rule) { + if (rule == null) { + return java.sql.DatabaseMetaData.importedKeyNoAction; + } + switch (rule.toUpperCase()) { + case "CASCADE": + return java.sql.DatabaseMetaData.importedKeyCascade; + case "SET NULL": + return java.sql.DatabaseMetaData.importedKeySetNull; + case "RESTRICT": + return java.sql.DatabaseMetaData.importedKeyRestrict; + case "NO ACTION": + default: + return java.sql.DatabaseMetaData.importedKeyNoAction; + } } private TableIndexColumn getTableIndexColumn(ResultSet resultSet) throws SQLException { @@ -278,7 +638,7 @@ public SqlBuilder getSqlBuilder() { } @Override - public TableMeta getTableMeta(String databaseName, String schemaName, String tableName) { + public TableMeta getTableMeta(Connection connection, String databaseName, String schemaName) { return TableMeta.builder() .columnTypes(MysqlColumnTypeEnum.getTypes()) .charsets(MysqlCharsetEnum.getCharsets()) diff --git a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/builder/MysqlSqlBuilder.java b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/builder/MysqlSqlBuilder.java index 1a97c843b..4600737ab 100644 --- a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/builder/MysqlSqlBuilder.java +++ b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/builder/MysqlSqlBuilder.java @@ -1,148 +1,252 @@ package ai.chat2db.plugin.mysql.builder; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.StringUtils; + import ai.chat2db.plugin.mysql.type.MysqlColumnTypeEnum; import ai.chat2db.plugin.mysql.type.MysqlIndexTypeEnum; +import ai.chat2db.plugin.mysql.util.MysqlSqlUtils; import ai.chat2db.spi.SqlBuilder; +import ai.chat2db.spi.enums.EditStatus; import ai.chat2db.spi.jdbc.DefaultSqlBuilder; import ai.chat2db.spi.model.Database; +import ai.chat2db.spi.model.ForeignKey; +import ai.chat2db.spi.model.Header; import ai.chat2db.spi.model.Table; import ai.chat2db.spi.model.TableColumn; import ai.chat2db.spi.model.TableIndex; +import ai.chat2db.spi.MetaData; import cn.hutool.core.util.ArrayUtil; -import org.apache.commons.lang3.StringUtils; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; - public class MysqlSqlBuilder extends DefaultSqlBuilder implements SqlBuilder { + @Override public String buildCreateTableSql(Table table) { StringBuilder script = new StringBuilder(); + if (StringUtils.isNotBlank(table.getAiComment())) { + script.append(" -- ").append(table.getAiComment()).append("\n"); + } script.append("CREATE TABLE "); if (StringUtils.isNotBlank(table.getDatabaseName())) { script.append("`").append(table.getDatabaseName()).append("`").append("."); } script.append("`").append(table.getName()).append("`").append(" (").append("\n"); - // append column - for (TableColumn column : table.getColumnList()) { - if (StringUtils.isBlank(column.getName()) || StringUtils.isBlank(column.getColumnType())) { + appendColumns(script, table.getColumnList(), + hasPrimaryKeyIndex(table.getIndexList()) || getPrimaryKeyColumns(table.getColumnList()).size() > 1); + appendIndexes(script, table.getIndexList()); + appendForeignKeys(script, table.getForeignKeyList()); + appendVirtualForeignKeys(script, table.getVirtualForeignKeyList()); + + if (script.length() > 2) { + script.setLength(script.length() - 2); + } + script.append("\n)"); + appendTableAttributes(script, table); + script.append(";"); + + return script.toString(); + } + + // 添加列的方法 + @Override + protected void appendColumns(StringBuilder script, List columns) { + appendColumns(script, columns, shouldSuppressInlinePrimaryKey(columns)); + } + + private void appendColumns(StringBuilder script, List columns, boolean suppressInlinePrimaryKey) { + if (columns == null) { + return; + } + Set primaryKeyColumns = getPrimaryKeyColumns(columns); + for (TableColumn column : columns) { + if (StringUtils.isBlank(column.getName())) { + continue; + } + MysqlColumnTypeEnum typeEnum = MysqlColumnTypeEnum.getColumnTypeEnum(column); + if (typeEnum == null) { continue; } - MysqlColumnTypeEnum typeEnum = MysqlColumnTypeEnum.getByType(column.getColumnType()); + Boolean originalPrimaryKey = column.getPrimaryKey(); + if (suppressInlinePrimaryKey && primaryKeyColumns.contains(column.getName())) { + column.setPrimaryKey(false); + } script.append("\t").append(typeEnum.buildCreateColumnSql(column)).append(",\n"); + if (suppressInlinePrimaryKey && primaryKeyColumns.contains(column.getName())) { + column.setPrimaryKey(originalPrimaryKey); + } + } + } + + private boolean shouldSuppressInlinePrimaryKey(List columns) { + if (columns == null) { + return false; + } + Set primaryKeyColumns = getPrimaryKeyColumns(columns); + boolean hasPrimaryKeyIndexMetadata = columns.stream() + .anyMatch(column -> Boolean.TRUE.equals(column.getPrimaryKey()) + && StringUtils.isNotBlank(column.getPrimaryKeyName())); + return hasPrimaryKeyIndexMetadata || primaryKeyColumns.size() > 1; + } + + private Set getPrimaryKeyColumns(List columns) { + Set primaryKeyColumns = new HashSet<>(); + if (columns == null) { + return primaryKeyColumns; + } + for (TableColumn column : columns) { + if (Boolean.TRUE.equals(column.getPrimaryKey()) && StringUtils.isNotBlank(column.getName())) { + primaryKeyColumns.add(column.getName()); + } } + return primaryKeyColumns; + } - // append primary key and index - for (TableIndex tableIndex : table.getIndexList()) { + // 添加索引的方法 + @Override + protected void appendIndexes(StringBuilder script, List indexes) { + if (indexes == null) { + return; + } + for (TableIndex tableIndex : indexes) { if (StringUtils.isBlank(tableIndex.getName()) || StringUtils.isBlank(tableIndex.getType())) { continue; } MysqlIndexTypeEnum mysqlIndexTypeEnum = MysqlIndexTypeEnum.getByType(tableIndex.getType()); - script.append("\t").append("").append(mysqlIndexTypeEnum.buildIndexScript(tableIndex)).append(",\n"); + if (mysqlIndexTypeEnum == null) { + continue; + } + script.append("\t").append(mysqlIndexTypeEnum.buildIndexScript(tableIndex)).append(",\n"); } + } - script = new StringBuilder(script.substring(0, script.length() - 2)); - script.append("\n)"); - + private boolean hasPrimaryKeyIndex(List indexes) { + if (indexes == null) { + return false; + } + return indexes.stream() + .anyMatch(index -> MysqlIndexTypeEnum.PRIMARY_KEY.getName().equalsIgnoreCase(index.getType())); + } + // 添加表的其他属性的方法 + @Override + protected void appendTableAttributes(StringBuilder script, Table table) { if (StringUtils.isNotBlank(table.getEngine())) { script.append(" ENGINE=").append(table.getEngine()); } - if (StringUtils.isNotBlank(table.getCharset())) { script.append(" DEFAULT CHARACTER SET=").append(table.getCharset()); } - if (StringUtils.isNotBlank(table.getCollate())) { script.append(" COLLATE=").append(table.getCollate()); } - if (table.getIncrementValue() != null) { script.append(" AUTO_INCREMENT=").append(table.getIncrementValue()); } - if (StringUtils.isNotBlank(table.getComment())) { - script.append(" COMMENT='").append(table.getComment()).append("'"); + script.append(" COMMENT=").append(MysqlSqlUtils.quoteString(table.getComment())); } - if (StringUtils.isNotBlank(table.getPartition())) { script.append(" \n").append(table.getPartition()); } - script.append(";"); - - return script.toString(); } @Override - public String buildModifyTaleSql(Table oldTable, Table newTable) { - StringBuilder script = new StringBuilder(); - script.append("ALTER TABLE "); - if (StringUtils.isNotBlank(oldTable.getDatabaseName())) { - script.append("`").append(oldTable.getDatabaseName()).append("`").append("."); - } - script.append("`").append(oldTable.getName()).append("`").append("\n"); + protected void modifyTableNameAndComment(StringBuilder script, Table oldTable, Table newTable) { if (!StringUtils.equalsIgnoreCase(oldTable.getName(), newTable.getName())) { - script.append("\t").append("RENAME TO ").append("`").append(newTable.getName()).append("`").append(",\n"); + script.append("\t").append("RENAME TO `").append(newTable.getName()).append("`,\n"); } if (!StringUtils.equalsIgnoreCase(oldTable.getComment(), newTable.getComment())) { - script.append("\t").append("COMMENT=").append("'").append(newTable.getComment()).append("'").append(",\n"); + script.append("\t").append("COMMENT=").append(MysqlSqlUtils.quoteString(newTable.getComment())).append(",\n"); } - if (oldTable.getIncrementValue() != newTable.getIncrementValue()) { + if (newTable.getIncrementValue() != null + && !Objects.equals(oldTable.getIncrementValue(), newTable.getIncrementValue())) { script.append("\t").append("AUTO_INCREMENT=").append(newTable.getIncrementValue()).append(",\n"); } + } - // append modify column + @Override + // 修改列的方法 + protected void modifyColumns(StringBuilder script, Table oldTable, Table newTable) { for (TableColumn tableColumn : newTable.getColumnList()) { - if (StringUtils.isNotBlank(tableColumn.getEditStatus()) && StringUtils.isNotBlank(tableColumn.getColumnType()) && StringUtils.isNotBlank(tableColumn.getName())) { - MysqlColumnTypeEnum typeEnum = MysqlColumnTypeEnum.getByType(tableColumn.getColumnType()); + if (StringUtils.isNotBlank(tableColumn.getEditStatus()) + && StringUtils.isNotBlank(tableColumn.getName())) { + MysqlColumnTypeEnum typeEnum = MysqlColumnTypeEnum.getColumnTypeEnum(tableColumn); + if (typeEnum == null) { + continue; + } script.append("\t").append(typeEnum.buildModifyColumn(tableColumn)).append(",\n"); } } + } - // append modify index + @Override + // 修改索引的方法 + protected void modifyIndexes(StringBuilder script, Table oldTable, Table newTable) { for (TableIndex tableIndex : newTable.getIndexList()) { if (StringUtils.isNotBlank(tableIndex.getEditStatus()) && StringUtils.isNotBlank(tableIndex.getType())) { MysqlIndexTypeEnum mysqlIndexTypeEnum = MysqlIndexTypeEnum.getByType(tableIndex.getType()); script.append("\t").append(mysqlIndexTypeEnum.buildModifyIndex(tableIndex)).append(",\n"); } } + } - // append reorder column - script.append(buildGenerateReorderColumnSql(oldTable, newTable)); - - if (script.length() > 2) { - script = new StringBuilder(script.substring(0, script.length() - 2)); - script.append(";"); + @Override + // 修改外键的方法 + protected void modifyForeignKeys(StringBuilder script, Table oldTable, Table newTable) { + if (newTable.getForeignKeyList() == null) { + return; + } + for (ForeignKey newForeignKey : newTable.getForeignKeyList()) { + if (EditStatus.DELETE.name().equals(newForeignKey.getEditStatus())) { + script.append("\t").append("DROP FOREIGN KEY `").append(newForeignKey.getName()).append("`,\n"); + } else if (EditStatus.ADD.name().equals(newForeignKey.getEditStatus())) { + script.append("\t").append("ADD CONSTRAINT `").append(newForeignKey.getName()).append("` ") + .append("FOREIGN KEY (`").append(newForeignKey.getColumn()).append("`) ") + .append("REFERENCES `").append(newForeignKey.getReferencedTable()).append("` (`") + .append(newForeignKey.getReferencedColumn()).append("`),\n"); + } else if (EditStatus.MODIFY.name().equals(newForeignKey.getEditStatus())) { + // 处理修改的外键 + script.append("\t").append("DROP FOREIGN KEY `").append(newForeignKey.getName()).append("`,\n"); + script.append("\t").append("ADD CONSTRAINT `").append(newForeignKey.getName()).append("` ") + .append("FOREIGN KEY (`").append(newForeignKey.getColumn()).append("`) ") + .append("REFERENCES `").append(newForeignKey.getReferencedTable()).append("` (`") + .append(newForeignKey.getReferencedColumn()).append("`),\n"); + } } - - return script.toString(); } - @Override public String pageLimit(String sql, int offset, int pageNo, int pageSize) { StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14); sqlBuilder.append(sql); + sqlBuilder.append("\n LIMIT "); if (offset == 0) { - sqlBuilder.append("\n LIMIT "); sqlBuilder.append(pageSize); } else { - sqlBuilder.append("\n LIMIT "); - sqlBuilder.append(offset); - sqlBuilder.append(","); - sqlBuilder.append(pageSize); + sqlBuilder.append(offset).append(",").append(pageSize); } return sqlBuilder.toString(); } - + /** + * 构建创建数据库的SQL语句 + * + * @param database 数据库对象 + * @return 创建数据库的SQL语句 + */ @Override public String buildCreateDatabaseSql(Database database) { StringBuilder sqlBuilder = new StringBuilder(); - sqlBuilder.append("CREATE DATABASE `" + database.getName() + "`"); + sqlBuilder.append("CREATE DATABASE `").append(database.getName()).append("`"); if (StringUtils.isNotBlank(database.getCharset())) { sqlBuilder.append(" DEFAULT CHARACTER SET=").append(database.getCharset()); } @@ -152,142 +256,147 @@ public String buildCreateDatabaseSql(Database database) { return sqlBuilder.toString(); } + @Override public String buildGenerateReorderColumnSql(Table oldTable, Table newTable) { + List oldColumns = oldTable.getColumnList().stream() + .filter(column -> !EditStatus.DELETE.name().equals(column.getEditStatus())) + .map(TableColumn::getName) + .collect(Collectors.toList()); + List targetColumns = newTable.getColumnList().stream() + .filter(column -> !EditStatus.ADD.name().equals(column.getEditStatus())) + .map(column -> StringUtils.isNotBlank(column.getOldName()) ? column.getOldName() : column.getName()) + .collect(Collectors.toList()); + // 初始化SQL构建器 StringBuilder sql = new StringBuilder(); - int n = 0; - // Create a map to store the index of each column in the old table's column list - Map oldColumnIndexMap = new HashMap<>(); - for (int i = 0; i < oldTable.getColumnList().size(); i++) { - oldColumnIndexMap.put(oldTable.getColumnList().get(i).getName(), i); - } - String[] oldColumnArray = oldTable.getColumnList().stream().map(TableColumn::getName).toArray(String[]::new); - String[] newColumnArray = newTable.getColumnList().stream().map(TableColumn::getName).toArray(String[]::new); - - buildSql(oldColumnArray, newColumnArray, sql, oldTable, newTable, n); - - return sql.toString(); - } - - private String[] buildSql(String[] originalArray, String[] targetArray, StringBuilder sql, Table oldTable, Table newTable, int n) { - // 先完成首位移动 - if (!originalArray[0].equals(targetArray[0])) { - int a = findIndex(originalArray, targetArray[0]); - TableColumn column = oldTable.getColumnList().stream().filter(col -> StringUtils.equals(col.getName(), originalArray[a])).findFirst().get(); - String[] newArray = moveElement(originalArray, a, 0); - System.out.println(ArrayUtil.toString(newArray)); - sql.append(" MODIFY COLUMN "); - MysqlColumnTypeEnum typeEnum = MysqlColumnTypeEnum.getByType(column.getColumnType()); - sql.append(typeEnum.buildColumn(column)); - sql.append(" FIRST;\n"); - n++; - if (Arrays.equals(newArray, targetArray)) { - return newArray; - } - String[] resultArray = buildSql(newArray, targetArray, sql, oldTable, newTable, n); - if (Arrays.equals(resultArray, targetArray)) { - return resultArray; + if (!oldColumns.equals(targetColumns)) { + sql.append("ALTER TABLE "); + if (StringUtils.isNotBlank(oldTable.getDatabaseName())) { + sql.append("`").append(oldTable.getDatabaseName()).append("`."); } + sql.append("`").append(oldTable.getName()).append("`\n"); + buildReorderStatements(oldColumns, targetColumns, oldTable, sql); } + return sql.toString(); + } - // 在完成最后一位移动 - int max = originalArray.length - 1; - if (!originalArray[max].equals(targetArray[max])) { - int a = findIndex(originalArray, targetArray[max]); - //System.out.println("Move " + originalArray[a] + " after " + (a > 0 ? originalArray[max] : "start")); - TableColumn column = oldTable.getColumnList().stream().filter(col -> StringUtils.equals(col.getName(), originalArray[a])).findFirst().get(); - String[] newArray = moveElement(originalArray, a, max); - System.out.println(ArrayUtil.toString(newArray)); - if (n > 0) { - sql.append("ALTER TABLE "); - if (StringUtils.isNotBlank(oldTable.getDatabaseName())) { - sql.append("`").append(oldTable.getDatabaseName()).append("`").append("."); - } - sql.append("`").append(oldTable.getName()).append("`").append("\n"); + private void buildReorderStatements(List currentColumns, + List targetColumns, + Table table, + StringBuilder sql) { + int steps = 0; + while (!currentColumns.equals(targetColumns)) { + // 寻找第一个不匹配的位置 + int firstMismatch = findFirstMismatch(currentColumns, targetColumns); + if (firstMismatch == -1) { + break; } - sql.append(" MODIFY COLUMN "); - MysqlColumnTypeEnum typeEnum = MysqlColumnTypeEnum.getByType(column.getColumnType()); - sql.append(typeEnum.buildColumn(column)); - sql.append(" "); - sql.append(" AFTER "); - sql.append(oldTable.getColumnList().get(max).getName()); - sql.append(";\n"); - n++; - if (Arrays.equals(newArray, targetArray)) { - return newArray; + String expectedColumn = targetColumns.get(firstMismatch); + int currentPosition = currentColumns.indexOf(expectedColumn); + // 构建MODIFY COLUMN语句 + TableColumn column = table.getColumnList().stream() + .filter(c -> c.getName().equals(expectedColumn)) + .findFirst() + .orElseThrow(() -> new RuntimeException("Column not found: " + expectedColumn)); + if (steps++ > 0) { + sql.append(",\n"); } - String[] resultArray = buildSql(newArray, targetArray, sql, oldTable, newTable, n); - if (Arrays.equals(resultArray, targetArray)) { - return resultArray; + sql.append("MODIFY COLUMN ") + .append(buildColumnDefinition(column)) + .append(" "); + // 确定移动位置 + if (firstMismatch == 0) { + sql.append("FIRST"); + } else { + String afterColumn = targetColumns.get(firstMismatch - 1); + sql.append("AFTER `").append(afterColumn).append("`"); } + // 更新当前列顺序 + currentColumns.remove(currentPosition); + currentColumns.add(firstMismatch, expectedColumn); } + sql.append(";"); + } - - for (int i = 0; i < originalArray.length; i++) { - int a = findIndex(targetArray, originalArray[i]); - if (i != a && isMoveValid(originalArray, targetArray, i, a)) { - // oldTable.getColumnList中查找name为a - int finalI = i; - TableColumn column = oldTable.getColumnList().stream().filter(col -> StringUtils.equals(col.getName(), originalArray[finalI])).findFirst().get(); - if (n > 0) { - sql.append("ALTER TABLE "); - if (StringUtils.isNotBlank(oldTable.getDatabaseName())) { - sql.append("`").append(oldTable.getDatabaseName()).append("`").append("."); - } - sql.append("`").append(oldTable.getName()).append("`").append("\n"); - } - sql.append(" MODIFY COLUMN "); - MysqlColumnTypeEnum typeEnum = MysqlColumnTypeEnum.getByType(column.getColumnType()); - sql.append(typeEnum.buildColumn(column)); - sql.append(" "); - sql.append(" AFTER "); - if (i < a) { - sql.append(originalArray[a]); - } else { - sql.append(originalArray[a - 1]); - } - - sql.append(";\n"); - n++; - String[] newArray = moveElement(originalArray, i, a); - if (Arrays.equals(newArray, targetArray)) { - return newArray; - } - String[] resultArray = buildSql(newArray, targetArray, sql, oldTable, newTable, n); - if (Arrays.equals(resultArray, targetArray)) { - return resultArray; - } - } + // 辅助方法:构建列定义 + private String buildColumnDefinition(TableColumn column) { + MysqlColumnTypeEnum type = MysqlColumnTypeEnum.getColumnTypeEnum(column); + if (type == null) { + return ""; } - return null; + return type.buildColumn(column); } - private static int findIndex(String[] array, String element) { - for (int i = 0; i < array.length; i++) { - if (array[i].equals(element)) { + // 辅助方法:找到第一个不匹配的位置 + private int findFirstMismatch(List list1, List list2) { + int minLength = Math.min(list1.size(), list2.size()); + for (int i = 0; i < minLength; i++) { + if (!list1.get(i).equals(list2.get(i))) { return i; } } - return -1; + return list1.size() == list2.size() ? -1 : minLength; } - private static boolean isMoveValid(String[] originalArray, String[] targetArray, int i, int a) { - System.out.println("i : " + i + " a:" + a); - return (i == 0 || a == 0 || !originalArray[i - 1].equals(targetArray[a - 1])) && - (i >= originalArray.length - 1 || a >= targetArray.length - 1 || !originalArray[i + 1].equals(targetArray[a + 1])); + @Override + protected String buildImportUpsertSql(String tableName, List
headerList, List primaryKeyColumns, + MetaData metaSchema) { + // MySQL: INSERT INTO ... ON DUPLICATE KEY UPDATE col=VALUES(col) + StringBuilder sql = new StringBuilder("INSERT INTO "); + sql.append(tableName).append(" ("); + for (int i = 0; i < headerList.size(); i++) { + if (i > 0) sql.append(","); + sql.append(metaSchema.getMetaDataName(headerList.get(i).getName())); + } + sql.append(") VALUES ("); + for (int i = 0; i < headerList.size(); i++) { + if (i > 0) sql.append(","); + sql.append("?"); + } + sql.append(") ON DUPLICATE KEY UPDATE "); + boolean first = true; + for (Header header : headerList) { + if (primaryKeyColumns != null && primaryKeyColumns.contains(header.getName())) { + continue; + } + if (!first) sql.append(","); + String quotedName = metaSchema.getMetaDataName(header.getName()); + sql.append(quotedName).append("=VALUES(").append(quotedName).append(")"); + first = false; + } + return sql.toString(); } - private static String[] moveElement(String[] originalArray, int from, int to) { - String[] newArray = new String[originalArray.length]; - System.arraycopy(originalArray, 0, newArray, 0, originalArray.length); - String temp = newArray[from]; - if (from < to) { - System.arraycopy(originalArray, from + 1, newArray, from, to - from); - } else { - System.arraycopy(originalArray, to, newArray, to + 1, from - to); + @Override + protected String buildImportInsertIgnoreSql(String tableName, List
headerList, MetaData metaSchema) { + // MySQL: INSERT IGNORE INTO ... + StringBuilder sql = new StringBuilder("INSERT IGNORE INTO "); + sql.append(tableName).append(" ("); + for (int i = 0; i < headerList.size(); i++) { + if (i > 0) sql.append(","); + sql.append(metaSchema.getMetaDataName(headerList.get(i).getName())); + } + sql.append(") VALUES ("); + for (int i = 0; i < headerList.size(); i++) { + if (i > 0) sql.append(","); + sql.append("?"); } - newArray[to] = temp; - System.out.println(ArrayUtil.toString(newArray)); - return newArray; + sql.append(")"); + return sql.toString(); + } + + @Override + public String buildOptimizeTableSql(String databaseName, String schemaName, String tableName) { + return "OPTIMIZE TABLE `" + databaseName + "`.`" + tableName + "`"; + } + + @Override + public String buildAnalyzeTableSql(String databaseName, String schemaName, String tableName) { + return "ANALYZE TABLE `" + databaseName + "`.`" + tableName + "`"; + } + + @Override + public String buildExplainSql(String originalSql) { + return "EXPLAIN " + originalSql; } } diff --git a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlColumnTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlColumnTypeEnum.java index c6ed4175b..ec9202f5a 100644 --- a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlColumnTypeEnum.java +++ b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlColumnTypeEnum.java @@ -4,12 +4,15 @@ import ai.chat2db.spi.enums.EditStatus; import ai.chat2db.spi.model.ColumnType; import ai.chat2db.spi.model.TableColumn; +import ai.chat2db.plugin.mysql.util.MysqlSqlUtils; import com.google.common.collect.Maps; import org.apache.commons.lang3.StringUtils; import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public enum MysqlColumnTypeEnum implements ColumnBuilder { @@ -115,8 +118,14 @@ public enum MysqlColumnTypeEnum implements ColumnBuilder { private ColumnType columnType; + private static final Pattern COLUMN_TYPE_PATTERN = Pattern.compile("^([a-zA-Z]+)(?:\\s*\\([^)]*\\))?(?:\\s+(unsigned))?.*$", + Pattern.CASE_INSENSITIVE); + public static MysqlColumnTypeEnum getByType(String dataType) { - return COLUMN_TYPE_MAP.get(dataType.toUpperCase()); + if (StringUtils.isBlank(dataType)) { + return null; + } + return COLUMN_TYPE_MAP.get(normalizeTypeName(dataType)); } public ColumnType getColumnType() { @@ -139,7 +148,11 @@ public ColumnType getColumnType() { @Override public String buildCreateColumnSql(TableColumn column) { - MysqlColumnTypeEnum type = COLUMN_TYPE_MAP.get(column.getColumnType().toUpperCase()); + return buildCreateColumnSql(column, true); + } + + private String buildCreateColumnSql(TableColumn column, boolean includePrimaryKey) { + MysqlColumnTypeEnum type = getColumnTypeEnum(column); if (type == null) { return ""; } @@ -163,6 +176,10 @@ public String buildCreateColumnSql(TableColumn column) { script.append(buildComment(column,type)).append(" "); + if (includePrimaryKey) { + script.append(buildPrimaryKey(column,type)).append(" "); + } + return script.toString(); } @@ -191,9 +208,10 @@ public String buildModifyColumn(TableColumn tableColumn) { } if (EditStatus.MODIFY.name().equals(tableColumn.getEditStatus())) { if (!StringUtils.equalsIgnoreCase(tableColumn.getOldName(), tableColumn.getName())) { - return StringUtils.join("CHANGE COLUMN `", tableColumn.getOldName(), "` ", buildCreateColumnSql(tableColumn)); + return StringUtils.join("CHANGE COLUMN `", tableColumn.getOldName(), "` ", + buildCreateColumnSql(tableColumn, false)); } else { - return StringUtils.join("MODIFY COLUMN ", buildCreateColumnSql(tableColumn)); + return StringUtils.join("MODIFY COLUMN ", buildCreateColumnSql(tableColumn, false)); } } return ""; @@ -213,7 +231,14 @@ private String buildComment(TableColumn column, MysqlColumnTypeEnum type) { if(!type.columnType.isSupportComments() || StringUtils.isEmpty(column.getComment())){ return ""; } - return StringUtils.join("COMMENT '",column.getComment(),"'"); + return StringUtils.join("COMMENT ", MysqlSqlUtils.quoteString(column.getComment())); + } + + private String buildPrimaryKey(TableColumn column, MysqlColumnTypeEnum type) { + if (Boolean.TRUE.equals(column.getPrimaryKey())) { + return "PRIMARY KEY"; + } + return ""; } private String buildExt(TableColumn column, MysqlColumnTypeEnum type) { @@ -224,11 +249,12 @@ private String buildExt(TableColumn column, MysqlColumnTypeEnum type) { } private String buildDefaultValue(TableColumn column, MysqlColumnTypeEnum type) { - if(!type.getColumnType().isSupportDefaultValue() || StringUtils.isEmpty(column.getDefaultValue())){ + if(!type.getColumnType().isSupportDefaultValue() || column.getDefaultValue() == null){ return ""; } - if("EMPTY_STRING".equalsIgnoreCase(column.getDefaultValue().trim())){ + if (StringUtils.isEmpty(column.getDefaultValue()) + || "EMPTY_STRING".equalsIgnoreCase(column.getDefaultValue().trim())) { return StringUtils.join("DEFAULT ''"); } @@ -237,18 +263,18 @@ private String buildDefaultValue(TableColumn column, MysqlColumnTypeEnum type) { } if(Arrays.asList(CHAR,VARCHAR,BINARY,VARBINARY, SET,ENUM).contains(type)){ - return StringUtils.join("DEFAULT '",column.getDefaultValue(),"'"); + return StringUtils.join("DEFAULT ", MysqlSqlUtils.quoteString(column.getDefaultValue())); } if(Arrays.asList(DATE,TIME,YEAR).contains(type)){ - return StringUtils.join("DEFAULT '",column.getDefaultValue(),"'"); + return StringUtils.join("DEFAULT ", MysqlSqlUtils.quoteString(column.getDefaultValue())); } if(Arrays.asList(DATETIME,TIMESTAMP).contains(type)){ if("CURRENT_TIMESTAMP".equalsIgnoreCase(column.getDefaultValue().trim())){ return StringUtils.join("DEFAULT ",column.getDefaultValue()); } - return StringUtils.join("DEFAULT '",column.getDefaultValue(),"'"); + return StringUtils.join("DEFAULT ", MysqlSqlUtils.quoteString(column.getDefaultValue())); } return StringUtils.join("DEFAULT ",column.getDefaultValue()); @@ -284,8 +310,8 @@ private String buildDataType(TableColumn column, MysqlColumnTypeEnum type) { } - if (Arrays.asList(DECIMAL, FLOAT, DOUBLE,TINYINT).contains(type)) { - if (column.getColumnSize() == null || column.getDecimalDigits() == null) { + if (Arrays.asList(DECIMAL, FLOAT, DOUBLE).contains(type)) { + if (column.getColumnSize() == null && column.getDecimalDigits() == null) { return columnType; } if (column.getColumnSize() != null && column.getDecimalDigits() == null) { @@ -296,8 +322,8 @@ private String buildDataType(TableColumn column, MysqlColumnTypeEnum type) { } } - if (Arrays.asList(DECIMAL_UNSIGNED, FLOAT_UNSIGNED, DECIMAL_UNSIGNED,TINYINT_UNSIGNED).contains(type)) { - if (column.getColumnSize() == null || column.getDecimalDigits() == null) { + if (Arrays.asList(DECIMAL_UNSIGNED, FLOAT_UNSIGNED, DOUBLE_UNSIGNED).contains(type)) { + if (column.getColumnSize() == null && column.getDecimalDigits() == null) { return columnType; } if (column.getColumnSize() != null && column.getDecimalDigits() == null) { @@ -308,6 +334,16 @@ private String buildDataType(TableColumn column, MysqlColumnTypeEnum type) { } } + if (Arrays.asList(TINYINT, TINYINT_UNSIGNED).contains(type)) { + if (column.getColumnSize() == null || column.getColumnSize() == 0) { + return columnType; + } + if (TINYINT_UNSIGNED == type) { + return unsignedDataType(columnType, "(" + column.getColumnSize() + ")"); + } + return StringUtils.join(columnType, "(", column.getColumnSize(), ")"); + } + if(Arrays.asList(SET,ENUM).contains(type)){ if(!StringUtils.isEmpty( column.getValue())){ return StringUtils.join(columnType,"(",column.getValue(),")"); @@ -319,7 +355,7 @@ private String buildDataType(TableColumn column, MysqlColumnTypeEnum type) { } public String buildColumn(TableColumn column) { - MysqlColumnTypeEnum type = COLUMN_TYPE_MAP.get(column.getColumnType().toUpperCase()); + MysqlColumnTypeEnum type = getColumnTypeEnum(column); if (type == null) { return ""; } @@ -330,6 +366,26 @@ public String buildColumn(TableColumn column) { return script.toString(); } + public static MysqlColumnTypeEnum getColumnTypeEnum(TableColumn column) { + MysqlColumnTypeEnum type = getByType(column.getColumnType()); + if (type != null) { + return type; + } + return getByType(column.getDataType()); + } + + private static String normalizeTypeName(String typeName) { + String normalized = StringUtils.trimToEmpty(typeName); + Matcher matcher = COLUMN_TYPE_PATTERN.matcher(normalized); + if (matcher.matches()) { + normalized = matcher.group(1); + if (StringUtils.isNotBlank(matcher.group(2))) { + normalized = normalized + " " + matcher.group(2); + } + } + return normalized.toUpperCase(); + } + private String unsignedDataType(String dataTypeName, String middle) { String[] split = dataTypeName.split(" "); if (split.length == 2) { diff --git a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlIndexTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlIndexTypeEnum.java index a03c29a69..c38cb56d3 100644 --- a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlIndexTypeEnum.java +++ b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlIndexTypeEnum.java @@ -1,5 +1,6 @@ package ai.chat2db.plugin.mysql.type; +import ai.chat2db.plugin.mysql.util.MysqlSqlUtils; import ai.chat2db.spi.enums.EditStatus; import ai.chat2db.spi.model.IndexType; import ai.chat2db.spi.model.TableIndex; @@ -78,7 +79,7 @@ private String buildIndexComment(TableIndex tableIndex) { if(StringUtils.isBlank(tableIndex.getComment())){ return ""; }else { - return StringUtils.join("COMMENT '",tableIndex.getComment(),"'"); + return StringUtils.join("COMMENT ", MysqlSqlUtils.quoteString(tableIndex.getComment())); } } diff --git a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/util/MysqlSqlUtils.java b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/util/MysqlSqlUtils.java new file mode 100644 index 000000000..0fe3bd231 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/util/MysqlSqlUtils.java @@ -0,0 +1,17 @@ +package ai.chat2db.plugin.mysql.util; + +import org.apache.commons.lang3.StringUtils; + +public final class MysqlSqlUtils { + + private MysqlSqlUtils() { + } + + public static String quoteString(String value) { + return "'" + escapeString(value) + "'"; + } + + public static String escapeString(String value) { + return StringUtils.defaultString(value).replace("\\", "\\\\").replace("'", "''"); + } +} diff --git a/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/OracleMetaData.java b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/OracleMetaData.java index f86637f91..5fb45fea9 100644 --- a/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/OracleMetaData.java +++ b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/OracleMetaData.java @@ -151,7 +151,7 @@ public Function function(Connection connection, @NotEmpty String databaseName, S Function function = new Function(); function.setDatabaseName(databaseName); function.setSchemaName(schemaName); - function.setFunctionName(functionName); + function.setName(functionName); function.setFunctionBody(sb.toString()); return function; @@ -243,7 +243,7 @@ public List triggers(Connection connection, String databaseName, String resultSet -> { while (resultSet.next()) { Trigger trigger = new Trigger(); - trigger.setTriggerName(resultSet.getString("TRIGGER_NAME")); + trigger.setName(resultSet.getString("TRIGGER_NAME")); trigger.setSchemaName(schemaName); trigger.setDatabaseName(databaseName); triggers.add(trigger); @@ -265,7 +265,7 @@ public Trigger trigger(Connection connection, @NotEmpty String databaseName, Str Trigger trigger = new Trigger(); trigger.setDatabaseName(databaseName); trigger.setSchemaName(schemaName); - trigger.setTriggerName(triggerName); + trigger.setName(triggerName); trigger.setTriggerBody(resultSet.getString(sb.toString())); return trigger; }); @@ -283,7 +283,7 @@ public Procedure procedure(Connection connection, @NotEmpty String databaseName, Procedure procedure = new Procedure(); procedure.setDatabaseName(databaseName); procedure.setSchemaName(schemaName); - procedure.setProcedureName(procedureName); + procedure.setName(procedureName); procedure.setProcedureBody(sb.toString()); return procedure; }); @@ -313,7 +313,7 @@ public SqlBuilder getSqlBuilder() { } @Override - public TableMeta getTableMeta(String databaseName, String schemaName, String tableName) { + public TableMeta getTableMeta(Connection connection, String databaseName, String schemaName) { return TableMeta.builder() .columnTypes(OracleColumnTypeEnum.getTypes()) .charsets(Lists.newArrayList()) diff --git a/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/builder/OracleSqlBuilder.java b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/builder/OracleSqlBuilder.java index 468ac2027..de649a479 100644 --- a/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/builder/OracleSqlBuilder.java +++ b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/builder/OracleSqlBuilder.java @@ -2,13 +2,17 @@ import ai.chat2db.plugin.oracle.type.OracleColumnTypeEnum; import ai.chat2db.plugin.oracle.type.OracleIndexTypeEnum; +import ai.chat2db.spi.MetaData; import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.jdbc.DefaultSqlBuilder; +import ai.chat2db.spi.model.Header; import ai.chat2db.spi.model.Table; import ai.chat2db.spi.model.TableColumn; import ai.chat2db.spi.model.TableIndex; import org.apache.commons.lang3.StringUtils; +import java.util.List; + public class OracleSqlBuilder extends DefaultSqlBuilder implements SqlBuilder { @Override public String buildCreateTableSql(Table table) { @@ -17,10 +21,14 @@ public String buildCreateTableSql(Table table) { script.append("CREATE TABLE ").append("\"").append(table.getSchemaName()).append("\".\"").append(table.getName()).append("\" (").append("\n"); for (TableColumn column : table.getColumnList()) { - if (StringUtils.isBlank(column.getName()) || StringUtils.isBlank(column.getColumnType())) { + String columnType = column.getDataType(); + if (StringUtils.isBlank(columnType)) { + columnType = column.getColumnType(); + } + if (StringUtils.isBlank(column.getName()) || StringUtils.isBlank(columnType)) { continue; } - OracleColumnTypeEnum typeEnum = OracleColumnTypeEnum.getByType(column.getColumnType()); + OracleColumnTypeEnum typeEnum = OracleColumnTypeEnum.getByType(columnType); script.append("\t").append(typeEnum.buildCreateColumnSql(column)).append(",\n"); } @@ -36,7 +44,11 @@ public String buildCreateTableSql(Table table) { } for (TableColumn column : table.getColumnList()) { - if (StringUtils.isBlank(column.getName()) || StringUtils.isBlank(column.getColumnType()) || StringUtils.isBlank(column.getComment())) { + String columnType = column.getDataType(); + if (StringUtils.isBlank(columnType)) { + columnType = column.getColumnType(); + } + if (StringUtils.isBlank(column.getName()) || StringUtils.isBlank(columnType) || StringUtils.isBlank(column.getComment())) { continue; } script.append("\n").append(buildComment(column)).append(";"); @@ -77,8 +89,12 @@ public String buildModifyTaleSql(Table oldTable, Table newTable) { // append modify column for (TableColumn tableColumn : newTable.getColumnList()) { + String columnType = tableColumn.getDataType(); + if (StringUtils.isBlank(columnType)) { + columnType = tableColumn.getColumnType(); + } if (StringUtils.isNotBlank(tableColumn.getEditStatus())) { - OracleColumnTypeEnum typeEnum = OracleColumnTypeEnum.getByType(tableColumn.getColumnType()); + OracleColumnTypeEnum typeEnum = OracleColumnTypeEnum.getByType(columnType); script.append("\t").append(typeEnum.buildModifyColumn(tableColumn)).append(";\n"); if (StringUtils.isNotBlank(tableColumn.getComment())) { script.append("\n").append(buildComment(tableColumn)).append(";\n"); @@ -126,16 +142,50 @@ public String pageLimit(String sql, int offset, int pageNo, int pageSize) { return sqlBuilder.toString(); } -// @Override -// public String buildCreateSchemaSql(Schema schema){ -// StringBuilder sqlBuilder = new StringBuilder(); -// sqlBuilder.append("CREATE SCHEMA \""+schema.getName()+"\""); -// if(StringUtils.isNotBlank(schema.getOwner())){ -// sqlBuilder.append(" AUTHORIZATION ").append(schema.getOwner()); -// } -// if(StringUtils.isNotBlank(schema.getComment())){ -// sqlBuilder.append("; COMMENT ON SCHEMA \"").append(schema.getName()).append("\" IS '").append(schema.getComment()).append("';"); -// } -// return sqlBuilder.toString(); -// } + @Override + protected String buildImportUpsertSql(String tableName, List
headerList, List primaryKeyColumns, + MetaData metaSchema) { + if (primaryKeyColumns == null || primaryKeyColumns.isEmpty()) { + return buildImportInsertSql(tableName, headerList, metaSchema); + } + StringBuilder sql = new StringBuilder("MERGE INTO "); + sql.append(tableName).append(" t USING (SELECT "); + for (int i = 0; i < headerList.size(); i++) { + if (i > 0) sql.append(","); + sql.append("? AS ").append(metaSchema.getMetaDataName(headerList.get(i).getName())); + } + sql.append(" FROM DUAL) s ON ("); + for (int i = 0; i < primaryKeyColumns.size(); i++) { + if (i > 0) sql.append(" AND "); + String pk = metaSchema.getMetaDataName(primaryKeyColumns.get(i)); + sql.append("t.").append(pk).append("=s.").append(pk); + } + sql.append(") WHEN MATCHED THEN UPDATE SET "); + boolean first = true; + for (Header header : headerList) { + if (primaryKeyColumns.contains(header.getName())) { + continue; + } + if (!first) sql.append(","); + String quotedName = metaSchema.getMetaDataName(header.getName()); + sql.append("t.").append(quotedName).append("=s.").append(quotedName); + first = false; + } + sql.append(" WHEN NOT MATCHED THEN INSERT ("); + first = true; + for (Header header : headerList) { + if (!first) sql.append(","); + sql.append(metaSchema.getMetaDataName(header.getName())); + first = false; + } + sql.append(") VALUES ("); + first = true; + for (Header header : headerList) { + if (!first) sql.append(","); + sql.append("s.").append(metaSchema.getMetaDataName(header.getName())); + first = false; + } + sql.append(")"); + return sql.toString(); + } } diff --git a/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/type/OracleColumnTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/type/OracleColumnTypeEnum.java index 8b9ea3b61..abc1e2d95 100644 --- a/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/type/OracleColumnTypeEnum.java +++ b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/type/OracleColumnTypeEnum.java @@ -259,7 +259,7 @@ public String buildModifyColumnSql(TableColumn column,TableColumn oldColumn) { script.append(buildDefaultValue(column,type)).append(" "); - if(oldColumn.getNullable() != column.getNullable()) { + if(!oldColumn.getNullable().equals(column.getNullable())) { script.append(buildNullable(column, type)).append(" "); } diff --git a/chat2db-server/chat2db-plugins/chat2db-phoenix/pom.xml b/chat2db-server/chat2db-plugins/chat2db-phoenix/pom.xml new file mode 100644 index 000000000..ace692e93 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-phoenix/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + ai.chat2db + chat2db-plugins + ${revision} + ../pom.xml + + + + + ai.chat2db + chat2db-spi + + + + chat2db-phoenix + + + + src/main/java + + **/*.json + + + + src/main/resources + + + + + \ No newline at end of file diff --git a/chat2db-server/chat2db-plugins/chat2db-phoenix/src/main/java/ai/chat2db/plugin/phoenix/PhoenixDBManage.java b/chat2db-server/chat2db-plugins/chat2db-phoenix/src/main/java/ai/chat2db/plugin/phoenix/PhoenixDBManage.java new file mode 100644 index 000000000..5f4565e46 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-phoenix/src/main/java/ai/chat2db/plugin/phoenix/PhoenixDBManage.java @@ -0,0 +1,17 @@ +package ai.chat2db.plugin.phoenix; + +import java.sql.Connection; + +import ai.chat2db.spi.DBManage; +import ai.chat2db.spi.jdbc.DefaultDBManage; +import ai.chat2db.spi.sql.SQLExecutor; + +public class PhoenixDBManage extends DefaultDBManage implements DBManage { + + + @Override + public void dropTable(Connection connection, String databaseName, String schemaName, String tableName) { + String sql = "drop table if exists " +schemaName+"." +tableName; + SQLExecutor.getInstance().execute(connection,sql, resultSet -> null); + } +} diff --git a/chat2db-server/chat2db-plugins/chat2db-phoenix/src/main/java/ai/chat2db/plugin/phoenix/PhoenixMetaData.java b/chat2db-server/chat2db-plugins/chat2db-phoenix/src/main/java/ai/chat2db/plugin/phoenix/PhoenixMetaData.java new file mode 100644 index 000000000..6b75869d9 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-phoenix/src/main/java/ai/chat2db/plugin/phoenix/PhoenixMetaData.java @@ -0,0 +1,42 @@ +package ai.chat2db.plugin.phoenix; + +import java.sql.Connection; +import java.util.List; + +import org.apache.commons.collections4.CollectionUtils; + +import ai.chat2db.plugin.phoenix.builder.PhoenixSqlBuilder; +import ai.chat2db.plugin.phoenix.type.PhoenixColumnTypeEnum; +import ai.chat2db.plugin.phoenix.type.PhoenixDefaultValueEnum; +import ai.chat2db.plugin.phoenix.type.PhoenixIndexTypeEnum; +import ai.chat2db.spi.MetaData; +import ai.chat2db.spi.SqlBuilder; +import ai.chat2db.spi.jdbc.DefaultMetaService; +import ai.chat2db.spi.model.TableColumn; +import ai.chat2db.spi.model.TableMeta; + +public class PhoenixMetaData extends DefaultMetaService implements MetaData { + @Override + public SqlBuilder getSqlBuilder() { + return new PhoenixSqlBuilder(); + } + + @Override + public TableMeta getTableMeta(Connection connection, String databaseName, String schemaName) { + return TableMeta.builder() + .columnTypes(PhoenixColumnTypeEnum.getTypes()) + .indexTypes(PhoenixIndexTypeEnum.getIndexTypes()) + .defaultValues(PhoenixDefaultValueEnum.getDefaultValues()) + .build(); + } + + @Override + public List columns(Connection connection, String databaseName, String schemaName, String tableName) { + List columns = super.columns(connection, databaseName, schemaName, tableName); + if(CollectionUtils.isEmpty(columns)){ + return columns; + } + columns.forEach(column-> column.setPrimaryKey(column.getPrimaryKeyOrder()!=0)); + return columns; + } +} diff --git a/chat2db-server/chat2db-plugins/chat2db-phoenix/src/main/java/ai/chat2db/plugin/phoenix/PhoenixPlugin.java b/chat2db-server/chat2db-plugins/chat2db-phoenix/src/main/java/ai/chat2db/plugin/phoenix/PhoenixPlugin.java new file mode 100644 index 000000000..1be28246c --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-phoenix/src/main/java/ai/chat2db/plugin/phoenix/PhoenixPlugin.java @@ -0,0 +1,24 @@ +package ai.chat2db.plugin.phoenix; + +import ai.chat2db.spi.DBManage; +import ai.chat2db.spi.MetaData; +import ai.chat2db.spi.Plugin; +import ai.chat2db.spi.config.DBConfig; +import ai.chat2db.spi.util.FileUtils; + +public class PhoenixPlugin implements Plugin { + @Override + public DBConfig getDBConfig() { + return FileUtils.readJsonValue(this.getClass(),"phoenix.json", DBConfig.class); + } + + @Override + public MetaData getMetaData() { + return new PhoenixMetaData(); + } + + @Override + public DBManage getDBManage() { + return new PhoenixDBManage(); + } +} diff --git a/chat2db-server/chat2db-plugins/chat2db-phoenix/src/main/java/ai/chat2db/plugin/phoenix/builder/PhoenixSqlBuilder.java b/chat2db-server/chat2db-plugins/chat2db-phoenix/src/main/java/ai/chat2db/plugin/phoenix/builder/PhoenixSqlBuilder.java new file mode 100644 index 000000000..b86f73061 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-phoenix/src/main/java/ai/chat2db/plugin/phoenix/builder/PhoenixSqlBuilder.java @@ -0,0 +1,147 @@ +package ai.chat2db.plugin.phoenix.builder; + +import java.util.List; +import java.util.Objects; + +import org.apache.commons.lang3.StringUtils; + +import ai.chat2db.plugin.phoenix.type.PhoenixColumnTypeEnum; +import ai.chat2db.plugin.phoenix.type.PhoenixIndexTypeEnum; +import ai.chat2db.spi.SqlBuilder; +import ai.chat2db.spi.jdbc.DefaultSqlBuilder; +import ai.chat2db.spi.model.Table; +import ai.chat2db.spi.model.TableColumn; +import ai.chat2db.spi.model.TableIndex; + +public class PhoenixSqlBuilder extends DefaultSqlBuilder implements SqlBuilder{ + + + @Override + public String buildCreateTableSql(Table table) { + StringBuilder script = new StringBuilder(); + if(StringUtils.isNotBlank(table.getAiComment())){ + script.append(" -- ").append(table.getAiComment()).append("\n"); + } + script.append("CREATE TABLE "); + + // 添加数据库名 + if (StringUtils.isNotBlank(table.getSchemaName())) { + script.append(table.getSchemaName()).append("."); + } + script.append(table.getName()).append(" (").append("\n"); + + // 添加列 + appendColumns(script, table.getColumnList()); + + // 添加索引 + appendIndexes(script, table.getIndexList()); + + // 移除最后的逗号 + if (script.length() > 2) { + script.setLength(script.length() - 2); + } + script.append("\n)"); + + // 添加表的其他属性 + appendTableAttributes(script, table); + + script.append(";"); + + return script.toString(); + } + + // 添加列的方法 + @Override + protected void appendColumns(StringBuilder script, List columns) { + for (TableColumn column : columns) { + String columnType = column.getDataType(); + if (StringUtils.isBlank(columnType)) { + columnType = column.getColumnType(); + } + if (StringUtils.isBlank(column.getName()) || StringUtils.isBlank(columnType)) { + continue; + } + PhoenixColumnTypeEnum typeEnum = PhoenixColumnTypeEnum.getByType(columnType); + script.append("\t").append(typeEnum.buildCreateColumnSql(column)).append(","); + if(StringUtils.isNotBlank(column.getAiComment())){ + script.append(" -- ").append(column.getAiComment()); + } + script.append("\n"); + } + } + + // 添加索引的方法 + @Override + protected void appendIndexes(StringBuilder script, List indexes) { + for (TableIndex tableIndex : indexes) { + if (StringUtils.isBlank(tableIndex.getName()) || StringUtils.isBlank(tableIndex.getType())) { + continue; + } + PhoenixIndexTypeEnum indexTypeEnum = PhoenixIndexTypeEnum.getByType(tableIndex.getType()); + script.append("\t").append(indexTypeEnum.buildIndexScript(tableIndex)).append(",\n"); + } + } + + // 添加表的其他属性的方法 + @Override + protected void appendTableAttributes(StringBuilder script, Table table) { + if (StringUtils.isNotBlank(table.getEngine())) { + script.append(" ENGINE=").append(table.getEngine()); + } + if (StringUtils.isNotBlank(table.getCharset())) { + script.append(" DEFAULT CHARACTER SET=").append(table.getCharset()); + } + if (StringUtils.isNotBlank(table.getCollate())) { + script.append(" COLLATE=").append(table.getCollate()); + } + if (table.getIncrementValue() != null) { + script.append(" AUTO_INCREMENT=").append(table.getIncrementValue()); + } + if (StringUtils.isNotBlank(table.getPartition())) { + script.append(" \n").append(table.getPartition()); + } + } + + + @Override + // 修改列的方法 + protected void modifyColumns(StringBuilder script, Table oldTable, Table newTable) { + for (TableColumn tableColumn : newTable.getColumnList()) { + String columnType = tableColumn.getDataType(); + if (StringUtils.isBlank(columnType)) { + columnType = tableColumn.getColumnType(); + } + if (StringUtils.isNotBlank(tableColumn.getEditStatus()) && StringUtils.isNotBlank(columnType) && StringUtils.isNotBlank(tableColumn.getName())) { + PhoenixColumnTypeEnum typeEnum = PhoenixColumnTypeEnum.getByType(columnType); + script.append("\t").append(typeEnum.buildModifyColumn(tableColumn)).append(",\n"); + } + } + } + @Override + // 修改索引的方法 + protected void modifyIndexes(StringBuilder script, Table oldTable, Table newTable) { + for (TableIndex tableIndex : newTable.getIndexList()) { + if (StringUtils.isNotBlank(tableIndex.getEditStatus()) && StringUtils.isNotBlank(tableIndex.getType())) { + PhoenixIndexTypeEnum indexTypeEnum = PhoenixIndexTypeEnum.getByType(tableIndex.getType()); + script.append("\t").append(indexTypeEnum.buildModifyIndex(tableIndex)).append(",\n"); + } + } + } + + + /** + * 修改表名和注释的方法 + */ + @Override + protected void modifyTableNameAndComment(StringBuilder script, Table oldTable, Table newTable) { + if (!StringUtils.equalsIgnoreCase(oldTable.getName(), newTable.getName())) { + script.append("\t").append("RENAME TO ").append("`").append(newTable.getName()).append("`").append(",\n"); + } + if (!Objects.equals(oldTable.getIncrementValue(), newTable.getIncrementValue())) { + script.append("\t").append("AUTO_INCREMENT=").append(newTable.getIncrementValue()).append(",\n"); + } + if(StringUtils.isNotBlank(newTable.getAiComment())){ + script.append(" -- ").append(newTable.getAiComment()).append(",\n"); + } + } +} diff --git a/chat2db-server/chat2db-plugins/chat2db-phoenix/src/main/java/ai/chat2db/plugin/phoenix/phoenix.json b/chat2db-server/chat2db-plugins/chat2db-phoenix/src/main/java/ai/chat2db/plugin/phoenix/phoenix.json new file mode 100644 index 000000000..0a6acd72f --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-phoenix/src/main/java/ai/chat2db/plugin/phoenix/phoenix.json @@ -0,0 +1,28 @@ +{ + "dbType": "PHOENIX", + "supportDatabase": false, + "supportSchema": false, + "driverConfigList": [ + { + "url": "jdbc:phoenix://localhost:2181/", + "defaultDriver": true, + "custom": false, + "downloadJdbcDriverUrls": [ + "https://oss.sqlgpt.cn/lib/phoenix-client-hbase-2.3-5.1.2.jar" + ], + "jdbcDriver": "phoenix-client-hbase-2.3-5.1.2.jar", + "jdbcDriverClass": "org.apache.phoenix.jdbc.PhoenixDriver" + }, + { + "url": "jdbc:mysql://localhost:3306/", + "defaultDriver": false, + "custom": false, + "downloadJdbcDriverUrls": [ + "https://oss.sqlgpt.cn/lib/phoenix-5.0.0-HBase-2.0-client.jar" + ], + "jdbcDriver": "phoenix-5.0.0-HBase-2.0-client.jar", + "jdbcDriverClass": "org.apache.phoenix.jdbc.PhoenixDriver" + } + ], + "name": "Phoenix" +} \ No newline at end of file diff --git a/chat2db-server/chat2db-plugins/chat2db-phoenix/src/main/java/ai/chat2db/plugin/phoenix/type/PhoenixColumnTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-phoenix/src/main/java/ai/chat2db/plugin/phoenix/type/PhoenixColumnTypeEnum.java new file mode 100644 index 000000000..face0a4aa --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-phoenix/src/main/java/ai/chat2db/plugin/phoenix/type/PhoenixColumnTypeEnum.java @@ -0,0 +1,198 @@ +package ai.chat2db.plugin.phoenix.type; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; + +import com.google.common.collect.Maps; + +import ai.chat2db.spi.ColumnBuilder; +import ai.chat2db.spi.enums.EditStatus; +import ai.chat2db.spi.model.ColumnType; +import ai.chat2db.spi.model.TableColumn; + +public enum PhoenixColumnTypeEnum implements ColumnBuilder { + INTEGER("INTEGER", false, false, true, false, false, false, false, true, false, false), + BIGINT("BIGINT", false, false, true, false, false, false, false, true, false, false), + VARCHAR("VARCHAR", true, false, true, false, false, true, true, true, false, false), + CHAR("CHAR", true, false, true, false, false, true, true, true, false, false), + DATE("DATE", false, false, true, false, false, false, false, true, false, false), + TIMESTAMP("TIMESTAMP", false, false, true, false, false, false, false, true, false, false), + FLOAT("FLOAT", false, false, true, false, false, false, false, true, false, false), + DOUBLE("DOUBLE", false, false, true, false, false, false, false, true, false, false), + BOOLEAN("BOOLEAN", false, false, true, false, false, false, false, true, false, false), + BINARY("BINARY", false, false, true, false, false, false, false, true, false, false), + DECIMAL("DECIMAL", true, false, true, false, false, false, false, true, false, false), + JSON("JSON", false, false, true, false, false, false, false, true, false, false), + UUID("UUID", false, false, true, false, false, false, false, true, false, false); + + private static Map COLUMN_TYPE_MAP = Maps.newHashMap(); + + static { + for (PhoenixColumnTypeEnum value : PhoenixColumnTypeEnum.values()) { + COLUMN_TYPE_MAP.put(value.getColumnType().getTypeName(), value); + } + } + + private ColumnType columnType; + + + PhoenixColumnTypeEnum(String dataTypeName, boolean supportLength, boolean supportScale, boolean supportNullable, boolean supportAutoIncrement, boolean supportCharset, boolean supportCollation, boolean supportComments, boolean supportDefaultValue, boolean supportExtent, boolean supportValue) { + this.columnType = new ColumnType(dataTypeName, supportLength, supportScale, supportNullable, supportAutoIncrement, supportCharset, supportCollation, supportComments, supportDefaultValue, supportExtent, supportValue, false); + } + + public static PhoenixColumnTypeEnum getByType(String dataType) { + return COLUMN_TYPE_MAP.get(dataType.toUpperCase()); + } + + public static List getTypes() { + return Arrays.stream(PhoenixColumnTypeEnum.values()).map(columnTypeEnum -> + columnTypeEnum.getColumnType() + ).toList(); + } + + public ColumnType getColumnType() { + return columnType; + } + + @Override + public String buildCreateColumnSql(TableColumn column) { + PhoenixColumnTypeEnum type = COLUMN_TYPE_MAP.get(column.getColumnType().toUpperCase()); + if (type == null) { + return ""; + } + StringBuilder script = new StringBuilder(); + + script.append("\"").append(column.getName()).append("\"").append(" "); + + script.append(buildDataType(column, type)).append(" "); + + + script.append(buildCollation(column, type)).append(" "); + + script.append(buildNullable(column, type)).append(" "); + + script.append(buildDefaultValue(column, type)).append(" "); + + // 添加是否主键 + if (Boolean.TRUE.equals(column.getPrimaryKey())) { + script.append("PRIMARY KEY "); + } + return script.toString(); + } + + private String buildCollation(TableColumn column, PhoenixColumnTypeEnum type) { + if (!type.getColumnType().isSupportCollation() || StringUtils.isEmpty(column.getCollationName())) { + return ""; + } + return StringUtils.join("\"", column.getCollationName(), "\""); + } + + @Override + public String buildModifyColumn(TableColumn column) { + + if (EditStatus.DELETE.name().equals(column.getEditStatus())) { + return StringUtils.join("DROP COLUMN \"", column.getName() + "\""); + } + if (EditStatus.ADD.name().equals(column.getEditStatus())) { + return StringUtils.join("ADD COLUMN ", buildCreateColumnSql(column)); + } + if (EditStatus.MODIFY.name().equals(column.getEditStatus())) { + StringBuilder script = new StringBuilder(); + script.append("ALTER COLUMN \"").append(column.getName()).append("\" TYPE ").append(buildDataType(column, this)).append(",\n"); + if (column.getNullable() != null && 1 == column.getNullable()) { + script.append("\t").append("ALTER COLUMN \"").append(column.getName()).append("\" DROP NOT NULL ,\n"); + } else { + script.append("\t").append("ALTER COLUMN \"").append(column.getName()).append("\" SET NOT NULL ,\n"); + + } + String defaultValue = buildDefaultValue(column, this); + if (StringUtils.isNotBlank(defaultValue)) { + script.append("ALTER COLUMN \"").append(column.getName()).append("\" SET ").append(defaultValue).append(",\n"); + } + script = new StringBuilder(script.substring(0, script.length() - 2)); + return script.toString(); + } + return ""; + } + + public String buildComment(TableColumn column, PhoenixColumnTypeEnum type) { + if (!this.columnType.isSupportComments() || column.getComment() == null + || EditStatus.DELETE.name().equals(column.getEditStatus())) { + return ""; + } + return StringUtils.join("COMMENT ON COLUMN", " \"", column.getTableName(), + "\".\"", column.getName(), "\" IS '", column.getComment(), "';"); + } + + private String buildDefaultValue(TableColumn column, PhoenixColumnTypeEnum type) { + if (!type.getColumnType().isSupportDefaultValue() || StringUtils.isEmpty(column.getDefaultValue())) { + return ""; + } + + if("EMPTY_STRING".equalsIgnoreCase(column.getDefaultValue().trim())){ + return StringUtils.join("DEFAULT ''"); + } + + if("NULL".equalsIgnoreCase(column.getDefaultValue().trim())){ + return StringUtils.join("DEFAULT NULL"); + } + + if (Arrays.asList(CHAR, VARCHAR).contains(type)) { + return StringUtils.join("DEFAULT '", column.getDefaultValue(), "'"); + } + + if (Arrays.asList(TIMESTAMP, DATE).contains(type)) { + if ("CURRENT_TIMESTAMP".equalsIgnoreCase(column.getDefaultValue().trim())) { + return StringUtils.join("DEFAULT ", column.getDefaultValue()); + } + return StringUtils.join("DEFAULT '", column.getDefaultValue(), "'"); + } + + return StringUtils.join("DEFAULT ", column.getDefaultValue()); + } + + private String buildNullable(TableColumn column, PhoenixColumnTypeEnum type) { + if (!type.getColumnType().isSupportNullable()) { + return ""; + } + if (column.getNullable() != null && 1 == column.getNullable()) { + return "NULL"; + } else { + return "NOT NULL"; + } + } + + private String buildDataType(TableColumn column, PhoenixColumnTypeEnum type) { + String columnType = type.columnType.getTypeName(); + if (Arrays.asList(VARCHAR, CHAR).contains(type)) { + if (column.getColumnSize() == null ) { + return columnType; + } + return StringUtils.join(columnType, "(", column.getColumnSize(), ")"); + } + + + if (Arrays.asList(TIMESTAMP).contains(type)) { + if (column.getColumnSize() == null || column.getColumnSize() == 0) { + return columnType; + } else { + return StringUtils.join(columnType, "(", column.getColumnSize(), ")"); + } + } + + if (Arrays.asList(DECIMAL,INTEGER).contains(type)) { + if (column.getColumnSize() == null && column.getDecimalDigits() == null) { + return columnType; + } + if (column.getColumnSize() != null && column.getDecimalDigits() == null) { + return StringUtils.join(columnType, "(", column.getColumnSize() + ")"); + } else { + return StringUtils.join(columnType, "(", column.getColumnSize() + "," + column.getDecimalDigits() + ")"); + } + } + return columnType; + } +} diff --git a/chat2db-server/chat2db-plugins/chat2db-phoenix/src/main/java/ai/chat2db/plugin/phoenix/type/PhoenixDefaultValueEnum.java b/chat2db-server/chat2db-plugins/chat2db-phoenix/src/main/java/ai/chat2db/plugin/phoenix/type/PhoenixDefaultValueEnum.java new file mode 100644 index 000000000..8dd92ec31 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-phoenix/src/main/java/ai/chat2db/plugin/phoenix/type/PhoenixDefaultValueEnum.java @@ -0,0 +1,27 @@ +package ai.chat2db.plugin.phoenix.type; + +import java.util.Arrays; +import java.util.List; + +import ai.chat2db.spi.model.DefaultValue; + +public enum PhoenixDefaultValueEnum { + EMPTY_STRING("EMPTY_STRING"), + NULL("NULL"), + ; + private DefaultValue defaultValue; + + PhoenixDefaultValueEnum(String defaultValue) { + this.defaultValue = new DefaultValue(defaultValue); + } + + + public DefaultValue getDefaultValue() { + return defaultValue; + } + + public static List getDefaultValues() { + return Arrays.stream(PhoenixDefaultValueEnum.values()).map(PhoenixDefaultValueEnum::getDefaultValue).collect(java.util.stream.Collectors.toList()); + } + +} diff --git a/chat2db-server/chat2db-plugins/chat2db-phoenix/src/main/java/ai/chat2db/plugin/phoenix/type/PhoenixIndexTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-phoenix/src/main/java/ai/chat2db/plugin/phoenix/type/PhoenixIndexTypeEnum.java new file mode 100644 index 000000000..9a3c8458c --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-phoenix/src/main/java/ai/chat2db/plugin/phoenix/type/PhoenixIndexTypeEnum.java @@ -0,0 +1,181 @@ +package ai.chat2db.plugin.phoenix.type; + +import java.util.Arrays; +import java.util.List; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.StringUtils; + +import ai.chat2db.spi.enums.EditStatus; +import ai.chat2db.spi.model.IndexType; +import ai.chat2db.spi.model.TableIndex; +import ai.chat2db.spi.model.TableIndexColumn; + +public enum PhoenixIndexTypeEnum { + + PRIMARY("Primary", "PRIMARY KEY"), + + FOREIGN("Foreign", "FOREIGN KEY"), + + NORMAL("Normal", "INDEX"), + + UNIQUE("Unique", "UNIQUE"), + ; + + private String name; + private String keyword; + + private IndexType indexType; + + + PhoenixIndexTypeEnum(String name, String keyword) { + this.name = name; + this.keyword = keyword; + this.indexType =new IndexType(name); + } + + public static PhoenixIndexTypeEnum getByType(String type) { + for (PhoenixIndexTypeEnum value : PhoenixIndexTypeEnum.values()) { + if (value.name.equalsIgnoreCase(type)) { + return value; + } + } + return null; + } + + public static List getIndexTypes() { + return Arrays.asList(PhoenixIndexTypeEnum.values()).stream().map(PhoenixIndexTypeEnum::getIndexType).collect(java.util.stream.Collectors.toList()); + } + + public IndexType getIndexType() { + return indexType; + } + + public String getName() { + return name; + } + + public String getKeyword() { + return keyword; + } + + public String buildIndexScript(TableIndex tableIndex) { + StringBuilder script = new StringBuilder(); + if (NORMAL.equals(this)) { + script.append("CREATE").append(" "); + script.append(buildIndexUnique(tableIndex)).append(" "); + script.append(buildIndexConcurrently(tableIndex)).append(" "); + script.append(buildIndexName(tableIndex)).append(" "); + script.append("ON ").append("\"").append(tableIndex.getTableName()).append("\"").append(" "); + script.append(buildIndexMethod(tableIndex)).append(" "); + script.append(buildIndexColumn(tableIndex)); + } else { + script.append("CONSTRAINT").append(" "); + script.append(buildIndexName(tableIndex)).append(" "); + script.append(keyword).append(" "); + script.append(buildIndexColumn(tableIndex)); + script.append(buildForeignColum(tableIndex)); + } + return script.toString(); + } + + private String buildForeignColum(TableIndex tableIndex) { + if (FOREIGN.equals(this)) { + StringBuilder script = new StringBuilder(); + script.append(" REFERENCES "); + if (StringUtils.isNotBlank(tableIndex.getForeignSchemaName())) { + script.append(tableIndex.getForeignSchemaName()).append("."); + } + if (StringUtils.isNotBlank(tableIndex.getForeignTableName())) { + script.append(tableIndex.getForeignTableName()).append(" "); + } + if (CollectionUtils.isNotEmpty(tableIndex.getForeignColumnNamelist())) { + script.append("("); + for (String column : tableIndex.getForeignColumnNamelist()) { + if (StringUtils.isNotBlank(column)) { + script.append("\"").append(column).append("\"").append(","); + } + } + script.deleteCharAt(script.length() - 1); + script.append(")"); + } + return script.toString(); + } + return ""; + } + + private String buildIndexMethod(TableIndex tableIndex) { + if (StringUtils.isNotBlank(tableIndex.getMethod())) { + return "USING " + tableIndex.getMethod(); + } else { + return ""; + } + } + + private String buildIndexConcurrently(TableIndex tableIndex) { + if (BooleanUtils.isTrue(tableIndex.getConcurrently())) { + return "CONCURRENTLY"; + } else { + return ""; + } + } + + private String buildIndexUnique(TableIndex tableIndex) { + if (BooleanUtils.isTrue(tableIndex.getUnique())) { + return "UNIQUE " + keyword; + } else { + return keyword; + } + } + + public String buildIndexComment(TableIndex tableIndex) { + if (StringUtils.isBlank(tableIndex.getComment()) || EditStatus.DELETE.name().equals(tableIndex.getEditStatus())) { + return ""; + } else if (NORMAL.equals(this)) { + return StringUtils.join("COMMENT ON INDEX", " ", + "\"", tableIndex.getName(), "\" IS '", tableIndex.getComment(), "';"); + } else { + return StringUtils.join("COMMENT ON CONSTRAINT", " \"", tableIndex.getName(), "\" ON \"", tableIndex.getSchemaName(), + "\".\"", tableIndex.getTableName(), "\" IS '", tableIndex.getComment(), "';"); + } + } + + private String buildIndexColumn(TableIndex tableIndex) { + StringBuilder script = new StringBuilder(); + script.append("("); + for (TableIndexColumn column : tableIndex.getColumnList()) { + if (StringUtils.isNotBlank(column.getColumnName())) { + script.append("\"").append(column.getColumnName()).append("\"").append(","); + } + } + script.deleteCharAt(script.length() - 1); + script.append(")"); + return script.toString(); + } + + private String buildIndexName(TableIndex tableIndex) { + return "\"" + tableIndex.getName() + "\""; + } + + public String buildModifyIndex(TableIndex tableIndex) { + boolean isNormal = NORMAL.equals(this); + if (EditStatus.DELETE.name().equals(tableIndex.getEditStatus())) { + return buildDropIndex(tableIndex); + } + if (EditStatus.MODIFY.name().equals(tableIndex.getEditStatus())) { + return StringUtils.join(buildDropIndex(tableIndex), isNormal ? ";\n" : ",\n\tADD ", buildIndexScript(tableIndex)); + } + if (EditStatus.ADD.name().equals(tableIndex.getEditStatus())) { + return StringUtils.join(isNormal ? "" : "ADD ", buildIndexScript(tableIndex)); + } + return ""; + } + + private String buildDropIndex(TableIndex tableIndex) { + if (NORMAL.equals(this)) { + return StringUtils.join("DROP INDEX \"", tableIndex.getOldName(), "\""); + } + return StringUtils.join("DROP CONSTRAINT \"", tableIndex.getOldName(), "\""); + } +} diff --git a/chat2db-server/chat2db-plugins/chat2db-phoenix/src/main/resources/META-INF/services/ai.chat2db.spi.Plugin b/chat2db-server/chat2db-plugins/chat2db-phoenix/src/main/resources/META-INF/services/ai.chat2db.spi.Plugin new file mode 100644 index 000000000..7dd59612d --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-phoenix/src/main/resources/META-INF/services/ai.chat2db.spi.Plugin @@ -0,0 +1 @@ +ai.chat2db.plugin.phoenix.PhoenixPlugin \ No newline at end of file diff --git a/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/PostgreSQLDBManage.java b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/PostgreSQLDBManage.java index 43d9a2714..b58dc7048 100644 --- a/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/PostgreSQLDBManage.java +++ b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/PostgreSQLDBManage.java @@ -56,6 +56,12 @@ public String replaceDatabaseInJdbcUrl(String url, String newDatabase) { } + @Override + public void dropDatabase(Connection connection, String databaseName) { + String sql = "DROP DATABASE \"" + databaseName + "\""; + SQLExecutor.getInstance().execute(connection, sql, resultSet -> null); + } + @Override public void dropTable(Connection connection, String databaseName, String schemaName, String tableName) { String sql = "DROP TABLE " + tableName; diff --git a/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/PostgreSQLMetaData.java b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/PostgreSQLMetaData.java index 0e7729571..0f4a81fdf 100644 --- a/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/PostgreSQLMetaData.java +++ b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/PostgreSQLMetaData.java @@ -28,6 +28,55 @@ public class PostgreSQLMetaData extends DefaultMetaService implements MetaData { private List systemDatabases = Arrays.asList("postgres"); + + private static final String SELECT_TABLES_SQL = "SELECT c.relname AS table_name, " + + "obj_description(c.oid, 'pg_class') AS table_comment, " + + "c.reltuples::bigint AS row_count " + + "FROM pg_class c " + + "JOIN pg_namespace n ON n.oid = c.relnamespace " + + "WHERE n.nspname = '%s' AND c.relkind = 'r'"; + + @Override + public List
tables(Connection connection, @NotEmpty String databaseName, String schemaName, String tableName) { + List
tables = new ArrayList<>(); + + String schema = StringUtils.isEmpty(schemaName) ? "public" : schemaName; + String sql = String.format(SELECT_TABLES_SQL, schema); + if (StringUtils.isNotBlank(tableName)) { + sql += String.format(" AND c.relname = '%s'", tableName); + } + sql += " ORDER BY c.relname"; + + try { + SQLExecutor.getInstance().execute(connection, sql, resultSet -> { + while (resultSet.next()) { + Table table = Table.builder() + .name(resultSet.getString("table_name")) + .comment(resultSet.getString("table_comment")) + .databaseName(databaseName) + .schemaName(schema) + .type("BASE TABLE") + .build(); + + // 设置预估行数(reltuples 是估算值,可能为 -1 或 0) + long rowCount = resultSet.getLong("row_count"); + if (rowCount >= 0) { + table.setRowCount(rowCount); + } + + tables.add(table); + } + return null; + }); + } catch (Exception e) { + // 如果查询失败,回退到 JDBC 元数据方式 + return SQLExecutor.getInstance().tables(connection, databaseName, schemaName, tableName, + new String[]{"TABLE", "SYSTEM TABLE"}); + } + + return tables; + } + @Override public List databases(Connection connection) { List list = SQLExecutor.getInstance().execute(connection, "SELECT datname FROM pg_database;", resultSet -> { @@ -91,7 +140,7 @@ public List triggers(Connection connection, String databaseName, String return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { while (resultSet.next()) { Trigger trigger = new Trigger(); - trigger.setTriggerName(resultSet.getString("trigger_name")); + trigger.setName(resultSet.getString("trigger_name")); trigger.setSchemaName(schemaName); trigger.setDatabaseName(databaseName); triggers.add(trigger); @@ -127,7 +176,7 @@ public Function function(Connection connection, @NotEmpty String databaseName, S Function function = new Function(); function.setDatabaseName(databaseName); function.setSchemaName(schemaName); - function.setFunctionName(functionName); + function.setName(functionName); if (resultSet.next()) { function.setFunctionBody(resultSet.getString("code")); } @@ -160,7 +209,7 @@ public Trigger trigger(Connection connection, @NotEmpty String databaseName, Str Trigger trigger = new Trigger(); trigger.setDatabaseName(databaseName); trigger.setSchemaName(schemaName); - trigger.setTriggerName(triggerName); + trigger.setName(triggerName); if (resultSet.next()) { trigger.setTriggerBody(resultSet.getString("trigger_body")); } @@ -177,7 +226,7 @@ public Procedure procedure(Connection connection, @NotEmpty String databaseName, Procedure procedure = new Procedure(); procedure.setDatabaseName(databaseName); procedure.setSchemaName(schemaName); - procedure.setProcedureName(procedureName); + procedure.setName(procedureName); if (resultSet.next()) { procedure.setProcedureBody(resultSet.getString("code")); } @@ -292,7 +341,7 @@ public SqlBuilder getSqlBuilder() { } @Override - public TableMeta getTableMeta(String databaseName, String schemaName, String tableName) { + public TableMeta getTableMeta(Connection connection, String databaseName, String schemaName) { return TableMeta.builder() .columnTypes(PostgreSQLColumnTypeEnum.getTypes()) .charsets(PostgreSQLCharsetEnum.getCharsets()) diff --git a/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/builder/PostgreSQLSqlBuilder.java b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/builder/PostgreSQLSqlBuilder.java index e0d90a517..5995d7259 100644 --- a/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/builder/PostgreSQLSqlBuilder.java +++ b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/builder/PostgreSQLSqlBuilder.java @@ -2,6 +2,7 @@ import ai.chat2db.plugin.postgresql.type.PostgreSQLColumnTypeEnum; import ai.chat2db.plugin.postgresql.type.PostgreSQLIndexTypeEnum; +import ai.chat2db.spi.MetaData; import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.jdbc.DefaultSqlBuilder; import ai.chat2db.spi.model.*; @@ -22,10 +23,14 @@ public String buildCreateTableSql(Table table) { script.append("\"").append(table.getName()).append("\"").append(" (").append(" ").append("\n"); // append column for (TableColumn column : table.getColumnList()) { - if (StringUtils.isBlank(column.getName()) || StringUtils.isBlank(column.getColumnType())) { + String columnType = column.getDataType(); + if (StringUtils.isBlank(columnType)) { + columnType = column.getColumnType(); + } + if (StringUtils.isBlank(column.getName()) || StringUtils.isBlank(columnType)) { continue; } - PostgreSQLColumnTypeEnum typeEnum = PostgreSQLColumnTypeEnum.getByType(column.getColumnType()); + PostgreSQLColumnTypeEnum typeEnum = PostgreSQLColumnTypeEnum.getByType(columnType); script.append("\t").append(typeEnum.buildCreateColumnSql(column)).append(",\n"); } Map> tableIndexMap = table.getIndexList().stream() @@ -65,7 +70,11 @@ public String buildCreateTableSql(Table table) { } List tableColumnList = table.getColumnList().stream().filter(v -> StringUtils.isNotBlank(v.getComment())).toList(); for (TableColumn tableColumn : tableColumnList) { - PostgreSQLColumnTypeEnum typeEnum = PostgreSQLColumnTypeEnum.getByType(tableColumn.getColumnType()); + String columnType = tableColumn.getDataType(); + if (StringUtils.isBlank(columnType)) { + columnType = tableColumn.getColumnType(); + } + PostgreSQLColumnTypeEnum typeEnum = PostgreSQLColumnTypeEnum.getByType(columnType); script.append(typeEnum.buildComment(tableColumn, typeEnum)).append("\n"); ; } @@ -105,7 +114,11 @@ public String buildModifyTaleSql(Table oldTable, Table newTable) { scriptModify.append("ALTER TABLE ").append("\"").append(newTable.getName()).append("\" \n"); // append modify column for (TableColumn tableColumn : newTable.getColumnList()) { - PostgreSQLColumnTypeEnum typeEnum = PostgreSQLColumnTypeEnum.getByType(tableColumn.getColumnType()); + String columnType = tableColumn.getDataType(); + if (StringUtils.isBlank(columnType)) { + columnType = tableColumn.getColumnType(); + } + PostgreSQLColumnTypeEnum typeEnum = PostgreSQLColumnTypeEnum.getByType(columnType); scriptModify.append("\t").append(typeEnum.buildModifyColumn(tableColumn)).append(",\n"); modify = true; @@ -141,7 +154,11 @@ public String buildModifyTaleSql(Table oldTable, Table newTable) { .append(newTable.getComment()).append("';\n"); } for (TableColumn tableColumn : newTable.getColumnList()) { - PostgreSQLColumnTypeEnum typeEnum = PostgreSQLColumnTypeEnum.getByType(tableColumn.getColumnType()); + String columnType = tableColumn.getDataType(); + if (StringUtils.isBlank(columnType)) { + columnType = tableColumn.getColumnType(); + } + PostgreSQLColumnTypeEnum typeEnum = PostgreSQLColumnTypeEnum.getByType(columnType); script.append(typeEnum.buildComment(tableColumn, typeEnum)).append("\n"); ; } @@ -201,4 +218,57 @@ public String buildCreateSchemaSql(Schema schema){ } return sqlBuilder.toString(); } + + @Override + protected String buildImportUpsertSql(String tableName, List
headerList, List primaryKeyColumns, + MetaData metaSchema) { + StringBuilder sql = new StringBuilder("INSERT INTO "); + sql.append(tableName).append(" ("); + for (int i = 0; i < headerList.size(); i++) { + if (i > 0) sql.append(","); + sql.append(metaSchema.getMetaDataName(headerList.get(i).getName())); + } + sql.append(") VALUES ("); + for (int i = 0; i < headerList.size(); i++) { + if (i > 0) sql.append(","); + sql.append("?"); + } + sql.append(")"); + if (primaryKeyColumns != null && !primaryKeyColumns.isEmpty()) { + sql.append(" ON CONFLICT ("); + for (int i = 0; i < primaryKeyColumns.size(); i++) { + if (i > 0) sql.append(","); + sql.append(metaSchema.getMetaDataName(primaryKeyColumns.get(i))); + } + sql.append(") DO UPDATE SET "); + boolean first = true; + for (Header header : headerList) { + if (primaryKeyColumns.contains(header.getName())) { + continue; + } + if (!first) sql.append(","); + String quotedName = metaSchema.getMetaDataName(header.getName()); + sql.append(quotedName).append("=EXCLUDED.").append(quotedName); + first = false; + } + } + return sql.toString(); + } + + @Override + public String buildOptimizeTableSql(String databaseName, String schemaName, String tableName) { + String tableRef = schemaName != null ? "\"" + schemaName + "\".\"" + tableName + "\"" : "\"" + tableName + "\""; + return "VACUUM ANALYZE " + tableRef; + } + + @Override + public String buildAnalyzeTableSql(String databaseName, String schemaName, String tableName) { + String tableRef = schemaName != null ? "\"" + schemaName + "\".\"" + tableName + "\"" : "\"" + tableName + "\""; + return "ANALYZE " + tableRef; + } + + @Override + public String buildExplainSql(String originalSql) { + return "EXPLAIN (ANALYZE, COSTS, VERBOSE, BUFFERS, FORMAT TEXT) " + originalSql; + } } diff --git a/chat2db-server/chat2db-plugins/chat2db-redis/pom.xml b/chat2db-server/chat2db-plugins/chat2db-redis/pom.xml index 598defa20..2a3962cd6 100644 --- a/chat2db-server/chat2db-plugins/chat2db-redis/pom.xml +++ b/chat2db-server/chat2db-plugins/chat2db-redis/pom.xml @@ -14,6 +14,10 @@ ai.chat2db chat2db-spi + + io.lettuce + lettuce-core + chat2db-redis @@ -31,4 +35,4 @@ - \ No newline at end of file + diff --git a/chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/RedisCommandExecutor.java b/chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/RedisCommandExecutor.java new file mode 100644 index 000000000..59cd754b3 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/RedisCommandExecutor.java @@ -0,0 +1,270 @@ +package ai.chat2db.plugin.redis; + +import ai.chat2db.spi.CommandExecutor; +import ai.chat2db.spi.ValueHandler; +import ai.chat2db.spi.enums.DataTypeEnum; +import ai.chat2db.spi.enums.SqlTypeEnum; +import ai.chat2db.spi.model.Command; +import ai.chat2db.spi.model.ExecuteResult; +import ai.chat2db.spi.model.Header; +import ai.chat2db.spi.sql.Chat2DBContext; +import cn.hutool.core.date.TimeInterval; +import io.lettuce.core.KeyScanCursor; +import io.lettuce.core.ScanArgs; +import io.lettuce.core.ScanCursor; +import io.lettuce.core.api.sync.RedisCommands; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +public class RedisCommandExecutor implements CommandExecutor { + + private static final int DEFAULT_SCAN_COUNT = 1000; + + @Override + public List execute(Command command) { + List statements = + RedisCommandParser.splitStatementPositions(command.getScript()); + List results = new ArrayList<>(); + if (CollectionUtils.isEmpty(statements)) { + return results; + } + try (RedisConnectionProvider.RedisConnectionContext context = + RedisConnectionProvider.open(Chat2DBContext.getConnectInfo())) { + RedisCommands commands = context.connection().sync(); + for (int i = 0; i < statements.size(); i++) { + RedisCommandParser.StatementPosition statement = statements.get(i); + ExecuteResult result = executeStatement(commands, statement.statement()); + result.setStatementIndex(i + 1); + result.setStatementStartLine(getStatementLine(command.getScriptStartLine(), statement.startLine())); + result.setStatementEndLine(getStatementLine(command.getScriptStartLine(), statement.endLine())); + result.setOriginalSql(statement.statement()); + results.add(result); + } + } + return results; + } + + @Override + public ExecuteResult executeUpdate(String sql, Connection connection, int n) throws SQLException { + return execute(sql, connection, true, null, null, null); + } + + @Override + public ExecuteResult execute(String sql, Connection connection, boolean limitRowSize, Integer offset, Integer count, + ValueHandler valueHandler) throws SQLException { + try (RedisConnectionProvider.RedisConnectionContext context = + RedisConnectionProvider.open(Chat2DBContext.getConnectInfo())) { + return executeStatement(context.connection().sync(), sql); + } + } + + private ExecuteResult executeStatement(RedisCommands commands, String statement) { + TimeInterval timer = new TimeInterval(); + ExecuteResult result = ExecuteResult.builder() + .sql(statement) + .success(Boolean.TRUE) + .description("Success") + .sqlType(SqlTypeEnum.UNKNOWN.getCode()) + .build(); + try { + List args = RedisCommandParser.tokenize(statement); + if (CollectionUtils.isEmpty(args)) { + result.setUpdateCount(0); + return result; + } + String command = args.get(0).toUpperCase(Locale.ROOT); + switch (command) { + case "PING" -> oneValue(result, "result", commands.ping()); + case "SELECT" -> status(result, commands.select(parseInt(args, 1, "database"))); + case "CONFIG" -> executeConfig(commands, args, result); + case "DBSIZE" -> oneValue(result, "size", commands.dbsize()); + case "TYPE" -> oneValue(result, "type", commands.type(required(args, 1, "key"))); + case "EXISTS" -> oneValue(result, "exists", commands.exists(tail(args, 1))); + case "TTL" -> oneValue(result, "ttl", commands.ttl(required(args, 1, "key"))); + case "EXPIRE" -> oneValue(result, "result", + commands.expire(required(args, 1, "key"), parseLong(args, 2, "seconds"))); + case "DEL" -> oneValue(result, "deleted", commands.del(tail(args, 1))); + case "GET" -> oneValue(result, "value", commands.get(required(args, 1, "key"))); + case "SET" -> status(result, commands.set(required(args, 1, "key"), required(args, 2, "value"))); + case "KEYS" -> listValues(result, "key", commands.keys(required(args, 1, "pattern"))); + case "SCAN" -> scan(commands, args, result); + case "HGET" -> oneValue(result, "value", commands.hget(required(args, 1, "key"), + required(args, 2, "field"))); + case "HGETALL" -> mapValues(result, "field", "value", commands.hgetall(required(args, 1, "key"))); + case "HSET" -> oneValue(result, "added", commands.hset(required(args, 1, "key"), + fieldValueMap(args, 2))); + case "LRANGE" -> listValues(result, "value", commands.lrange(required(args, 1, "key"), + parseLong(args, 2, "start"), parseLong(args, 3, "stop"))); + case "LPUSH" -> oneValue(result, "length", commands.lpush(required(args, 1, "key"), tail(args, 2))); + case "RPUSH" -> oneValue(result, "length", commands.rpush(required(args, 1, "key"), tail(args, 2))); + case "SMEMBERS" -> listValues(result, "member", commands.smembers(required(args, 1, "key"))); + case "SADD" -> oneValue(result, "added", commands.sadd(required(args, 1, "key"), tail(args, 2))); + case "ZRANGE" -> listValues(result, "member", commands.zrange(required(args, 1, "key"), + parseLong(args, 2, "start"), parseLong(args, 3, "stop"))); + case "ZADD" -> oneValue(result, "added", commands.zadd(required(args, 1, "key"), + parseDouble(args, 2, "score"), required(args, 3, "member"))); + default -> throw new IllegalArgumentException("Unsupported Redis command: " + command); + } + } catch (Exception e) { + result.setSuccess(Boolean.FALSE); + result.setMessage(e.getMessage()); + } finally { + result.setDuration(timer.interval()); + } + return result; + } + + private int getStatementLine(Integer scriptStartLine, int statementLine) { + return statementLine + Math.max((scriptStartLine == null ? 1 : scriptStartLine) - 1, 0); + } + + private void executeConfig(RedisCommands commands, List args, ExecuteResult result) { + if (args.size() >= 3 && "GET".equalsIgnoreCase(args.get(1))) { + mapValues(result, "parameter", "value", commands.configGet(args.get(2))); + return; + } + throw new IllegalArgumentException("Only CONFIG GET is supported"); + } + + private void scan(RedisCommands commands, List args, ExecuteResult result) { + String cursor = ScanCursor.INITIAL.getCursor(); + ScanArgs scanArgs = new ScanArgs().limit(DEFAULT_SCAN_COUNT); + int optionStart = 1; + if (args.size() > 1) { + String firstArg = args.get(1); + if (StringUtils.isNumeric(firstArg)) { + cursor = firstArg; + optionStart = 2; + } else if (!isScanOption(firstArg)) { + scanArgs.match("*" + firstArg + "*"); + optionStart = 2; + } + } + for (int i = optionStart; i < args.size(); i++) { + String token = args.get(i); + if ("MATCH".equalsIgnoreCase(token)) { + scanArgs.match(required(args, ++i, "pattern")); + } else if ("COUNT".equalsIgnoreCase(token)) { + scanArgs.limit(parseLong(args, ++i, "count")); + } + } + KeyScanCursor scan = commands.scan(ScanCursor.of(cursor), scanArgs); + List> rows = new ArrayList<>(); + rows.add(Arrays.asList("cursor", scan.getCursor())); + for (String key : scan.getKeys()) { + rows.add(Arrays.asList("key", key)); + } + result.setHeaderList(headers("type", "value")); + result.setDataList(rows); + result.setSqlType(SqlTypeEnum.SELECT.getCode()); + result.setPageNo(1); + result.setPageSize(rows.size()); + result.setHasNextPage(!scan.isFinished()); + result.setFuzzyTotal(scan.isFinished() ? Integer.toString(rows.size()) : rows.size() + "+"); + } + + private boolean isScanOption(String token) { + return "MATCH".equalsIgnoreCase(token) || "COUNT".equalsIgnoreCase(token); + } + + private void status(ExecuteResult result, String value) { + result.setUpdateCount(1); + oneValue(result, "result", value); + } + + private void oneValue(ExecuteResult result, String name, Object value) { + result.setHeaderList(headers(name)); + result.setDataList(List.of(List.of(String.valueOf(value)))); + result.setSqlType(SqlTypeEnum.SELECT.getCode()); + result.setPageNo(1); + result.setPageSize(1); + result.setHasNextPage(Boolean.FALSE); + result.setFuzzyTotal("1"); + } + + private void listValues(ExecuteResult result, String name, Iterable values) { + List> rows = new ArrayList<>(); + for (Object value : values) { + rows.add(List.of(String.valueOf(value))); + } + result.setHeaderList(headers(name)); + result.setDataList(rows); + result.setSqlType(SqlTypeEnum.SELECT.getCode()); + result.setPageNo(1); + result.setPageSize(rows.size()); + result.setHasNextPage(Boolean.FALSE); + result.setFuzzyTotal(Integer.toString(rows.size())); + } + + private void mapValues(ExecuteResult result, String keyName, String valueName, Map values) { + List> rows = new ArrayList<>(); + for (Map.Entry entry : values.entrySet()) { + rows.add(Arrays.asList(entry.getKey(), entry.getValue())); + } + result.setHeaderList(headers(keyName, valueName)); + result.setDataList(rows); + result.setSqlType(SqlTypeEnum.SELECT.getCode()); + result.setPageNo(1); + result.setPageSize(rows.size()); + result.setHasNextPage(Boolean.FALSE); + result.setFuzzyTotal(Integer.toString(rows.size())); + } + + private List
headers(String... names) { + List
headers = new ArrayList<>(); + for (String name : names) { + headers.add(Header.builder().name(name).dataType(DataTypeEnum.STRING.getCode()).build()); + } + return headers; + } + + private String required(List args, int index, String name) { + if (args.size() <= index || StringUtils.isBlank(args.get(index))) { + throw new IllegalArgumentException("Missing Redis command argument: " + name); + } + return args.get(index); + } + + private String[] tail(List args, int start) { + if (args.size() <= start) { + throw new IllegalArgumentException("Missing Redis command arguments"); + } + return args.subList(start, args.size()).toArray(new String[0]); + } + + private Map fieldValueMap(List args, int start) { + if (args.size() <= start) { + throw new IllegalArgumentException("Missing Redis command arguments"); + } + int pairCount = args.size() - start; + if (pairCount % 2 != 0) { + throw new IllegalArgumentException("Redis HSET requires field/value pairs"); + } + Map values = new LinkedHashMap<>(pairCount / 2); + for (int i = start; i < args.size(); i += 2) { + values.put(args.get(i), args.get(i + 1)); + } + return values; + } + + private int parseInt(List args, int index, String name) { + return Integer.parseInt(required(args, index, name)); + } + + private long parseLong(List args, int index, String name) { + return Long.parseLong(required(args, index, name)); + } + + private double parseDouble(List args, int index, String name) { + return Double.parseDouble(required(args, index, name)); + } +} diff --git a/chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/RedisCommandParser.java b/chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/RedisCommandParser.java new file mode 100644 index 000000000..07bf9f1ab --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/RedisCommandParser.java @@ -0,0 +1,142 @@ +package ai.chat2db.plugin.redis; + +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.List; + +public final class RedisCommandParser { + + private RedisCommandParser() { + } + + public static List splitStatements(String script) { + return splitStatementPositions(script).stream() + .map(StatementPosition::statement) + .toList(); + } + + public static List splitStatementPositions(String script) { + List positions = new ArrayList<>(); + if (StringUtils.isBlank(script)) { + return positions; + } + StringBuilder current = new StringBuilder(); + Character quote = null; + boolean escaped = false; + int statementStartIndex = 0; + for (int i = 0; i < script.length(); i++) { + char ch = script.charAt(i); + if (quote != null) { + current.append(ch); + if (escaped) { + escaped = false; + } else if (ch == '\\') { + escaped = true; + } else if (ch == quote) { + quote = null; + } + continue; + } + if (ch == '\'' || ch == '"') { + quote = ch; + current.append(ch); + continue; + } + if (ch == ';' || ch == '\n' || ch == '\r') { + addStatementPosition(positions, script, current, statementStartIndex, i); + current.setLength(0); + statementStartIndex = i + 1; + continue; + } + current.append(ch); + } + addStatementPosition(positions, script, current, statementStartIndex, script.length()); + return positions; + } + + public static List tokenize(String statement) { + List tokens = new ArrayList<>(); + StringBuilder current = new StringBuilder(); + Character quote = null; + boolean escaped = false; + for (int i = 0; i < statement.length(); i++) { + char ch = statement.charAt(i); + if (quote != null) { + if (escaped) { + current.append(ch); + escaped = false; + } else if (ch == '\\') { + escaped = true; + } else if (ch == quote) { + quote = null; + } else { + current.append(ch); + } + continue; + } + if (Character.isWhitespace(ch)) { + addToken(tokens, current); + continue; + } + if (ch == '\'' || ch == '"') { + quote = ch; + continue; + } + current.append(ch); + } + if (quote != null) { + throw new IllegalArgumentException("Unclosed quoted string"); + } + addToken(tokens, current); + return tokens; + } + + private static void addStatementPosition(List positions, String script, StringBuilder current, + int startIndex, int endExclusive) { + String statement = current.toString().trim(); + if (StringUtils.isBlank(statement)) { + return; + } + int realStart = startIndex; + int realEndExclusive = Math.max(startIndex, endExclusive); + while (realStart < realEndExclusive && Character.isWhitespace(script.charAt(realStart))) { + realStart++; + } + while (realEndExclusive > realStart && Character.isWhitespace(script.charAt(realEndExclusive - 1))) { + realEndExclusive--; + } + positions.add(new StatementPosition( + statement, + getLineNumber(script, realStart), + getLineNumber(script, realEndExclusive - 1) + )); + } + + private static int getLineNumber(String script, int indexInclusive) { + int line = 1; + int max = Math.min(Math.max(indexInclusive, 0), script.length()); + for (int i = 0; i < max; i++) { + char ch = script.charAt(i); + if (ch == '\n') { + line++; + } else if (ch == '\r') { + line++; + if (i + 1 < max && script.charAt(i + 1) == '\n') { + i++; + } + } + } + return line; + } + + private static void addToken(List tokens, StringBuilder current) { + if (current.length() > 0) { + tokens.add(current.toString()); + current.setLength(0); + } + } + + public record StatementPosition(String statement, int startLine, int endLine) { + } +} diff --git a/chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/RedisConnectionProvider.java b/chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/RedisConnectionProvider.java new file mode 100644 index 000000000..e81b71f40 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/RedisConnectionProvider.java @@ -0,0 +1,124 @@ +package ai.chat2db.plugin.redis; + +import ai.chat2db.spi.model.SSHInfo; +import ai.chat2db.spi.sql.ConnectInfo; +import ai.chat2db.spi.ssh.SSHManager; +import com.jcraft.jsch.JSchException; +import com.jcraft.jsch.Session; +import io.lettuce.core.RedisClient; +import io.lettuce.core.RedisURI; +import io.lettuce.core.api.StatefulRedisConnection; +import org.apache.commons.lang3.StringUtils; + +import java.net.URI; +import java.time.Duration; + +public final class RedisConnectionProvider { + + private static final Duration COMMAND_TIMEOUT = Duration.ofSeconds(30); + + private RedisConnectionProvider() { + } + + public static RedisConnectionContext open(ConnectInfo connectInfo) { + RedisConnectionInfo connectionInfo = parse(connectInfo); + Session session = null; + String host = connectionInfo.host(); + int port = connectionInfo.port(); + SSHInfo ssh = connectInfo.getSsh(); + if (ssh != null && ssh.isUse()) { + ssh.setRHost(host); + ssh.setRPort(Integer.toString(port)); + session = SSHManager.getSSHSession(ssh); + host = "127.0.0.1"; + port = Integer.parseInt(ssh.getLocalPort()); + } + + RedisURI.Builder builder = RedisURI.builder() + .withHost(host) + .withPort(port) + .withDatabase(connectionInfo.database()) + .withTimeout(COMMAND_TIMEOUT); + if (StringUtils.isNotBlank(connectInfo.getPassword())) { + if (StringUtils.isNotBlank(connectInfo.getUser())) { + builder.withAuthentication(connectInfo.getUser(), connectInfo.getPassword().toCharArray()); + } else { + builder.withPassword(connectInfo.getPassword().toCharArray()); + } + } + + RedisClient client = RedisClient.create(builder.build()); + StatefulRedisConnection connection = client.connect(); + return new RedisConnectionContext(client, connection, session); + } + + public static void testConnect(ConnectInfo connectInfo) { + try (RedisConnectionContext context = open(connectInfo)) { + context.connection().sync().ping(); + } + } + + public static RedisConnectionInfo parse(ConnectInfo connectInfo) { + String url = connectInfo.getUrl(); + if (StringUtils.isBlank(url)) { + throw new IllegalArgumentException("Redis url is required"); + } + URI uri = URI.create(url); + if (!"redis".equalsIgnoreCase(uri.getScheme())) { + throw new IllegalArgumentException("Redis url must use redis://host:port/db"); + } + if (StringUtils.isBlank(uri.getHost())) { + throw new IllegalArgumentException("Redis host is required"); + } + int port = uri.getPort() > 0 ? uri.getPort() : 6379; + int database = parseDatabase(uri.getPath(), connectInfo.getDatabaseName()); + return new RedisConnectionInfo(uri.getHost(), port, database); + } + + private static int parseDatabase(String path, String databaseName) { + String database = StringUtils.stripStart(path, "/"); + if (StringUtils.isBlank(database)) { + database = databaseName; + } + if (StringUtils.isBlank(database)) { + return 0; + } + try { + int db = Integer.parseInt(database); + if (db < 0) { + throw new IllegalArgumentException("Redis database must be greater than or equal to 0"); + } + return db; + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Redis database must be a number", e); + } + } + + public record RedisConnectionInfo(String host, int port, int database) { + } + + public record RedisConnectionContext( + RedisClient client, + StatefulRedisConnection connection, + Session session + ) implements AutoCloseable { + + @Override + public void close() { + if (connection != null) { + connection.close(); + } + if (client != null) { + client.shutdown(); + } + if (session != null) { + try { + session.delPortForwardingL(Integer.parseInt(session.getPortForwardingL()[0].split(":")[0])); + } catch (JSchException | RuntimeException e) { + // ignore + } + session.disconnect(); + } + } + } +} diff --git a/chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/RedisDBManage.java b/chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/RedisDBManage.java index b9761c5d4..f241934d1 100644 --- a/chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/RedisDBManage.java +++ b/chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/RedisDBManage.java @@ -2,7 +2,16 @@ import ai.chat2db.spi.DBManage; import ai.chat2db.spi.jdbc.DefaultDBManage; +import ai.chat2db.spi.sql.ConnectInfo; + +import java.sql.Connection; public class RedisDBManage extends DefaultDBManage implements DBManage { + @Override + public Connection getConnection(ConnectInfo connectInfo) { + RedisConnectionProvider.testConnect(connectInfo); + return null; + } + } diff --git a/chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/RedisMetaData.java b/chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/RedisMetaData.java index ac85570d2..f49ee176c 100644 --- a/chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/RedisMetaData.java +++ b/chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/RedisMetaData.java @@ -1,57 +1,722 @@ package ai.chat2db.plugin.redis; -import java.sql.Connection; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; - +import ai.chat2db.spi.CommandExecutor; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.jdbc.DefaultMetaService; import ai.chat2db.spi.model.Database; +import ai.chat2db.spi.model.Schema; import ai.chat2db.spi.model.Table; -import ai.chat2db.spi.sql.SQLExecutor; +import ai.chat2db.spi.redis.RedisCommandMonitor; +import ai.chat2db.spi.redis.RedisKeyBrowser; +import ai.chat2db.spi.redis.RedisKeyInfo; +import ai.chat2db.spi.redis.RedisKeyScanResult; +import ai.chat2db.spi.ssh.SSHManager; +import ai.chat2db.spi.sql.Chat2DBContext; +import ai.chat2db.spi.sql.ConnectInfo; +import com.jcraft.jsch.JSchException; +import com.jcraft.jsch.Session; +import com.google.common.collect.Lists; +import io.lettuce.core.ScanArgs; +import io.lettuce.core.ScanCursor; +import io.lettuce.core.api.async.RedisAsyncCommands; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; -public class RedisMetaData extends DefaultMetaService implements MetaData { +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.Socket; +import java.nio.charset.StandardCharsets; +import java.sql.Connection; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.StringJoiner; +import java.util.concurrent.CompletableFuture; +import java.util.function.BooleanSupplier; +import java.util.function.Consumer; + +/** + * Redis 元数据和键浏览服务实现。 + *

+ * 该类负责: + * - 获取 Redis 数据库列表 + * - 浏览和查询 Redis 键信息 + * - 创建、更新、删除 Redis 键 + * - 监控 Redis 命令流 + */ +@Slf4j +public class RedisMetaData extends DefaultMetaService implements MetaData, RedisKeyBrowser, RedisCommandMonitor { + + private static final int KEY_SCAN_BATCH_SIZE = 1000; + private static final int KEY_INFO_EMIT_BATCH_SIZE = 50; + private static final int VALUE_PREVIEW_LIMIT = 5; + private static final int VALUE_TEXT_LIMIT = 200; + private static final CommandExecutor COMMAND_EXECUTOR = new RedisCommandExecutor(); @Override public List databases(Connection connection) { - List databases = new ArrayList<>(); - return SQLExecutor.getInstance().execute(connection,"config get databases", resultSet -> { - try { - if (resultSet.next()) { - Object count = resultSet.getObject(2); - if(StringUtils.isNotBlank(count.toString())) { - for (int i = 0; i < Integer.parseInt(count.toString()); i++) { - Database database = Database.builder().name(String.valueOf(i)).build(); - databases.add(database); - } - } + try (RedisConnectionProvider.RedisConnectionContext context = + RedisConnectionProvider.open(Chat2DBContext.getConnectInfo())) { + RedisAsyncCommands commands = context.connection().async(); + Map config = commands.configGet("databases").toCompletableFuture().join(); + String count = config.get("databases"); + List databases = new ArrayList<>(); + if (StringUtils.isNotBlank(count)) { + for (int i = 0; i < Integer.parseInt(count); i++) { + databases.add(Database.builder().name(String.valueOf(i)).build()); } - } catch (SQLException e) { - throw new RuntimeException(e); } return databases; - }); + } + } + + @Override + @SneakyThrows + public List schemas(Connection connection, String databaseName) { + return List.of(); } @Override + @SneakyThrows public List

tables(Connection connection, String databaseName, String schemaName, String tableName) { - return SQLExecutor.getInstance().execute(connection,"scan 0 MATCH * COUNT 1000", resultSet -> { - List
tables = new ArrayList<>(); - try { - while (resultSet.next()) { - ArrayList list = (ArrayList)resultSet.getObject(2); - for (Object object : list) { - Table table = new Table(); - table.setName(object.toString()); - tables.add(table); + return List.of(); + } + + @Override + public CommandExecutor getCommandExecutor() { + // 提供 Redis 命令执行器,用于执行 Redis 相关 SQL 转换后的命令。 + return COMMAND_EXECUTOR; + } + + + @Override + public CompletableFuture streamKeys(String databaseName, String searchKey, String cursor, + int count, + Consumer> batchConsumer) { + // 流式扫描 Redis 键并按批次返回,支持分页和模糊匹配。 + try { + RedisConnectionProvider.RedisConnectionContext context = + RedisConnectionProvider.open(Chat2DBContext.getConnectInfo()); + RedisAsyncCommands commands = context.connection().async(); + CompletableFuture result = selectDatabase(commands, databaseName) + .thenCompose(ignored -> { + String pattern = StringUtils.isBlank(searchKey) ? null : searchKey; + if (isInitialCursor(cursor) && isExactKeyPattern(pattern)) { + return queryExactKeyAsync(commands, pattern, batchConsumer); + } + // count < 0 means fetch all keys for the Redis data page. + long limit = count < 0 ? Long.MAX_VALUE : count == 0 ? KEY_SCAN_BATCH_SIZE : count; + return scanKeyInfoAsync(commands, pattern, cursor, limit, batchConsumer); + }); + return result.whenComplete((ignored, throwable) -> { + try { + context.close(); + } catch (Exception e) { + log.warn("Redis connection close failed", e); + } + }); + } catch (Exception e) { + return CompletableFuture.failedFuture(e); + } + } + + @Override + public RedisKeyInfo queryKey(String databaseName, String keyName) { + try (RedisConnectionProvider.RedisConnectionContext context = + RedisConnectionProvider.open(Chat2DBContext.getConnectInfo())) { + RedisAsyncCommands commands = context.connection().async(); + selectDatabase(commands, databaseName).join(); + return buildKeyInfo(commands, keyName, true).join(); + } + } + + @Override + public void createKey(String databaseName, String keyName, String keyType, Object value, Long ttl) { + // 创建新的 Redis 键,如果键已存在则抛出异常。 + try (RedisConnectionProvider.RedisConnectionContext context = + RedisConnectionProvider.open(Chat2DBContext.getConnectInfo())) { + RedisAsyncCommands commands = context.connection().async(); + if (StringUtils.isBlank(keyName)) { + throw new IllegalArgumentException("Redis key 不能为空"); + } + selectDatabase(commands, databaseName) + .thenCompose(ignored -> commands.exists(keyName).toCompletableFuture()) + .thenApply(exists -> { + if (exists > 0) { + throw new IllegalArgumentException("Redis key 已存在: " + keyName); + } + return null; + }) + .thenCompose(ignored -> writeValue(commands, keyName, keyType, value)) + .thenCompose(ignored -> commands.exists(keyName).toCompletableFuture()) + .thenApply(exists -> { + if (exists == 0) { + throw new IllegalArgumentException("Redis key value 不能为空"); + } + return null; + }) + .thenCompose(ignored -> applyTtl(commands, keyName, ttl)) + .join(); + } + } + + @Override + public void updateKey(String databaseName, String originalKey, String updateKey, String keyType, Object value, + Long ttl) { + try (RedisConnectionProvider.RedisConnectionContext context = + RedisConnectionProvider.open(Chat2DBContext.getConnectInfo())) { + RedisAsyncCommands commands = context.connection().async(); + String targetKey = StringUtils.defaultIfBlank(updateKey, originalKey); + selectDatabase(commands, databaseName) + .thenCompose(ignored -> { + if (!StringUtils.equals(originalKey, targetKey)) { + return commands.rename(originalKey, targetKey).toCompletableFuture(); + } + return CompletableFuture.completedFuture(null); + }) + .thenCompose(ignored -> commands.del(targetKey).toCompletableFuture()) + .thenCompose(ignored -> writeValue(commands, targetKey, keyType, value)) + .thenCompose(ignored -> applyTtl(commands, targetKey, ttl)) + .join(); + } + } + + @Override + public void partialUpdateKey(String databaseName, String keyName, String keyType, + Map addedFields, List removedFields, Long ttl) { + try (RedisConnectionProvider.RedisConnectionContext context = + RedisConnectionProvider.open(Chat2DBContext.getConnectInfo())) { + RedisAsyncCommands commands = context.connection().async(); + selectDatabase(commands, databaseName) + .thenCompose(ignored -> { + if (addedFields != null && !addedFields.isEmpty()) { + return commands.hset(keyName, addedFields).toCompletableFuture(); + } + return CompletableFuture.completedFuture(null); + }) + .thenCompose(ignored -> { + if (removedFields != null && !removedFields.isEmpty()) { + return commands.hdel(keyName, removedFields.toArray(new String[0])).toCompletableFuture(); + } + return CompletableFuture.completedFuture(null); + }) + .thenCompose(ignored -> applyTtl(commands, keyName, ttl)) + .join(); + } + } + + @Override + public void deleteKey(String databaseName, String keyName) { + // 删除指定 Redis 键,空键名将被忽略。 + try (RedisConnectionProvider.RedisConnectionContext context = + RedisConnectionProvider.open(Chat2DBContext.getConnectInfo())) { + RedisAsyncCommands commands = context.connection().async(); + selectDatabase(commands, databaseName) + .thenCompose(ignored -> { + if (StringUtils.isNotBlank(keyName)) { + return commands.del(keyName).toCompletableFuture(); + } + return CompletableFuture.completedFuture(null); + }) + .join(); + } + } + + @Override + public void monitor(String databaseName, Consumer lineConsumer, BooleanSupplier running) { + // 使用原生 Redis MONITOR 命令,输出实时命令流。 + ConnectInfo connectInfo = Chat2DBContext.getConnectInfo(); + RedisConnectionProvider.RedisConnectionInfo connectionInfo = RedisConnectionProvider.parse(connectInfo); + Session session = null; + String host = connectionInfo.host(); + int port = connectionInfo.port(); + if (connectInfo.getSsh() != null && connectInfo.getSsh().isUse()) { + connectInfo.getSsh().setRHost(host); + connectInfo.getSsh().setRPort(Integer.toString(port)); + session = SSHManager.getSSHSession(connectInfo.getSsh()); + host = "127.0.0.1"; + port = Integer.parseInt(connectInfo.getSsh().getLocalPort()); + } + try (Socket socket = new Socket(host, port); + BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), + StandardCharsets.UTF_8)); + OutputStream writer = socket.getOutputStream()) { + socket.setSoTimeout(1000); + authenticate(connectInfo, writer, reader); + selectMonitorDatabase(databaseName, writer, reader); + writeCommand(writer, "MONITOR"); + expectStatus(reader, "OK"); + while (running.getAsBoolean() && !socket.isClosed()) { + try { + String line = reader.readLine(); + if (line == null) { + return; + } + lineConsumer.accept(redactMonitorLine(stripRespSimpleString(line))); + } catch (java.net.SocketTimeoutException ignored) { + // allow cancellation check + } + } + } catch (Exception e) { + if (running.getAsBoolean()) { + throw new IllegalStateException("Redis monitor failed: " + e.getMessage(), e); + } + } finally { + closeSshSession(session); + } + } + + /** + * 使用 SCAN 命令分批获取 Redis 键信息,并将结果按批次回传。 + */ + private CompletableFuture scanKeyInfoAsync(RedisAsyncCommands commands, + String pattern, String cursor, long count, + Consumer> batchConsumer) { + ScanArgs scanArgs = new ScanArgs().limit(KEY_SCAN_BATCH_SIZE); + if (StringUtils.isNotBlank(pattern)) { + scanArgs.match(pattern); + } + RedisKeyInfoBatchEmitter emitter = new RedisKeyInfoBatchEmitter(batchConsumer); + return scanKeyInfoAsync(commands, scanArgs, buildScanCursor(cursor), count, 0, emitter); + } + + private CompletableFuture scanKeyInfoAsync(RedisAsyncCommands commands, + ScanArgs scanArgs, ScanCursor scanCursor, + long count, long emitted, + RedisKeyInfoBatchEmitter emitter) { + if (emitted >= count) { + return emitter.finish().thenApply(ignored -> buildScanResult(scanCursor, emitted)); + } + return commands.scan(scanCursor, scanArgs).toCompletableFuture() + .thenCompose(result -> { + List keys = result.getKeys(); + long remaining = count - emitted; + List selectedKeys = keys.size() > remaining + ? keys.subList(0, Math.toIntExact(remaining)) + : keys; + long nextEmitted = emitted + selectedKeys.size(); + ScanCursor nextCursor = ScanCursor.of(result.getCursor()); + nextCursor.setFinished(result.isFinished()); + boolean shouldStop = result.isFinished() || nextEmitted >= count; + return emitKeyInfoAsync(commands, selectedKeys, emitter) + .thenCompose(ignored -> { + if (shouldStop) { + return emitter.finish() + .thenApply(unused -> buildScanResult(nextCursor, nextEmitted)); + } + return scanKeyInfoAsync(commands, scanArgs, nextCursor, count, nextEmitted, emitter); + }); + }); + } + + private CompletableFuture queryExactKeyAsync(RedisAsyncCommands commands, + String key, + Consumer> batchConsumer) { + RedisKeyInfoBatchEmitter emitter = new RedisKeyInfoBatchEmitter(batchConsumer); + return commands.exists(key).toCompletableFuture() + .thenCompose(exists -> { + if (exists <= 0) { + return CompletableFuture.completedFuture(0); + } + return buildKeyInfo(commands, key, false) + .thenCompose(emitter::accept) + .thenApply(ignored -> 1); + }) + .thenCompose(total -> emitter.finish() + .thenApply(ignored -> RedisKeyScanResult.builder() + .cursor("0") + .hasMore(false) + .total(total) + .build())); + } + + private CompletableFuture emitKeyInfoAsync(RedisAsyncCommands commands, List keys, + RedisKeyInfoBatchEmitter emitter) { + if (keys.isEmpty()) { + return CompletableFuture.completedFuture(null); + } + return buildKeyInfoBatch(commands, keys, false) + .thenCompose(futures -> CompletableFuture.allOf(futures.stream() + .map(future -> future.thenCompose(emitter::accept)) + .toArray(CompletableFuture[]::new))); + } + + private RedisKeyScanResult buildScanResult(ScanCursor scanCursor, long emitted) { + boolean hasMore = !scanCursor.isFinished(); + return RedisKeyScanResult.builder() + .cursor(hasMore ? scanCursor.getCursor() : "0") + .hasMore(hasMore) + .total(Math.toIntExact(Math.min(emitted, Integer.MAX_VALUE))) + .build(); + } + + private ScanCursor buildScanCursor(String cursor) { + if (isInitialCursor(cursor)) { + return ScanCursor.INITIAL; + } + return ScanCursor.of(cursor); + } + + private boolean isInitialCursor(String cursor) { + return StringUtils.isBlank(cursor) || "0".equals(cursor); + } + + private boolean isExactKeyPattern(String pattern) { + return StringUtils.isNotBlank(pattern) + && !StringUtils.containsAny(pattern, '*', '?', '[', ']'); + } + + /** + * 构建键信息。 + * + * @param fullValue true 读取完整值(详情场景),false 读取预览摘要(列表场景) + */ + private CompletableFuture buildKeyInfo(RedisAsyncCommands commands, String key, + boolean fullValue) { + return getTypeAsync(commands, key).thenCompose(type -> buildKeyInfo(commands, key, type, fullValue)); + } + + /** + * 批量构建键信息:先并发获取本批 key 的类型,再按类型并发读取预览、TTL 和大小。 + */ + private CompletableFuture>> buildKeyInfoBatch( + RedisAsyncCommands commands, List keys, boolean fullValue) { + List> typeFutures = new ArrayList<>(keys.size()); + keys.forEach(key -> typeFutures.add(getTypeAsync(commands, key))); + return CompletableFuture.allOf(typeFutures.toArray(new CompletableFuture[0])) + .thenApply(ignored -> { + List> keyInfoFutures = new ArrayList<>(keys.size()); + for (int i = 0; i < keys.size(); i++) { + keyInfoFutures.add(buildKeyInfo(commands, keys.get(i), typeFutures.get(i).join(), fullValue)); } + return keyInfoFutures; + }); + } + + private CompletableFuture buildKeyInfo(RedisAsyncCommands commands, String key, + String type, boolean fullValue) { + CompletableFuture value = fullValue ? readValue(commands, key, type) + : previewValue(commands, key, type); + CompletableFuture ttl = commands.ttl(key).toCompletableFuture() + .exceptionally(e -> null); + CompletableFuture size = commands.memoryUsage(key).toCompletableFuture() + .exceptionally(e -> null); + return CompletableFuture.allOf(value, ttl, size) + .thenApply(ignored -> RedisKeyInfo.builder() + .name(key) + .type(type) + .value(value.join()) + .ttl(ttl.join()) + .size(size.join()) + .build()) + .exceptionally(e -> RedisKeyInfo.builder() + .name(key) + .type("unknown") + .value("") + .build()); + } + + /** + * 预览键值内容,仅返回简短摘要用于界面展示。 + */ + private CompletableFuture previewValue(RedisAsyncCommands commands, String key, + String type) { + try { + return switch (StringUtils.defaultString(type).toLowerCase()) { + case "string" -> commands.getrange(key, 0, VALUE_TEXT_LIMIT).toCompletableFuture() + .thenApply(this::abbreviate); + case "hash" -> commands.hscan(key, ScanCursor.INITIAL, + new ScanArgs().limit(VALUE_PREVIEW_LIMIT + 1)).toCompletableFuture() + .thenApply(cursor -> previewMap(cursor.getMap())); + case "list" -> commands.lrange(key, 0, VALUE_PREVIEW_LIMIT - 1).toCompletableFuture() + .thenApply(this::previewList); + case "set" -> commands.srandmember(key, VALUE_PREVIEW_LIMIT).toCompletableFuture() + .thenApply(this::previewList); + case "zset" -> commands.zrange(key, 0, VALUE_PREVIEW_LIMIT - 1).toCompletableFuture() + .thenApply(this::previewList); + default -> CompletableFuture.completedFuture(""); + }; + } catch (Exception e) { + return CompletableFuture.completedFuture(""); + } + } + + /** + * 根据键类型异步读取完整值。 + */ + private CompletableFuture readValue(RedisAsyncCommands commands, String key, String type) { + try { + return switch (StringUtils.defaultString(type).toLowerCase()) { + case "string" -> commands.get(key).toCompletableFuture().thenApply(v -> v); + case "hash" -> commands.hgetall(key).toCompletableFuture().thenApply(v -> v); + case "list" -> commands.lrange(key, 0, -1).toCompletableFuture().thenApply(v -> v); + case "set" -> commands.smembers(key).toCompletableFuture().thenApply(v -> v); + case "zset" -> commands.zrange(key, 0, -1).toCompletableFuture().thenApply(v -> v); + default -> CompletableFuture.completedFuture(""); + }; + } catch (Exception e) { + log.warn("Redis key value read failed, key={}", key, e); + return CompletableFuture.completedFuture(""); + } + } + + /** + * 根据键类型异步写入值。 + */ + private CompletableFuture writeValue(RedisAsyncCommands commands, String key, String keyType, + Object value) { + return switch (StringUtils.defaultString(keyType).toLowerCase()) { + case "string" -> commands.set(key, value == null ? "" : String.valueOf(value)) + .toCompletableFuture().thenApply(ignored -> null); + case "hash" -> { + Map map = toStringMap(value); + if (!map.isEmpty()) { + yield commands.hset(key, map).toCompletableFuture().thenApply(ignored -> null); } - } catch (SQLException e) { - throw new RuntimeException(e); + yield CompletableFuture.completedFuture(null); + } + case "list" -> { + List values = toStringList(value); + if (!values.isEmpty()) { + yield commands.rpush(key, values.toArray(new String[0])) + .toCompletableFuture().thenApply(ignored -> null); + } + yield CompletableFuture.completedFuture(null); + } + case "set" -> { + List values = toStringList(value); + if (!values.isEmpty()) { + yield commands.sadd(key, values.toArray(new String[0])) + .toCompletableFuture().thenApply(ignored -> null); + } + yield CompletableFuture.completedFuture(null); + } + case "zset" -> { + List values = toStringList(value); + yield addZsetMembers(commands, key, values, 0); + } + default -> throw new IllegalArgumentException("暂不支持编辑 Redis 类型: " + keyType); + }; + } + + /** + * 按顺序异步添加有序集合成员,保证添加顺序。 + */ + private CompletableFuture addZsetMembers(RedisAsyncCommands commands, String key, + List values, int index) { + if (index >= values.size()) { + return CompletableFuture.completedFuture(null); + } + return commands.zadd(key, index, values.get(index)).toCompletableFuture() + .thenCompose(ignored -> addZsetMembers(commands, key, values, index + 1)); + } + + /** + * 异步将 TTL 应用到指定键,如 ttl 为 null 或负值则取消过期时间。 + */ + private CompletableFuture applyTtl(RedisAsyncCommands commands, String key, Long ttl) { + if (ttl == null || ttl < 0) { + return commands.persist(key).toCompletableFuture().thenApply(ignored -> null); + } + if (ttl > 0) { + return commands.expire(key, ttl).toCompletableFuture().thenApply(ignored -> null); + } + return CompletableFuture.completedFuture(null); + } + + /** + * 将任意对象转换为字符串键值映射,供 Hash 类型写入使用。 + */ + private Map toStringMap(Object value) { + Map result = new LinkedHashMap<>(); + if (!(value instanceof Map map)) { + return result; + } + map.forEach((field, fieldValue) -> { + if (field != null) { + result.put(String.valueOf(field), fieldValue == null ? "" : String.valueOf(fieldValue)); } - return tables; }); + return result; + } + + private List toStringList(Object value) { + if (value instanceof Collection collection) { + return collection.stream().map(item -> item == null ? "" : String.valueOf(item)).toList(); + } + if (value == null) { + return List.of(); + } + return List.of(String.valueOf(value)); + } + + /** + * 通过原始 RESP 协议发送 AUTH 命令并验证返回结果。 + */ + private void authenticate(ConnectInfo connectInfo, OutputStream writer, BufferedReader reader) + throws IOException { + if (StringUtils.isBlank(connectInfo.getPassword())) { + return; + } + if (StringUtils.isNotBlank(connectInfo.getUser())) { + writeCommand(writer, "AUTH", connectInfo.getUser(), connectInfo.getPassword()); + } else { + writeCommand(writer, "AUTH", connectInfo.getPassword()); + } + expectStatus(reader, "OK"); + } + + /** + * 为监控连接选择指定数据库。 + */ + private void selectMonitorDatabase(String databaseName, OutputStream writer, BufferedReader reader) + throws IOException { + if (StringUtils.isBlank(databaseName)) { + return; + } + writeCommand(writer, "SELECT", databaseName); + expectStatus(reader, "OK"); + } + + private void writeCommand(OutputStream writer, String... args) throws IOException { + writer.write(("*" + args.length + "\r\n").getBytes(StandardCharsets.UTF_8)); + for (String arg : args) { + String value = StringUtils.defaultString(arg); + byte[] bytes = value.getBytes(StandardCharsets.UTF_8); + writer.write(("$" + bytes.length + "\r\n").getBytes(StandardCharsets.UTF_8)); + writer.write(bytes); + writer.write("\r\n".getBytes(StandardCharsets.UTF_8)); + } + writer.flush(); + } + + private void expectStatus(BufferedReader reader, String expected) throws IOException { + String line = reader.readLine(); + if (line == null) { + throw new IOException("Redis connection closed"); + } + if (line.startsWith("-")) { + throw new IOException(line.substring(1)); + } + String value = stripRespSimpleString(line); + if (!expected.equalsIgnoreCase(value)) { + throw new IOException("Unexpected Redis response: " + value); + } + } + + private String stripRespSimpleString(String line) { + if (StringUtils.isBlank(line)) { + return ""; + } + if (line.charAt(0) == '+' || line.charAt(0) == '$') { + return line.substring(1); + } + return line; + } + + private String redactMonitorLine(String line) { + return line.replaceAll("(?i)(\"AUTH\"\\s+(\"[^\"]*\"\\s+)?\")([^\"]*)(\")", "$1(redacted)$4"); + } + + private void closeSshSession(Session session) { + if (session == null) { + return; + } + try { + session.delPortForwardingL(Integer.parseInt(session.getPortForwardingL()[0].split(":")[0])); + } catch (JSchException | RuntimeException e) { + // ignore + } + session.disconnect(); + } + + private String previewMap(Map values) { + StringJoiner joiner = new StringJoiner(", ", "[", values.size() > VALUE_PREVIEW_LIMIT ? ", ...]" : "]"); + int index = 0; + for (Map.Entry entry : values.entrySet()) { + if (index++ >= VALUE_PREVIEW_LIMIT) { + break; + } + joiner.add(entry.getKey() + ":" + abbreviate(entry.getValue())); + } + return joiner.toString(); + } + + private String previewList(List values) { + StringJoiner joiner = new StringJoiner(", ", "[", values.size() > VALUE_PREVIEW_LIMIT ? ", ...]" : "]"); + int limit = Math.min(values.size(), VALUE_PREVIEW_LIMIT); + for (int i = 0; i < limit; i++) { + joiner.add(abbreviate(values.get(i))); + } + return joiner.toString(); + } + + private String abbreviate(String value) { + if (value == null || value.length() <= VALUE_TEXT_LIMIT) { + return value; + } + return value.substring(0, VALUE_TEXT_LIMIT) + "..."; + } + + private CompletableFuture selectDatabase(RedisAsyncCommands commands, String databaseName) { + if (StringUtils.isBlank(databaseName)) { + return CompletableFuture.completedFuture(null); + } + return commands.select(Integer.parseInt(databaseName)).toCompletableFuture().thenApply(ignored -> null); + } + + /** + * 异步获取键类型,失败时默认返回 "string"。 + */ + private CompletableFuture getTypeAsync(RedisAsyncCommands commands, String key) { + return commands.type(key).toCompletableFuture() + .thenApply(type -> StringUtils.isNotBlank(type) ? type : "string") + .exceptionally(e -> { + log.error("type获取失败", e); + return "string"; + }); + } + + private static class RedisKeyInfoBatchEmitter { + + private final Object lock = new Object(); + private final Consumer> batchConsumer; + private final List buffer = new ArrayList<>(KEY_INFO_EMIT_BATCH_SIZE); + private CompletableFuture tail = CompletableFuture.completedFuture(null); + + private RedisKeyInfoBatchEmitter(Consumer> batchConsumer) { + this.batchConsumer = batchConsumer; + } + + private CompletableFuture accept(RedisKeyInfo keyInfo) { + synchronized (lock) { + buffer.add(keyInfo); + if (buffer.size() < KEY_INFO_EMIT_BATCH_SIZE) { + return tail; + } + return flushLocked(); + } + } + + private CompletableFuture finish() { + synchronized (lock) { + return flushLocked(); + } + } + + private CompletableFuture flushLocked() { + if (buffer.isEmpty()) { + return tail; + } + List batch = List.copyOf(buffer); + buffer.clear(); + tail = tail.thenRunAsync(() -> batchConsumer.accept(batch)); + return tail; + } } } diff --git a/chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/redis.json b/chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/redis.json index a10b33ae5..8f3e8dd2a 100644 --- a/chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/redis.json +++ b/chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/redis.json @@ -1,17 +1,12 @@ { "dbType": "REDIS", - "supportDatabase": false, + "supportDatabase": true, "supportSchema": false, "driverConfigList": [ { - "url": "jdbc:redis://127.0.0.1:6379/0", + "url": "redis://127.0.0.1:6379/0", "custom": false, - "defaultDriver": true, - "downloadJdbcDriverUrls": [ - "https://oss.sqlgpt.cn/lib/redis-jdbc-driver-1.3.jar" - ], - "jdbcDriver": "redis-jdbc-driver-1.3.jar", - "jdbcDriverClass": "jdbc.RedisDriver" + "defaultDriver": true } ], "name": "Redis" diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/SqliteMetaData.java b/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/SqliteMetaData.java index 4fb72270a..44a1a361f 100644 --- a/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/SqliteMetaData.java +++ b/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/SqliteMetaData.java @@ -51,7 +51,7 @@ public SqlBuilder getSqlBuilder() { return new SqliteBuilder(); } @Override - public TableMeta getTableMeta(String databaseName, String schemaName, String tableName) { + public TableMeta getTableMeta(Connection connection, String databaseName, String schemaName) { return TableMeta.builder() .columnTypes(SqliteColumnTypeEnum.getTypes()) .charsets(null) diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/builder/SqliteBuilder.java b/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/builder/SqliteBuilder.java index 8c28e9e60..e25068226 100644 --- a/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/builder/SqliteBuilder.java +++ b/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/builder/SqliteBuilder.java @@ -19,10 +19,14 @@ public String buildCreateTableSql(Table table) { // append column for (TableColumn column : table.getColumnList()) { - if (StringUtils.isBlank(column.getName()) || StringUtils.isBlank(column.getColumnType())) { + String columnType = column.getDataType(); + if (StringUtils.isBlank(columnType)) { + columnType = column.getColumnType(); + } + if (StringUtils.isBlank(column.getName()) || StringUtils.isBlank(columnType)) { continue; } - SqliteColumnTypeEnum typeEnum = SqliteColumnTypeEnum.getByType(column.getColumnType()); + SqliteColumnTypeEnum typeEnum = SqliteColumnTypeEnum.getByType(columnType); script.append("\t").append(typeEnum.buildCreateColumnSql(column)).append(",\n"); } for (TableIndex tableIndex : table.getIndexList()) { @@ -58,9 +62,13 @@ public String buildModifyTaleSql(Table oldTable, Table newTable) { // append modify column for (TableColumn tableColumn : newTable.getColumnList()) { - if (StringUtils.isNotBlank(tableColumn.getEditStatus()) && StringUtils.isNotBlank(tableColumn.getColumnType()) && StringUtils.isNotBlank(tableColumn.getName())) { + String columnType = tableColumn.getDataType(); + if (StringUtils.isBlank(columnType)) { + columnType = tableColumn.getColumnType(); + } + if (StringUtils.isNotBlank(tableColumn.getEditStatus()) && StringUtils.isNotBlank(columnType) && StringUtils.isNotBlank(tableColumn.getName())) { script.append("ALTER TABLE ").append("\"").append(newTable.getDatabaseName()).append("\".\"").append(newTable.getName()).append("\"").append("\n"); - SqliteColumnTypeEnum typeEnum = SqliteColumnTypeEnum.getByType(tableColumn.getColumnType()); + SqliteColumnTypeEnum typeEnum = SqliteColumnTypeEnum.getByType(columnType); script.append("\t").append(typeEnum.buildModifyColumn(tableColumn)).append(";\n"); } } diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/SqlServerCommandExecutor.java b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/SqlServerCommandExecutor.java index f30bf5f02..145a030cb 100644 --- a/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/SqlServerCommandExecutor.java +++ b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/SqlServerCommandExecutor.java @@ -44,6 +44,7 @@ public ExecuteResult executeUpdate(String sql, Connection connection, int n) thr /** * */ + @Override public ExecuteResult execute(final String sql, Connection connection, boolean limitRowSize, Integer offset, Integer count, ValueHandler valueHandler) throws SQLException { diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/SqlServerMetaData.java b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/SqlServerMetaData.java index 88498eafc..b02c97ff6 100644 --- a/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/SqlServerMetaData.java +++ b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/SqlServerMetaData.java @@ -163,7 +163,7 @@ public Function function(Connection connection, @NotEmpty String databaseName, S Function function = new Function(); function.setDatabaseName(databaseName); function.setSchemaName(schemaName); - function.setFunctionName(functionName); + function.setName(functionName); if (resultSet.next()) { function.setFunctionBody(resultSet.getString("definition")); } @@ -180,7 +180,7 @@ public List functions(Connection connection, String databaseName, Stri Function function = new Function(); function.setDatabaseName(databaseName); function.setSchemaName(schemaName); - function.setFunctionName(resultSet.getString("name")); + function.setName(resultSet.getString("name")); functions.add(function); } return functions; @@ -188,11 +188,11 @@ public List functions(Connection connection, String databaseName, Stri } private Function removeVersion(Function function) { - String fullFunctionName = function.getFunctionName(); + String fullFunctionName = function.getName(); if (!StringUtils.isEmpty(fullFunctionName)) { String[] parts = fullFunctionName.split(";"); String functionName = parts[0]; - function.setFunctionName(functionName); + function.setName(functionName); } return function; } @@ -206,7 +206,7 @@ public List procedures(Connection connection, String databaseName, St Procedure procedure = new Procedure(); procedure.setDatabaseName(databaseName); procedure.setSchemaName(schemaName); - procedure.setProcedureName(resultSet.getString("name")); + procedure.setName(resultSet.getString("name")); procedures.add(procedure); } return procedures; @@ -214,11 +214,11 @@ public List procedures(Connection connection, String databaseName, St } private Procedure removeVersion(Procedure procedure) { - String fullProcedureName = procedure.getProcedureName(); + String fullProcedureName = procedure.getName(); if (!StringUtils.isEmpty(fullProcedureName)) { String[] parts = fullProcedureName.split(";"); String procedureName = parts[0]; - procedure.setProcedureName(procedureName); + procedure.setName(procedureName); } return procedure; } @@ -239,7 +239,7 @@ public List triggers(Connection connection, String databaseName, String return SQLExecutor.getInstance().execute(connection, TRIGGER_SQL_LIST, resultSet -> { while (resultSet.next()) { Trigger trigger = new Trigger(); - trigger.setTriggerName(resultSet.getString("triggerName")); + trigger.setName(resultSet.getString("triggerName")); trigger.setSchemaName(schemaName); trigger.setDatabaseName(databaseName); triggers.add(trigger); @@ -257,7 +257,7 @@ public Trigger trigger(Connection connection, @NotEmpty String databaseName, Str Trigger trigger = new Trigger(); trigger.setDatabaseName(databaseName); trigger.setSchemaName(schemaName); - trigger.setTriggerName(triggerName); + trigger.setName(triggerName); if (resultSet.next()) { trigger.setTriggerBody(resultSet.getString("triggerDefinition")); } @@ -273,7 +273,7 @@ public Procedure procedure(Connection connection, @NotEmpty String databaseName, Procedure procedure = new Procedure(); procedure.setDatabaseName(databaseName); procedure.setSchemaName(schemaName); - procedure.setProcedureName(procedureName); + procedure.setName(procedureName); if (resultSet.next()) { procedure.setProcedureBody(resultSet.getString("definition")); } @@ -378,7 +378,7 @@ public SqlBuilder getSqlBuilder() { } @Override - public TableMeta getTableMeta(String databaseName, String schemaName, String tableName) { + public TableMeta getTableMeta(Connection connection, String databaseName, String schemaName) { return TableMeta.builder() .columnTypes(SqlServerColumnTypeEnum.getTypes()) .charsets(null) @@ -391,7 +391,7 @@ public TableMeta getTableMeta(String databaseName, String schemaName, String tab @Override public String getMetaDataName(String... names) { - return Arrays.stream(names).filter(name -> StringUtils.isNotBlank(name)).map(name -> "[" + name + "]").collect(Collectors.joining(".")); + return Arrays.stream(names).filter(StringUtils::isNotBlank).map(name -> "[" + name + "]").collect(Collectors.joining(".")); } @Override diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/builder/SqlServerSqlBuilder.java b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/builder/SqlServerSqlBuilder.java index 77e8d524f..daeba0081 100644 --- a/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/builder/SqlServerSqlBuilder.java +++ b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/builder/SqlServerSqlBuilder.java @@ -2,12 +2,15 @@ import ai.chat2db.plugin.sqlserver.type.SqlServerColumnTypeEnum; import ai.chat2db.plugin.sqlserver.type.SqlServerIndexTypeEnum; +import ai.chat2db.spi.MetaData; import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.jdbc.DefaultSqlBuilder; import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.Chat2DBContext; import org.apache.commons.lang3.StringUtils; +import java.util.List; + public class SqlServerSqlBuilder extends DefaultSqlBuilder implements SqlBuilder { @Override public String buildCreateTableSql(Table table) { @@ -16,10 +19,14 @@ public String buildCreateTableSql(Table table) { script.append("CREATE TABLE ").append("[").append(table.getSchemaName()).append("].[").append(table.getName()).append("] (").append("\n"); for (TableColumn column : table.getColumnList()) { - if (StringUtils.isBlank(column.getName()) || StringUtils.isBlank(column.getColumnType())) { + String columnType = column.getDataType(); + if (StringUtils.isBlank(columnType)) { + columnType = column.getColumnType(); + } + if (StringUtils.isBlank(column.getName()) || StringUtils.isBlank(columnType)) { continue; } - SqlServerColumnTypeEnum typeEnum = SqlServerColumnTypeEnum.getByType(column.getColumnType()); + SqlServerColumnTypeEnum typeEnum = SqlServerColumnTypeEnum.getByType(columnType); script.append("\t").append(typeEnum.buildCreateColumnSql(column)).append(",\n"); } @@ -38,7 +45,11 @@ public String buildCreateTableSql(Table table) { } for (TableColumn column : table.getColumnList()) { - if (StringUtils.isBlank(column.getName()) || StringUtils.isBlank(column.getColumnType()) || StringUtils.isBlank(column.getComment())) { + String columnType = column.getDataType(); + if (StringUtils.isBlank(columnType)) { + columnType = column.getColumnType(); + } + if (StringUtils.isBlank(column.getName()) || StringUtils.isBlank(columnType) || StringUtils.isBlank(column.getComment())) { continue; } script.append("\n").append(buildColumnComment(column)); @@ -90,8 +101,12 @@ public String buildModifyTaleSql(Table oldTable, Table newTable) { // append modify column for (TableColumn tableColumn : newTable.getColumnList()) { + String columnType = tableColumn.getDataType(); + if (StringUtils.isBlank(columnType)) { + columnType = tableColumn.getColumnType(); + } if (StringUtils.isNotBlank(tableColumn.getEditStatus())) { - SqlServerColumnTypeEnum typeEnum = SqlServerColumnTypeEnum.getByType(tableColumn.getColumnType()); + SqlServerColumnTypeEnum typeEnum = SqlServerColumnTypeEnum.getByType(columnType); script.append(typeEnum.buildModifyColumn(tableColumn)).append("\n"); } } @@ -173,4 +188,57 @@ public String buildCreateSchemaSql(Schema schema) { } return sqlBuilder.toString(); } + + @Override + protected String buildImportUpsertSql(String tableName, List
headerList, List primaryKeyColumns, + MetaData metaSchema) { + // SQL Server: MERGE INTO target USING (VALUES (...)) AS s (cols) ON (pk match) WHEN MATCHED THEN UPDATE WHEN NOT MATCHED THEN INSERT + if (primaryKeyColumns == null || primaryKeyColumns.isEmpty()) { + return buildImportInsertSql(tableName, headerList, metaSchema); + } + StringBuilder sql = new StringBuilder("MERGE INTO "); + sql.append(tableName).append(" t USING (VALUES ("); + for (int i = 0; i < headerList.size(); i++) { + if (i > 0) sql.append(","); + sql.append("?"); + } + sql.append(")) AS s ("); + for (int i = 0; i < headerList.size(); i++) { + if (i > 0) sql.append(","); + sql.append(metaSchema.getMetaDataName(headerList.get(i).getName())); + } + sql.append(") ON ("); + for (int i = 0; i < primaryKeyColumns.size(); i++) { + if (i > 0) sql.append(" AND "); + String pk = metaSchema.getMetaDataName(primaryKeyColumns.get(i)); + sql.append("t.").append(pk).append("=s.").append(pk); + } + sql.append(") WHEN MATCHED THEN UPDATE SET "); + boolean first = true; + for (Header header : headerList) { + if (primaryKeyColumns.contains(header.getName())) { + continue; + } + if (!first) sql.append(","); + String quotedName = metaSchema.getMetaDataName(header.getName()); + sql.append("t.").append(quotedName).append("=s.").append(quotedName); + first = false; + } + sql.append(" WHEN NOT MATCHED THEN INSERT ("); + first = true; + for (Header header : headerList) { + if (!first) sql.append(","); + sql.append(metaSchema.getMetaDataName(header.getName())); + first = false; + } + sql.append(") VALUES ("); + first = true; + for (Header header : headerList) { + if (!first) sql.append(","); + sql.append("s.").append(metaSchema.getMetaDataName(header.getName())); + first = false; + } + sql.append(");"); + return sql.toString(); + } } diff --git a/chat2db-server/chat2db-plugins/pom.xml b/chat2db-server/chat2db-plugins/pom.xml index d058b04e7..af76b2622 100644 --- a/chat2db-server/chat2db-plugins/pom.xml +++ b/chat2db-server/chat2db-plugins/pom.xml @@ -25,11 +25,13 @@ chat2db-db2 chat2db-mariadb chat2db-dm + chat2db-dlc chat2db-mongodb chat2db-presto chat2db-hive chat2db-redis chat2db-kingbase + chat2db-phoenix - \ No newline at end of file + diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/constant/AiConfigKeys.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/constant/AiConfigKeys.java new file mode 100644 index 000000000..8186cc31a --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/constant/AiConfigKeys.java @@ -0,0 +1,29 @@ +package ai.chat2db.server.domain.api.constant; + +public final class AiConfigKeys { + + public static final String AI_SQL_SOURCE = "ai.sql.source"; + + public static final String AI_API_KEY = "ai.apiKey"; + public static final String AI_API_HOST = "ai.apiHost"; + public static final String AI_MODEL = "ai.model"; + public static final String AI_TEMPERATURE = "ai.temperature"; + public static final String AI_MAX_TOKENS = "ai.maxTokens"; + public static final String AI_TOP_P = "ai.topP"; + public static final String AI_TOP_K = "ai.topK"; + public static final String AI_STOP_SEQUENCES = "ai.stopSequences"; + public static final String AI_BETA_VERSION = "ai.betaVersion"; + public static final String AI_HTTP_PROXY_HOST = "ai.httpProxyHost"; + public static final String AI_HTTP_PROXY_PORT = "ai.httpProxyPort"; + public static final String AI_N = "ai.n"; + public static final String AI_STOP = "ai.stop"; + public static final String AI_PRESENCE_PENALTY = "ai.presencePenalty"; + public static final String AI_FREQUENCY_PENALTY = "ai.frequencyPenalty"; + public static final String AI_LOGIT_BIAS = "ai.logitBias"; + public static final String AI_USER = "ai.user"; + public static final String AI_ORGANIZATION_ID = "ai.organizationId"; + public static final String AI_PROJECT_ID = "ai.projectId"; + + private AiConfigKeys() { + } +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/AiSqlSourceEnum.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/AiSqlSourceEnum.java index c44052d50..08fd72a03 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/AiSqlSourceEnum.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/AiSqlSourceEnum.java @@ -12,55 +12,12 @@ */ @Getter public enum AiSqlSourceEnum implements BaseEnum { - /** - * OPENAI - */ - OPENAI( "OPENAI"), - /** - * RESTAI - */ - RESTAI("RESTAI"), + OPENAI("OPENAI"), - /** - * AZURE OPENAI - */ AZUREAI("AZURE OPENAI"), - /** - * CHAT2DB OPENAI - */ - CHAT2DBAI("CHAT2DB OPENAI"), - - /** - * CLAUDE AI - */ - CLAUDEAI("CLAUDE AI"), - - /** - * WNEXIN AI - */ - WENXINAI("WENXIN AI"), - - /** - * BAICHUAN AI - */ - BAICHUANAI("BAICHUAN AI"), - - /** - * ZHIPU AI - */ - ZHIPUAI("ZHIPU AI"), - - /** - * TONGYIQIANWEN AI - */ - TONGYIQIANWENAI("TONGYIQIANWEN AI"), - - /** - * FAST CHAT AI - */ - FASTCHATAI("FAST CHAT AI"), + ANTHROPIC("ANTHROPIC"), ; diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/ExportFileSuffix.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/ExportFileSuffix.java index 9f98f0cdb..d3383ca1d 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/ExportFileSuffix.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/ExportFileSuffix.java @@ -19,7 +19,9 @@ public enum ExportFileSuffix { //html HTML(".html"), //pdf - PDF(".pdf"); + PDF(".pdf"), + //sql + SQL(".sql"); private String suffix; diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/ExportTypeEnum.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/ExportTypeEnum.java index c00ac9bbf..d6df16097 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/ExportTypeEnum.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/ExportTypeEnum.java @@ -45,7 +45,12 @@ public enum ExportTypeEnum implements BaseEnum { /** * PDF */ - PDF("PDF"); + PDF("PDF"), + + /** + * SQL (DDL) + */ + SQL("SQL"); final String description; diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/TaskTypeEnum.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/TaskTypeEnum.java index 7d3b93419..b602085de 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/TaskTypeEnum.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/TaskTypeEnum.java @@ -21,4 +21,14 @@ public enum TaskTypeEnum { * upload table structure */ UPLOAD_TABLE_STRUCTURE, + + /** + * generate table data + */ + GENERATE_TABLE_DATA, + + /** + * transfer table data + */ + TRANSFER_TABLE_DATA, } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/AIConfig.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/AIConfig.java index 48bf0d612..e84954f1a 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/AIConfig.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/AIConfig.java @@ -5,52 +5,53 @@ import jakarta.validation.constraints.NotNull; import lombok.Data; -/** - * @author jipengfei - * @version : SystemConfigRequest.java - */ @Data public class AIConfig { - /** - * APIKEY - */ private String apiKey = ""; - /** - * SECRETKEY - */ private String secretKey = ""; - /** - * APIHOST - */ private String apiHost = ""; - /** - * api http proxy host - */ private String httpProxyHost = ""; - /** - * api http proxy port - */ private String httpProxyPort = ""; - /** - * @see AiSqlSourceEnum - */ @NotNull private String aiSqlSource = ""; - /** - * return data stream - * 非必填,默认值为TRUE - */ private Boolean stream = Boolean.TRUE; - /** - * deployed model, default gpt-3.5-turbo - */ private String model = ""; + + private String embeddingModel = ""; + + private String temperature = ""; + + private String maxTokens = ""; + + private String topP = ""; + + private String topK = ""; + + private String stopSequences = ""; + + private String betaVersion = ""; + + private String n = ""; + + private String stop = ""; + + private String presencePenalty = ""; + + private String frequencyPenalty = ""; + + private String logitBias = ""; + + private String user = ""; + + private String organizationId = ""; + + private String projectId = ""; } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/AiConversation.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/AiConversation.java new file mode 100644 index 000000000..cef01fb97 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/AiConversation.java @@ -0,0 +1,27 @@ +package ai.chat2db.server.domain.api.model; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * AI 会话 DTO + */ +@Data +public class AiConversation { + + private Long id; + private LocalDateTime gmtCreate; + private LocalDateTime gmtModified; + + private String conversationId; + private Long userId; + private String title; + private Long dataSourceId; + private String dataSourceName; + private String databaseName; + private String schemaName; + private Integer messageCount; + private String lastMessagePreview; + private String status; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/AiConversationDetail.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/AiConversationDetail.java new file mode 100644 index 000000000..6b4e5de0c --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/AiConversationDetail.java @@ -0,0 +1,15 @@ +package ai.chat2db.server.domain.api.model; + +import lombok.Data; + +import java.util.List; + +/** + * AI 会话详情(含完整消息) + */ +@Data +public class AiConversationDetail { + + private AiConversation conversation; + private List messages; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/AiMessage.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/AiMessage.java new file mode 100644 index 000000000..222e416cf --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/AiMessage.java @@ -0,0 +1,25 @@ +package ai.chat2db.server.domain.api.model; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * AI 消息 DTO + */ +@Data +public class AiMessage { + + private Long id; + private LocalDateTime gmtCreate; + + private String conversationId; + private Long userId; + private String messageId; + private String role; + private String content; + private String thinking; + private String promptType; + private String sqlExtracted; + private Integer sequenceNo; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/ForeignKeyInfo.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/ForeignKeyInfo.java new file mode 100644 index 000000000..0c248d485 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/ForeignKeyInfo.java @@ -0,0 +1,52 @@ +package ai.chat2db.server.domain.api.model; + +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 外键关系导出信息 + */ +@Data +@Accessors(chain = true) +public class ForeignKeyInfo { + /** + * 外键名称 + */ + private String name; + /** + * 子表(拥有外键的表) + */ + private String tableName; + /** + * 子表列 + */ + private String columnName; + /** + * 主表(被引用的表) + */ + private String referencedTable; + /** + * 主表列 + */ + private String referencedColumnName; + /** + * 删除规则 + */ + private String deleteRule; + /** + * 更新规则 + */ + private String updateRule; + /** + * 来源类型(REAL/VIRTUAL) + */ + private String sourceType; + /** + * 注释 + */ + private String comment; + /** + * 所属数据库名 + */ + private String databaseName; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Task.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Task.java index 52c2a1b3c..d575dd9fe 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Task.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Task.java @@ -80,5 +80,7 @@ public class Task implements Serializable { /** * task content */ - private byte[] content; + private String content; + + private Integer totalCount; } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/TreeNode.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/TreeNode.java new file mode 100644 index 000000000..b24104d65 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/TreeNode.java @@ -0,0 +1,36 @@ +package ai.chat2db.server.domain.api.model; + +import java.util.List; +import java.util.Map; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class TreeNode { + + private String uuid; + + private String key; + + private String name; + + private String treeNodeType; + + private String pretendNodeType; + + private String comment; + + private Boolean isLeaf; + + private Boolean pinned; + + private List parentPath; + + private Map extraParams; +} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/schemaDiff/ColumnDiff.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/schemaDiff/ColumnDiff.java new file mode 100644 index 000000000..efd0bb93e --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/schemaDiff/ColumnDiff.java @@ -0,0 +1,21 @@ +package ai.chat2db.server.domain.api.model.schemaDiff; + +import java.util.List; + +import ai.chat2db.spi.enums.EditStatus; +import ai.chat2db.spi.model.TableColumn; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class ColumnDiff { + private EditStatus changeType; + private TableColumn sourceColumn; + private TableColumn targetColumn; + private List changedFields; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/schemaDiff/DiffSummary.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/schemaDiff/DiffSummary.java new file mode 100644 index 000000000..bfcf05281 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/schemaDiff/DiffSummary.java @@ -0,0 +1,19 @@ +package ai.chat2db.server.domain.api.model.schemaDiff; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class DiffSummary { + private int totalTables; + private int tablesOnlyInSource; + private int tablesOnlyInTarget; + private int modifiedTables; + private int unchangedTables; + private int excludedDeprecatedTables; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/schemaDiff/FieldDiff.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/schemaDiff/FieldDiff.java new file mode 100644 index 000000000..0db59fab6 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/schemaDiff/FieldDiff.java @@ -0,0 +1,16 @@ +package ai.chat2db.server.domain.api.model.schemaDiff; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class FieldDiff { + private String fieldName; + private String sourceValue; + private String targetValue; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/schemaDiff/ForeignKeyDiff.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/schemaDiff/ForeignKeyDiff.java new file mode 100644 index 000000000..245838d9c --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/schemaDiff/ForeignKeyDiff.java @@ -0,0 +1,21 @@ +package ai.chat2db.server.domain.api.model.schemaDiff; + +import java.util.List; + +import ai.chat2db.spi.enums.EditStatus; +import ai.chat2db.spi.model.ForeignKey; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class ForeignKeyDiff { + private EditStatus changeType; + private ForeignKey sourceForeignKey; + private ForeignKey targetForeignKey; + private List changedFields; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/schemaDiff/IndexDiff.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/schemaDiff/IndexDiff.java new file mode 100644 index 000000000..0d667864e --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/schemaDiff/IndexDiff.java @@ -0,0 +1,21 @@ +package ai.chat2db.server.domain.api.model.schemaDiff; + +import java.util.List; + +import ai.chat2db.spi.enums.EditStatus; +import ai.chat2db.spi.model.TableIndex; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class IndexDiff { + private EditStatus changeType; + private TableIndex sourceIndex; + private TableIndex targetIndex; + private List changedFields; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/schemaDiff/SchemaDiffResult.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/schemaDiff/SchemaDiffResult.java new file mode 100644 index 000000000..91115af93 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/schemaDiff/SchemaDiffResult.java @@ -0,0 +1,20 @@ +package ai.chat2db.server.domain.api.model.schemaDiff; + +import java.util.List; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class SchemaDiffResult { + private String sourceKey; + private String targetKey; + private DiffSummary summary; + private List tableDiffs; + private List warnings; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/schemaDiff/TableDiff.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/schemaDiff/TableDiff.java new file mode 100644 index 000000000..0f91b6cd1 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/schemaDiff/TableDiff.java @@ -0,0 +1,26 @@ +package ai.chat2db.server.domain.api.model.schemaDiff; + +import java.util.List; + +import ai.chat2db.spi.model.Table; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class TableDiff { + private String tableName; + private TableDiffType diffType; + private Table sourceTable; + private Table targetTable; + private List columnDiffs; + private List indexDiffs; + private List foreignKeyDiffs; + private List tableOptionDiffs; + private List ddlStatements; + private String ddlStatement; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/schemaDiff/TableDiffType.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/schemaDiff/TableDiffType.java new file mode 100644 index 000000000..f2c8d4586 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/schemaDiff/TableDiffType.java @@ -0,0 +1,8 @@ +package ai.chat2db.server.domain.api.model.schemaDiff; + +public enum TableDiffType { + ADDED, + REMOVED, + MODIFIED, + UNCHANGED +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/ColumnConfigParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/ColumnConfigParam.java new file mode 100644 index 000000000..fc7de9ebc --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/ColumnConfigParam.java @@ -0,0 +1,31 @@ +package ai.chat2db.server.domain.api.param; + +import lombok.Data; + +@Data +public class ColumnConfigParam { + + private String columnName; + + private String dataType; + + private String expression; + + private String comment; + + private Boolean nullable; + + private Boolean autoIncrement; + + private Integer maxLength; + + private Integer scale; + + private Boolean foreignKey; + + private String foreignKeySourceType; + + private String referencedTable; + + private String referencedColumnName; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/CreateVirtualFKParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/CreateVirtualFKParam.java new file mode 100644 index 000000000..25181310f --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/CreateVirtualFKParam.java @@ -0,0 +1,43 @@ +package ai.chat2db.server.domain.api.param; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class CreateVirtualFKParam { + + @NotNull + private Long dataSourceId; + + private String databaseName; + + private String schemaName; + + @NotBlank + private String tableName; + + @NotBlank + private String columnName; + + @NotBlank + private String referencedTable; + + @NotBlank + private String referencedColumnName; + + private String comment; + + /** + * 来源类型: MANUAL (手动创建) | INFERRED (推断生成) + */ + @Builder.Default + private String sourceType = "MANUAL"; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DataGenerationRequest.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DataGenerationRequest.java new file mode 100644 index 000000000..a205d98b1 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DataGenerationRequest.java @@ -0,0 +1,25 @@ +package ai.chat2db.server.domain.api.param; + +import lombok.Data; + +import java.util.List; + +@Data +public class DataGenerationRequest { + + private Long dataSourceId; + + private String databaseName; + + private String schemaName; + + private String tableName; + + private Integer rowCount; + + private List columnConfigs; + + private Integer batchSize = 1000; + + private Boolean previewMode = false; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DeleteFKParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DeleteFKParam.java new file mode 100644 index 000000000..a48fc81dc --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DeleteFKParam.java @@ -0,0 +1,21 @@ +package ai.chat2db.server.domain.api.param; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class DeleteFKParam { + + @NotNull + private Long id; + + @NotBlank + private String sourceType; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DeprecatedTableParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DeprecatedTableParam.java new file mode 100644 index 000000000..979ee8c68 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DeprecatedTableParam.java @@ -0,0 +1,31 @@ +package ai.chat2db.server.domain.api.param; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class DeprecatedTableParam { + + @NotNull + private Long dataSourceId; + + /** + * DB名称 + */ + private String databaseName; + + /** + * 表所在空间 + */ + private String schemaName; + + /** + * tableName + */ + private String tableName; + + /** + * deprecated userId + */ + private Long userId; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DlExecuteParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DlExecuteParam.java index cae98b5ce..604315c78 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DlExecuteParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DlExecuteParam.java @@ -18,6 +18,11 @@ public class DlExecuteParam { @NotNull private String sql; + /** + * 原始编辑器中执行脚本的起始行(从1开始) + */ + private Integer scriptStartLine; + /** * 控制台id */ diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DropKeyParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DropKeyParam.java new file mode 100644 index 000000000..13de370a3 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DropKeyParam.java @@ -0,0 +1,49 @@ +package ai.chat2db.server.domain.api.param; + +import ai.chat2db.spi.model.BaseModel; +import ai.chat2db.spi.model.IndexModel; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * 删除表结构 + * + * @author Jiaju Zhuang + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class DropKeyParam implements BaseModel{ + /** + * 对应数据库存储的来源id + */ + @NotNull + private Long dataSourceId; + + /** + * 对应的连接数据库名称 + */ + @NotNull + private String databaseName; + + /** + * 表名 + */ + private String tableName; + + /** + * 模式名 + */ + private String schemaName; + + /** + * 删除key名 + */ + private String keyName; + + private Class classType; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DropParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DropParam.java index 74fe89675..7b369a1d4 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DropParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DropParam.java @@ -1,7 +1,6 @@ package ai.chat2db.server.domain.api.param; import jakarta.validation.constraints.NotNull; - import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -35,7 +34,7 @@ public class DropParam { private String tableName; /** - * + * 模式名 */ - private String tableSchema; + private String schemaName; } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/ErDiagramQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/ErDiagramQueryParam.java new file mode 100644 index 000000000..ab4aa32c0 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/ErDiagramQueryParam.java @@ -0,0 +1,61 @@ +package ai.chat2db.server.domain.api.param; + +import java.io.Serial; + +import jakarta.validation.constraints.NotNull; + +import ai.chat2db.server.tools.base.wrapper.param.QueryParam; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * ER图查询参数 + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class ErDiagramQueryParam extends QueryParam { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 数据源ID + */ + @NotNull + private Long dataSourceId; + + /** + * 数据库名 + */ + @NotNull + private String databaseName; + + /** + * Schema名(Oracle/PostgreSQL等需要) + */ + private String schemaName; + + /** + * 表名过滤条件,支持模糊匹配 + */ + private String tableNameFilter; + + /** + * 是否包含虚拟外键,默认为true + */ + private Boolean includeVirtualFk; + + /** + * 是否同步数据库真实外键到本地,默认为false + */ + private Boolean syncForeignKeys; + + /** + * 是否只返回有外键关系的表,默认为false + */ + private Boolean onlyRelatedTables; +} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/GeneratorTemplate.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/GeneratorTemplate.java new file mode 100644 index 000000000..acee876f0 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/GeneratorTemplate.java @@ -0,0 +1,84 @@ +package ai.chat2db.server.domain.api.param; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class GeneratorTemplate implements Serializable { + + private static final long serialVersionUID = 1L; + + private String label; + + private String category; + + private String expression; + + private String example; + + private String suggestedDataType; + + public static GeneratorTemplate of(String label, String category, String expression, String example, String suggestedDataType) { + return new GeneratorTemplate(label, category, expression, example, suggestedDataType); + } + + public static List getDefaultTemplates() { + return List.of( + GeneratorTemplate.of("UUID", "基础", "#{Internet.uuid}", "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "VARCHAR"), + GeneratorTemplate.of("布尔值", "基础", "#{Bool.bool}", "true", "BOOLEAN"), + GeneratorTemplate.of("随机选项", "基础", "#{Options.option '启用','禁用','待定'}", "启用", "VARCHAR"), + GeneratorTemplate.of("MD5", "基础", "#{Hashing.md5}", "5d41402abc4b2a76b9719d911017c592", "VARCHAR"), + + GeneratorTemplate.of("姓", "姓名", "#{Name.last_name}", "Smith", "VARCHAR"), + GeneratorTemplate.of("名", "姓名", "#{Name.first_name}", "John", "VARCHAR"), + GeneratorTemplate.of("全名", "姓名", "#{Name.full_name}", "John Smith", "VARCHAR"), + + GeneratorTemplate.of("邮箱", "联系方式", "#{Internet.email_address}", "john@example.com", "VARCHAR"), + GeneratorTemplate.of("手机号", "联系方式", "#{PhoneNumber.cell_phone}", "555-123-4567", "VARCHAR"), + GeneratorTemplate.of("电话号码", "联系方式", "#{PhoneNumber.phone_number}", "555-123-4567", "VARCHAR"), + GeneratorTemplate.of("用户名", "联系方式", "#{Internet.username}", "john123", "VARCHAR"), + GeneratorTemplate.of("URL", "联系方式", "#{Internet.url}", "https://example.com", "VARCHAR"), + GeneratorTemplate.of("IP 地址", "联系方式", "#{Internet.ip_v4_address}", "192.168.1.1", "VARCHAR"), + GeneratorTemplate.of("MAC 地址", "联系方式", "#{Internet.mac_address}", "00:1A:2B:3C:4D:5E", "VARCHAR"), + + GeneratorTemplate.of("公司名", "商业", "#{Company.name}", "Acme Corp", "VARCHAR"), + GeneratorTemplate.of("职位", "商业", "#{Job.title}", "Software Engineer", "VARCHAR"), + GeneratorTemplate.of("部门", "商业", "#{Commerce.department}", "Electronics", "VARCHAR"), + GeneratorTemplate.of("产品名", "商业", "#{Commerce.product_name}", "Ergonomic Chair", "VARCHAR"), + GeneratorTemplate.of("价格", "商业", "#{Commerce.price}", "499.99", "DECIMAL"), + + GeneratorTemplate.of("国家", "地址", "#{Address.country}", "United States", "VARCHAR"), + GeneratorTemplate.of("城市", "地址", "#{Address.city}", "New York", "VARCHAR"), + GeneratorTemplate.of("省份", "地址", "#{Address.state}", "California", "VARCHAR"), + GeneratorTemplate.of("街道", "地址", "#{Address.street_address}", "123 Main St", "VARCHAR"), + GeneratorTemplate.of("邮编", "地址", "#{Address.zip_code}", "10001", "VARCHAR"), + GeneratorTemplate.of("完整地址", "地址", "#{Address.full_address}", "123 Main St, New York, NY 10001", "VARCHAR"), + + GeneratorTemplate.of("车架号", "车辆", "#{Vehicle.vin}", "ED9APPB07SGK81911", "VARCHAR"), + GeneratorTemplate.of("驱动类型", "车辆", "#{Vehicle.drive_type}", "4x2/2-wheel drive", "VARCHAR"), + GeneratorTemplate.of("车牌号", "车辆", "#{Vehicle.license_plate}", "abc-1234", "VARCHAR"), + + GeneratorTemplate.of("生日", "日期时间", "#{Date.birthday}", "1990-01-15", "DATE"), + GeneratorTemplate.of("过去时间", "日期时间", "#{Date.past '30','DAYS'}", "2024-01-10 14:30:00", "DATETIME"), + GeneratorTemplate.of("未来时间", "日期时间", "#{Date.future '30','DAYS'}", "2024-02-20 09:15:00", "DATETIME"), + + GeneratorTemplate.of("单词", "文本", "#{Lorem.word}", "lorem", "VARCHAR"), + GeneratorTemplate.of("句子", "文本", "#{Lorem.sentence}", "Lorem ipsum dolor sit amet.", "VARCHAR"), + GeneratorTemplate.of("段落", "文本", "#{Lorem.paragraph}", "Lorem ipsum dolor...", "TEXT"), + + GeneratorTemplate.of("整数 0-100", "数值", "#{Number.number_between '0','100'}", "42", "INT"), + GeneratorTemplate.of("整数 0-1000", "数值", "#{Number.number_between '0','1000'}", "756", "INT"), + GeneratorTemplate.of("整数 0-10000", "数值", "#{Number.number_between '0','10000'}", "5432", "INT"), + GeneratorTemplate.of("小数 0-9999", "数值", "#{Number.random_double '2','0','9999'}", "499.99", "DECIMAL"), + + GeneratorTemplate.of("颜色", "其他", "#{Color.name}", "black", "VARCHAR"), + GeneratorTemplate.of("EAN13", "其他", "#{Code.ean13}", "5385086204357", "VARCHAR") + ); + } +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TablePageQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TablePageQueryParam.java index d93c9beff..42796911c 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TablePageQueryParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TablePageQueryParam.java @@ -1,6 +1,10 @@ package ai.chat2db.server.domain.api.param; import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; +import ai.chat2db.spi.model.BaseModel; +import ai.chat2db.spi.model.LuceneField; +import ai.chat2db.spi.model.LuceneFieldType; +import ai.chat2db.spi.model.Table; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; @@ -16,29 +20,33 @@ @SuperBuilder @NoArgsConstructor @AllArgsConstructor -public class TablePageQueryParam extends PageQueryParam { +public class TablePageQueryParam extends PageQueryParam implements BaseModel
{ private static final long serialVersionUID = 8054519332890887747L; /** * 对应数据库存储的来源id */ @NotNull + @LuceneField(name = "dataSourceId", type = LuceneFieldType.STRING) private Long dataSourceId; /** * 对应的连接数据库名称 */ @NotNull + @LuceneField(name = "databaseName", type = LuceneFieldType.STRING) private String databaseName; /** * 表名 */ + @LuceneField(name = "tableName", type = LuceneFieldType.STRING) private String tableName; /** - * + * 模式名 */ + @LuceneField(name = "schemaName", type = LuceneFieldType.STRING) private String schemaName; @@ -50,4 +58,20 @@ public class TablePageQueryParam extends PageQueryParam { private String searchKey; + + /** + * 排序字段: name, rowCount + */ + private String sortField; + + /** + * 排序方向: ascend, descend + */ + private String sortOrder; + + @Override + public Class
getClassType() { + return Table.class; + } + } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TableQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TableQueryParam.java index 4f6e3b4cd..9aa567053 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TableQueryParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TableQueryParam.java @@ -2,10 +2,12 @@ import java.io.Serial; +import ai.chat2db.spi.model.*; +import com.jayway.jsonpath.internal.function.sequence.Index; + import jakarta.validation.constraints.NotNull; import ai.chat2db.server.tools.base.wrapper.param.QueryParam; - import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -20,30 +22,38 @@ @SuperBuilder @NoArgsConstructor @AllArgsConstructor -public class TableQueryParam extends QueryParam { +public class TableQueryParam extends QueryParam implements BaseModel{ @Serial private static final long serialVersionUID = -8918610899872508804L; /** * 对应数据库存储的来源id */ @NotNull + @LuceneField(name = "dataSourceId", type = LuceneFieldType.STRING) private Long dataSourceId; /** * 对应的连接数据库名称 */ @NotNull + @LuceneField(name = "databaseName", type = LuceneFieldType.STRING) private String databaseName; /** * 表名 */ + @LuceneField(name = "tableName", type = LuceneFieldType.STRING) private String tableName; /** - * 空间名 + * 模式名 */ + @LuceneField(name = "schemaName", type = LuceneFieldType.STRING) private String schemaName; private boolean refresh; + + + private Class classType; + } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TableSelector.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TableSelector.java index d91f5ff56..9cbcfb929 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TableSelector.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TableSelector.java @@ -24,5 +24,9 @@ public class TableSelector { * 索引列表 */ private Boolean indexList; + /** + * 外键 + */ + private Boolean foreignKey; } \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TaskCreateParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TaskCreateParam.java index 553beb34b..f4b92dad0 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TaskCreateParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TaskCreateParam.java @@ -50,5 +50,4 @@ public class TaskCreateParam implements Serializable { */ private String taskType; - } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TaskPageParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TaskPageParam.java index ecf207558..9ddf4ee1c 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TaskPageParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TaskPageParam.java @@ -9,6 +9,15 @@ @Data public class TaskPageParam extends PageQueryParam implements Serializable { + private Long dataSourceId; + + private String databaseName; + + private String schemaName; + + private String tableName; + + private String deleted; private Long userId; diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TaskUpdateParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TaskUpdateParam.java index 9465bc196..f58ea6c45 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TaskUpdateParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TaskUpdateParam.java @@ -25,14 +25,8 @@ public class TaskUpdateParam implements Serializable { */ private String taskStatus; - /** - * task progress - */ private String taskProgress; - /** - * task name - */ private String taskName; /** @@ -43,5 +37,5 @@ public class TaskUpdateParam implements Serializable { /** * task content */ - private byte[] content; + private String content; } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TreeSearchParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TreeSearchParam.java new file mode 100644 index 000000000..6fbb5399c --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TreeSearchParam.java @@ -0,0 +1,29 @@ +package ai.chat2db.server.domain.api.param; + +import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class TreeSearchParam extends PageQueryParam { + private static final long serialVersionUID = 1L; + + @NotNull + private Long dataSourceId; + + private String databaseName; + + private String schemaName; + + private String searchKey; + + private String treeNodeType; + + private boolean refresh; +} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/UpdateVirtualFKParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/UpdateVirtualFKParam.java new file mode 100644 index 000000000..2806c5278 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/UpdateVirtualFKParam.java @@ -0,0 +1,36 @@ +package ai.chat2db.server.domain.api.param; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class UpdateVirtualFKParam { + + @NotNull + private Long id; + + private Long dataSourceId; + + private String databaseName; + + private String schemaName; + + private String comment; + + private String tableName; + + private String columnName; + + private String referencedTable; + + private String referencedColumnName; + + private String vkName; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/ai/AiConversationCreateParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/ai/AiConversationCreateParam.java new file mode 100644 index 000000000..02b177567 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/ai/AiConversationCreateParam.java @@ -0,0 +1,17 @@ +package ai.chat2db.server.domain.api.param.ai; + +import lombok.Data; + +/** + * 新建 AI 会话 + */ +@Data +public class AiConversationCreateParam { + + private String conversationId; + private Long dataSourceId; + private String databaseName; + private String schemaName; + private String title; + private Long userId; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/ai/AiConversationQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/ai/AiConversationQueryParam.java new file mode 100644 index 000000000..a7b353af4 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/ai/AiConversationQueryParam.java @@ -0,0 +1,16 @@ +package ai.chat2db.server.domain.api.param.ai; + +import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; +import lombok.Data; + +/** + * AI 会话分页查询 + */ +@Data +public class AiConversationQueryParam extends PageQueryParam { + + private String searchKey; + private Long dataSourceId; + private Long userId; + private String status; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/ai/AiMessageSaveParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/ai/AiMessageSaveParam.java new file mode 100644 index 000000000..caa0fa579 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/ai/AiMessageSaveParam.java @@ -0,0 +1,20 @@ +package ai.chat2db.server.domain.api.param.ai; + +import lombok.Data; + +/** + * 单条 AI 消息写入(流式完成后) + */ +@Data +public class AiMessageSaveParam { + + private String conversationId; + private Long userId; + private String messageId; + private String role; + private String content; + private String thinking; + private String promptType; + private String sqlExtracted; + private Integer sequenceNo; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/DataSourceSortUpdateParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/DataSourceSortUpdateParam.java new file mode 100644 index 000000000..d39dfa817 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/DataSourceSortUpdateParam.java @@ -0,0 +1,18 @@ +package ai.chat2db.server.domain.api.param.datasource; + +import java.util.List; +import lombok.Data; + +/** + * 数据源连接排序更新参数 + * + * @author chat2db + */ +@Data +public class DataSourceSortUpdateParam { + + /** + * 按目标顺序排列的数据源 ID + */ + private List idList; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/schemaDiff/CompareOption.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/schemaDiff/CompareOption.java new file mode 100644 index 000000000..efa4d86ac --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/schemaDiff/CompareOption.java @@ -0,0 +1,22 @@ +package ai.chat2db.server.domain.api.param.schemaDiff; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class CompareOption { + private boolean compareColumn = true; + private boolean compareIndex = true; + private boolean compareForeignKey = true; + private boolean compareTableOption = true; + private boolean caseSensitive = false; + private boolean excludeDeprecated = true; + private boolean ignoreCharsetAlias = true; + private boolean ignoreIntegerDisplayWidth = true; + private boolean ignoreAutoIncrement = true; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/schemaDiff/MigrateResult.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/schemaDiff/MigrateResult.java new file mode 100644 index 000000000..c3bb06e5e --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/schemaDiff/MigrateResult.java @@ -0,0 +1,20 @@ +package ai.chat2db.server.domain.api.param.schemaDiff; + +import java.util.List; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class MigrateResult { + private boolean success; + private List statementResults; + private int totalStatements; + private int successCount; + private int failCount; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/schemaDiff/MigrationStatementResult.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/schemaDiff/MigrationStatementResult.java new file mode 100644 index 000000000..b89d82e49 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/schemaDiff/MigrationStatementResult.java @@ -0,0 +1,18 @@ +package ai.chat2db.server.domain.api.param.schemaDiff; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class MigrationStatementResult { + private int sequence; + private String sql; + private boolean success; + private String errorMessage; + private Long duration; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/schemaDiff/SchemaCompareParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/schemaDiff/SchemaCompareParam.java new file mode 100644 index 000000000..bdeafc6b2 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/schemaDiff/SchemaCompareParam.java @@ -0,0 +1,23 @@ +package ai.chat2db.server.domain.api.param.schemaDiff; + +import java.util.List; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class SchemaCompareParam { + private Long sourceDataSourceId; + private String sourceDatabaseName; + private String sourceSchemaName; + private Long targetDataSourceId; + private String targetDatabaseName; + private String targetSchemaName; + private List tableNames; + private CompareOption compareOption; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/schemaDiff/SchemaMigrateParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/schemaDiff/SchemaMigrateParam.java new file mode 100644 index 000000000..609ad600f --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/schemaDiff/SchemaMigrateParam.java @@ -0,0 +1,21 @@ +package ai.chat2db.server.domain.api.param.schemaDiff; + +import java.util.List; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class SchemaMigrateParam { + private Long targetDataSourceId; + private String targetDatabaseName; + private String targetSchemaName; + private List ddlStatements; + private boolean executeInTransaction; + private boolean continueOnError; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/AiConversationService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/AiConversationService.java new file mode 100644 index 000000000..43c2eb120 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/AiConversationService.java @@ -0,0 +1,73 @@ +package ai.chat2db.server.domain.api.service; + +import ai.chat2db.server.domain.api.model.AiConversation; +import ai.chat2db.server.domain.api.model.AiConversationDetail; +import ai.chat2db.server.domain.api.model.AiMessage; +import ai.chat2db.server.domain.api.param.ai.AiConversationCreateParam; +import ai.chat2db.server.domain.api.param.ai.AiConversationQueryParam; +import ai.chat2db.server.domain.api.param.ai.AiMessageSaveParam; +import ai.chat2db.server.tools.base.wrapper.ServicePage; +import jakarta.validation.constraints.NotNull; + +import java.util.List; + +/** + * AI 会话服务 + */ +public interface AiConversationService { + + /** + * 新建一个空会话 + * + * @return conversationId + */ + String create(@NotNull AiConversationCreateParam param); + + /** + * 重命名 + */ + void updateTitle(@NotNull String conversationId, @NotNull String title); + + /** + * 软删 + */ + void deleteWithPermission(@NotNull String conversationId, @NotNull Long userId); + + /** + * 列表(分页,按用户隔离) + */ + ServicePage queryPage(@NotNull AiConversationQueryParam param); + + /** + * 详情(含消息) + */ + AiConversationDetail getDetail(@NotNull String conversationId, @NotNull Long userId); + + /** + * 单条查询 + */ + AiConversation findByConversationId(@NotNull String conversationId); + + /** + * 追加单条消息 + */ + void appendMessage(@NotNull AiMessageSaveParam param); + + /** + * 流式完成后,落库一对 user/assistant 消息并更新会话元数据 + */ + void appendMessageTurn(@NotNull String conversationId, + @NotNull Long userId, + @NotNull String userMessageId, + @NotNull String userContent, + @NotNull String assistantMessageId, + @NotNull String assistantContent, + @NotNull String assistantThinking, + @NotNull String promptType, + @NotNull String sqlExtracted); + + /** + * 读取最近 N 条消息(revision 模式用) + */ + List listRecentMessages(@NotNull String conversationId, int limit); +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ChartService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ChartService.java index c831cbc5a..9951ecad1 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ChartService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ChartService.java @@ -7,9 +7,6 @@ import ai.chat2db.server.domain.api.chart.ChartQueryParam; import ai.chat2db.server.domain.api.chart.ChartUpdateParam; import ai.chat2db.server.domain.api.model.Chart; -import ai.chat2db.server.tools.base.wrapper.result.ActionResult; -import ai.chat2db.server.tools.base.wrapper.result.DataResult; -import ai.chat2db.server.tools.base.wrapper.result.ListResult; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; @@ -25,7 +22,7 @@ public interface ChartService { * @param param * @return */ - DataResult createWithPermission(ChartCreateParam param); + Long createWithPermission(ChartCreateParam param); /** * 更新报表 @@ -33,7 +30,7 @@ public interface ChartService { * @param param * @return */ - ActionResult updateWithPermission(ChartUpdateParam param); + void updateWithPermission(ChartUpdateParam param); /** * 根据id查询 @@ -41,7 +38,7 @@ public interface ChartService { * @param id * @return */ - DataResult find(@NotNull Long id); + Chart find(@NotNull Long id); /** * 查询一条数据 @@ -49,7 +46,7 @@ public interface ChartService { * @param param * @return */ - DataResult queryExistent(@NotNull ChartQueryParam param); + Chart queryExistent(@NotNull ChartQueryParam param); /** * 查询一条数据 @@ -57,7 +54,7 @@ public interface ChartService { * @param id * @return */ - DataResult queryExistent(@NotNull Long id); + Chart queryExistent(@NotNull Long id); /** * 查询多条数据 @@ -65,15 +62,7 @@ public interface ChartService { * @param param * @return */ - ListResult listQuery(@NotNull ChartListQueryParam param); - - /** - * 通过ID查询图表列表 - * - * @param ids - * @return - */ - ListResult queryByIds(@NotEmpty List ids); + List listQuery(@NotNull ChartListQueryParam param); /** * 删除 @@ -81,6 +70,6 @@ public interface ChartService { * @param id * @return */ - ActionResult deleteWithPermission(@NotNull Long id); + void deleteWithPermission(@NotNull Long id); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ConfigService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ConfigService.java index acab8df0c..a6f0d61d0 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ConfigService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ConfigService.java @@ -5,8 +5,6 @@ import ai.chat2db.server.domain.api.model.Config; import ai.chat2db.server.domain.api.param.SystemConfigParam; -import ai.chat2db.server.tools.base.wrapper.result.ActionResult; -import ai.chat2db.server.tools.base.wrapper.result.DataResult; /** * @author jipengfei @@ -20,7 +18,7 @@ public interface ConfigService { * @param param * @return */ - ActionResult create(SystemConfigParam param); + void create(SystemConfigParam param); /** * 修改配置 @@ -28,14 +26,14 @@ public interface ConfigService { * @param param * @return */ - ActionResult update(SystemConfigParam param); + void update(SystemConfigParam param); /** * 插入或者更新 * @param param * @return */ - ActionResult createOrUpdate(SystemConfigParam param); + void createOrUpdate(SystemConfigParam param); /** * 根据code查询 @@ -43,7 +41,7 @@ public interface ConfigService { * @param code * @return */ - DataResult find(@NotNull String code); + Config find(@NotNull String code); /** * 删除 @@ -51,5 +49,5 @@ public interface ConfigService { * @param code * @return */ - ActionResult delete(@NotNull String code); + void delete(@NotNull String code); } \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ConsoleService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ConsoleService.java index e500841e6..a94201d17 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ConsoleService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ConsoleService.java @@ -2,7 +2,6 @@ import ai.chat2db.server.domain.api.param.ConsoleConnectParam; import ai.chat2db.server.domain.api.param.ConsoleCloseParam; -import ai.chat2db.server.tools.base.wrapper.result.ActionResult; /** * 数据源管理服务 @@ -19,7 +18,7 @@ public interface ConsoleService { * @param param * @return */ - ActionResult createConsole(ConsoleConnectParam param); + void createConsole(ConsoleConnectParam param); /** * 关闭连接 @@ -27,6 +26,6 @@ public interface ConsoleService { * @param param * @return */ - ActionResult closeConsole(ConsoleCloseParam param); + void closeConsole(ConsoleCloseParam param); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DashboardService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DashboardService.java index 645f9df60..290dcb732 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DashboardService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DashboardService.java @@ -5,9 +5,8 @@ import ai.chat2db.server.domain.api.param.dashboard.DashboardPageQueryParam; import ai.chat2db.server.domain.api.param.dashboard.DashboardQueryParam; import ai.chat2db.server.domain.api.param.dashboard.DashboardUpdateParam; -import ai.chat2db.server.tools.base.wrapper.result.ActionResult; -import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; +import ai.chat2db.server.tools.base.wrapper.ServicePage; import jakarta.validation.constraints.NotNull; /** @@ -23,7 +22,7 @@ public interface DashboardService { * @param param * @return */ - DataResult createWithPermission(DashboardCreateParam param); + Long createWithPermission(DashboardCreateParam param); /** * 更新报表 @@ -31,7 +30,7 @@ public interface DashboardService { * @param param * @return */ - ActionResult updateWithPermission(DashboardUpdateParam param); + void updateWithPermission(DashboardUpdateParam param); /** * 根据id查询 @@ -39,7 +38,7 @@ public interface DashboardService { * @param id * @return */ - DataResult find(@NotNull Long id); + Dashboard find(@NotNull Long id); /** * 查询一条数据 @@ -48,7 +47,7 @@ public interface DashboardService { * @param selector * @return */ - DataResult queryExistent(@NotNull DashboardQueryParam param); + Dashboard queryExistent(@NotNull DashboardQueryParam param); /** * 查询一条数据 @@ -56,7 +55,7 @@ public interface DashboardService { * @param id * @return */ - DataResult queryExistent(@NotNull Long id); + Dashboard queryExistent(@NotNull Long id); /** * 删除 @@ -64,7 +63,7 @@ public interface DashboardService { * @param id * @return */ - ActionResult deleteWithPermission(@NotNull Long id); + void deleteWithPermission(@NotNull Long id); /** * 查询报表列表 @@ -72,5 +71,5 @@ public interface DashboardService { * @param param * @return */ - PageResult queryPage(DashboardPageQueryParam param); + ServicePage queryPage(DashboardPageQueryParam param); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataGenerationRuleService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataGenerationRuleService.java new file mode 100644 index 000000000..301c2b372 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataGenerationRuleService.java @@ -0,0 +1,12 @@ +package ai.chat2db.server.domain.api.service; + +import ai.chat2db.server.domain.api.param.ColumnConfigParam; + +import java.util.List; + +public interface DataGenerationRuleService { + + List getColumnConfigs(Long dataSourceId, String databaseName, String schemaName, String tableName); + + void saveColumnConfigs(Long dataSourceId, String databaseName, String schemaName, String tableName, Long userId, List configs, Integer rowCount); +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataGenerationService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataGenerationService.java new file mode 100644 index 000000000..dc11e2d4d --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataGenerationService.java @@ -0,0 +1,19 @@ +package ai.chat2db.server.domain.api.service; + +import ai.chat2db.server.domain.api.param.DataGenerationRequest; +import ai.chat2db.server.domain.api.param.ColumnConfigParam; +import ai.chat2db.server.domain.api.param.GeneratorTemplate; +import ai.chat2db.server.domain.api.vo.DataGenerationPreviewVO; + +import java.util.List; + +public interface DataGenerationService { + + List getTableColumns(DataGenerationRequest request); + + DataGenerationPreviewVO generatePreview(DataGenerationRequest request); + + Long executeDataGeneration(DataGenerationRequest request); + + List getAllGeneratorTemplates(); +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataSourceAccessBusinessService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataSourceAccessBusinessService.java index 4d4acf880..b941a9b39 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataSourceAccessBusinessService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataSourceAccessBusinessService.java @@ -1,7 +1,6 @@ package ai.chat2db.server.domain.api.service; import ai.chat2db.server.domain.api.model.DataSource; -import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import jakarta.validation.constraints.NotNull; /** @@ -16,5 +15,5 @@ public interface DataSourceAccessBusinessService { * @param dataSource * @return */ - ActionResult checkPermission(@NotNull DataSource dataSource); + void checkPermission(@NotNull DataSource dataSource); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataSourceAccessService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataSourceAccessService.java index 4cf4e531c..1921de9c9 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataSourceAccessService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataSourceAccessService.java @@ -5,9 +5,8 @@ import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessCreatParam; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessPageQueryParam; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessSelector; -import ai.chat2db.server.tools.base.wrapper.result.ActionResult; -import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; +import ai.chat2db.server.tools.base.wrapper.ServicePage; import jakarta.validation.constraints.NotNull; /** @@ -24,7 +23,7 @@ public interface DataSourceAccessService { * @param selector * @return */ - PageResult pageQuery(DataSourceAccessPageQueryParam param, DataSourceAccessSelector selector); + ServicePage pageQuery(DataSourceAccessPageQueryParam param, DataSourceAccessSelector selector); /** * Paging Query Data @@ -33,7 +32,7 @@ public interface DataSourceAccessService { * @param selector * @return */ - PageResult comprehensivePageQuery(DataSourceAccessComprehensivePageQueryParam param, + ServicePage comprehensivePageQuery(DataSourceAccessComprehensivePageQueryParam param, DataSourceAccessSelector selector); @@ -43,12 +42,12 @@ PageResult comprehensivePageQuery(DataSourceAccessComprehensiv * @param param * @return */ - DataResult create(DataSourceAccessCreatParam param); + Long create(DataSourceAccessCreatParam param); /** * delete * * @param id * @return */ - ActionResult delete(@NotNull Long id); + void delete(@NotNull Long id); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataSourceService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataSourceService.java index e9f706315..b54781800 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataSourceService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataSourceService.java @@ -7,11 +7,10 @@ import ai.chat2db.server.domain.api.param.datasource.DataSourcePageQueryParam; import ai.chat2db.server.domain.api.param.datasource.DataSourcePreConnectParam; import ai.chat2db.server.domain.api.param.datasource.DataSourceSelector; +import ai.chat2db.server.domain.api.param.datasource.DataSourceSortUpdateParam; import ai.chat2db.server.domain.api.param.datasource.DataSourceUpdateParam; -import ai.chat2db.server.tools.base.wrapper.result.ActionResult; -import ai.chat2db.server.tools.base.wrapper.result.DataResult; -import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; +import ai.chat2db.server.tools.base.wrapper.ServicePage; import ai.chat2db.server.tools.common.exception.PermissionDeniedBusinessException; import ai.chat2db.spi.model.Database; import jakarta.validation.constraints.NotNull; @@ -31,7 +30,7 @@ public interface DataSourceService { * @param param * @return */ - DataResult createWithPermission(DataSourceCreateParam param); + Long createWithPermission(DataSourceCreateParam param); /** * 更新数据源连接 @@ -39,7 +38,7 @@ public interface DataSourceService { * @param param * @return */ - DataResult updateWithPermission(DataSourceUpdateParam param); + Long updateWithPermission(DataSourceUpdateParam param); /** * 删除数据源连接 @@ -47,7 +46,7 @@ public interface DataSourceService { * @param id * @return */ - ActionResult deleteWithPermission(@NotNull Long id); + void deleteWithPermission(@NotNull Long id); /** * 根据id查询数据源连接详情 @@ -55,7 +54,7 @@ public interface DataSourceService { * @param id * @return */ - DataResult queryById(@NotNull Long id); + DataSource queryById(@NotNull Long id); /** * 根据id查询数据源连接详情 @@ -64,7 +63,7 @@ public interface DataSourceService { * @return * @throws ai.chat2db.server.tools.common.exception.DataNotFoundException */ - DataResult queryExistent(@NotNull Long id, DataSourceSelector selector); + DataSource queryExistent(@NotNull Long id, DataSourceSelector selector); /** * 克隆连接 @@ -72,44 +71,43 @@ public interface DataSourceService { * @param id * @return */ - DataResult copyByIdWithPermission(@NotNull Long id); + Long copyByIdWithPermission(@NotNull Long id); /** - * 分页查询数据源列表 + * 更新当前用户的数据源连接排序 * * @param param - * @param selector - * @return */ - PageResult queryPage(DataSourcePageQueryParam param, DataSourceSelector selector); + void updateSortWithPermission(DataSourceSortUpdateParam param); /** * 分页查询数据源列表 - * Need to determine permissions * * @param param * @param selector * @return - * @throws PermissionDeniedBusinessException */ - PageResult queryPageWithPermission(DataSourcePageQueryParam param, DataSourceSelector selector); + ServicePage queryPage(DataSourcePageQueryParam param, DataSourceSelector selector); /** - * 通过ID列表查询数据源 + * 分页查询数据源列表 + * Need to determine permissions * - * @param ids + * @param param + * @param selector * @return - * @deprecated Use {@link #listQuery(List, DataSourceSelector)} + * @throws PermissionDeniedBusinessException */ - ListResult queryByIds(List ids); + ServicePage queryPageWithPermission(DataSourcePageQueryParam param, DataSourceSelector selector); /** - * 通过ID列表查询数据源 + * 通过 ID 列表查询数据源 * * @param idList + * @param selector * @return */ - ListResult listQuery(List idList, DataSourceSelector selector); + List listQuery(List idList, DataSourceSelector selector); /** * 数据源连接测试 @@ -117,7 +115,7 @@ public interface DataSourceService { * @param param * @return */ - ActionResult preConnect(DataSourcePreConnectParam param); + void preConnect(DataSourcePreConnectParam param); /** * 连接数据源 @@ -125,7 +123,7 @@ public interface DataSourceService { * @param id * @return */ - ListResult connect(Long id); + List connect(Long id); /** * 关闭数据源连接 @@ -133,6 +131,15 @@ public interface DataSourceService { * @param id * @return */ - ActionResult close(Long id); + void close(Long id); + + + /** + * 获取数据库类型 + * + * @param dataSourceId 数据源 ID + * @return 数据库类型 + */ + String queryDatabaseType(Long dataSourceId); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DatabaseService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DatabaseService.java index 81957a0ac..ded0e1a7d 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DatabaseService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DatabaseService.java @@ -1,12 +1,11 @@ package ai.chat2db.server.domain.api.service; +import java.util.List; + import ai.chat2db.server.domain.api.param.*; import ai.chat2db.server.domain.api.param.datasource.DatabaseCreateParam; import ai.chat2db.server.domain.api.param.datasource.DatabaseQueryAllParam; -import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.spi.model.*; -import ai.chat2db.server.tools.base.wrapper.result.ActionResult; -import ai.chat2db.server.tools.base.wrapper.result.ListResult; /** * 数据源管理服务 @@ -23,21 +22,21 @@ public interface DatabaseService { * @param param * @return */ - ListResult queryAll(DatabaseQueryAllParam param); + List queryAll(DatabaseQueryAllParam param); /** * 查询某个database下的schema * @param param * @return */ - ListResult querySchema(SchemaQueryParam param); + List querySchema(SchemaQueryParam param); /** * query Database and Schema * @param param * @return */ - DataResult queryDatabaseSchema(MetaDataQueryParam param); + MetaSchema queryDatabaseSchema(MetaDataQueryParam param); @@ -47,7 +46,7 @@ public interface DatabaseService { * @param param * @return */ - ActionResult deleteDatabase(DatabaseCreateParam param); + void deleteDatabase(DatabaseCreateParam param); /** * 创建database @@ -55,14 +54,14 @@ public interface DatabaseService { * @param param * @return */ - DataResult createDatabase(Database param); + Sql createDatabase(Database param); /** * 修改database * * @return */ - ActionResult modifyDatabase( DatabaseCreateParam param) ; + void modifyDatabase( DatabaseCreateParam param) ; /** * 删除schema @@ -70,7 +69,7 @@ public interface DatabaseService { * @param param * @return */ - ActionResult deleteSchema(SchemaOperationParam param) ; + void deleteSchema(SchemaOperationParam param) ; /** * 创建schema @@ -78,13 +77,58 @@ public interface DatabaseService { * @param schema * @return */ - DataResult createSchema(Schema schema); + Sql createSchema(Schema schema); /** - * 修改schema + * 修改 schema * * @param request * @return */ - ActionResult modifySchema( SchemaOperationParam request); + void modifySchema( SchemaOperationParam request); + + /** + * 查询单个表的 DDL + * + * @param dataSourceId 数据源 ID + * @param databaseName 数据库名称 + * @param schemaName Schema 名称 + * @param tableName 表名 + * @return 表的 DDL + */ + String queryTableDdl(Long dataSourceId, String databaseName, String schemaName, String tableName); + + /** + * 批量构建表列信息 + * + * @param dataSourceId 数据源 ID + * @param databaseName 数据库名称 + * @param schemaName Schema 名称 + * @param tableNames 表名列表 + * @return 表的 DDL 拼接结果 + */ + String buildTableColumn(Long dataSourceId, String databaseName, String schemaName, java.util.List tableNames); + + /** + * 查询数据库所有表信息(用于自动选表场景) + * + * @param dataSourceId 数据源 ID + * @param databaseName 数据库名称 + * @param schemaName Schema 名称 + * @return 表的元数据信息(包含注释、外键等) + */ + String queryDatabaseTables(Long dataSourceId, String databaseName, String schemaName); + + /** + * 查询 Redis Schema 信息 + * + * @param dataSourceId 数据源 ID + * @param databaseName 数据库名称 + * @param schemaName Schema 名称 + * @param tableNames key 名称列表 + * @return Redis key 列表 + */ + String queryRedisSchema(Long dataSourceId, String databaseName, String schemaName, java.util.List tableNames); + + } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DeprecatedTableService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DeprecatedTableService.java new file mode 100644 index 000000000..2b4f6000f --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DeprecatedTableService.java @@ -0,0 +1,32 @@ +package ai.chat2db.server.domain.api.service; + +import ai.chat2db.server.domain.api.param.DeprecatedTableParam; +import ai.chat2db.server.tools.base.wrapper.result.ActionResult; + +import java.util.List; + +public interface DeprecatedTableService { + + /** + * User deprecated table + * @param param + * @return + */ + ActionResult deprecatedTable(DeprecatedTableParam param); + + + /** + * Delete deprecated table + * @param param + * @return + */ + ActionResult deleteDeprecatedTable(DeprecatedTableParam param); + + + /** + * Query user deprecated tables + * @param param + * @return + */ + List queryDeprecatedTables(DeprecatedTableParam param); +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DlTemplateService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DlTemplateService.java index cfee4cd12..ce824d840 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DlTemplateService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DlTemplateService.java @@ -1,13 +1,13 @@ package ai.chat2db.server.domain.api.service; +import java.util.List; + import ai.chat2db.server.domain.api.param.DlCountParam; import ai.chat2db.server.domain.api.param.DlExecuteParam; import ai.chat2db.server.domain.api.param.OrderByParam; import ai.chat2db.server.domain.api.param.UpdateSelectResultParam; import ai.chat2db.spi.model.ExecuteResult; -import ai.chat2db.server.tools.base.wrapper.result.DataResult; -import ai.chat2db.server.tools.base.wrapper.result.ListResult; /** * 数据源管理服务 @@ -24,7 +24,7 @@ public interface DlTemplateService { * @param param * @return */ - ListResult execute(DlExecuteParam param); + List execute(DlExecuteParam param); /** @@ -33,7 +33,7 @@ public interface DlTemplateService { * @param param * @return */ - DataResult executeUpdate(DlExecuteParam param); + ExecuteResult executeUpdate(DlExecuteParam param); /** * 执行统计sql @@ -41,7 +41,7 @@ public interface DlTemplateService { * @param param * @return */ - DataResult count(DlCountParam param); + Long count(DlCountParam param); /** @@ -49,7 +49,7 @@ public interface DlTemplateService { * @param param * @return */ - DataResult updateSelectResult(UpdateSelectResultParam param); + String updateSelectResult(UpdateSelectResultParam param); /** @@ -57,6 +57,6 @@ public interface DlTemplateService { * @param param * @return */ - DataResult getOrderBySql(OrderByParam param); + String getOrderBySql(OrderByParam param); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/EnvironmentService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/EnvironmentService.java index 9e9eb75c6..bbd0a2814 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/EnvironmentService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/EnvironmentService.java @@ -4,8 +4,8 @@ import ai.chat2db.server.domain.api.model.Environment; import ai.chat2db.server.domain.api.param.EnvironmentPageQueryParam; -import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; +import ai.chat2db.server.tools.base.wrapper.ServicePage; /** * environment @@ -20,7 +20,7 @@ public interface EnvironmentService { * @param idList * @return */ - ListResult listQuery(List idList); + List listQuery(List idList); /** * Paging Query Data @@ -28,6 +28,6 @@ public interface EnvironmentService { * @param param * @return */ - PageResult pageQuery(EnvironmentPageQueryParam param); + ServicePage pageQuery(EnvironmentPageQueryParam param); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ErDiagramService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ErDiagramService.java new file mode 100644 index 000000000..4ba109510 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ErDiagramService.java @@ -0,0 +1,29 @@ +package ai.chat2db.server.domain.api.service; + +import ai.chat2db.server.domain.api.param.ErDiagramQueryParam; +import ai.chat2db.server.domain.api.vo.InferVirtualFkResultVO; +import ai.chat2db.spi.model.ErDiagram; + +/** + * ER图服务接口 + */ +public interface ErDiagramService { + + /** + * 查询ER图数据 + * 根据数据库中的表和外键关系构建ER图数据 + * + * @param param 查询参数,包含数据源、数据库、schema、过滤条件等 + * @return ER图数据,包含节点(表)和边(外键关系) + */ + ErDiagram queryErDiagram(ErDiagramQueryParam param); + + /** + * 推断并添加虚拟外键 + * 根据命名规范(如 user_id -> users.id)自动推断可能的虚拟外键关系 + * + * @param param 查询参数 + * @return 推断结果,包含新增和删除的虚拟外键列表 + */ + InferVirtualFkResultVO inferVirtualForeignKeys(ErDiagramQueryParam param); +} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ForeignKeySyncService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ForeignKeySyncService.java new file mode 100644 index 000000000..e4072b9c7 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ForeignKeySyncService.java @@ -0,0 +1,52 @@ +package ai.chat2db.server.domain.api.service; + +import ai.chat2db.server.domain.api.param.CreateVirtualFKParam; +import ai.chat2db.server.domain.api.param.UpdateVirtualFKParam; +import ai.chat2db.spi.model.ForeignKey; +import ai.chat2db.spi.model.Table; +import ai.chat2db.spi.model.VirtualForeignKey; + +import java.util.List; + +public interface ForeignKeySyncService { + + SyncResult syncForeignKeys(Long dataSourceId, String databaseName, String schemaName, String tableName); + + List listAllForeignKeys(Long dataSourceId, String databaseName, String schemaName, String tableName); + + VirtualForeignKey createVirtualFK(CreateVirtualFKParam param); + + List createInferredVirtualFKs(List params); + + VirtualForeignKey updateVirtualFK(UpdateVirtualFKParam param); + + void deleteVirtualFK(Long id); + + String deleteRealFK(Long id); + + List generateForeignKeyDDL(Table oldTable, Table newTable); + + List queryRealForeignKeys(Long dataSourceId, String databaseName, String schemaName, String tableName); + + List queryVirtualForeignKeys(Long dataSourceId, String databaseName, String schemaName, String tableName); + + List queryAllVirtualForeignKeys(Long dataSourceId, String databaseName, String schemaName); + + int cleanInvalidVirtualForeignKeys(Long dataSourceId, String databaseName, String schemaName, List existingTableNames); + + class SyncResult { + private int added; + private int deleted; + private int unchanged; + + public SyncResult(int added, int deleted, int unchanged) { + this.added = added; + this.deleted = deleted; + this.unchanged = unchanged; + } + + public int getAdded() { return added; } + public int getDeleted() { return deleted; } + public int getUnchanged() { return unchanged; } + } +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/FunctionService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/FunctionService.java index ccce55e9f..3950072ba 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/FunctionService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/FunctionService.java @@ -1,10 +1,12 @@ package ai.chat2db.server.domain.api.service; -import ai.chat2db.server.tools.base.wrapper.result.DataResult; -import ai.chat2db.server.tools.base.wrapper.result.ListResult; +import ai.chat2db.server.domain.api.model.TreeNode; +import ai.chat2db.server.domain.api.param.TreeSearchParam; import ai.chat2db.spi.model.Function; import jakarta.validation.constraints.NotEmpty; +import java.util.List; + /** * author jipengfei * date 2021/9/23 15:22 @@ -17,7 +19,19 @@ public interface FunctionService { * @param databaseName * @return */ - ListResult functions(@NotEmpty String databaseName, String schemaName); + List functions(@NotEmpty String databaseName, String schemaName); + + /** + * Querying all functions under a schema with Lucene cache. + * + * @param dataSourceId data source id + * @param databaseName database name + * @param schemaName schema name + * @param searchKey search keyword + * @param refresh if true, refresh the cache + * @return + */ + List functionsWithCache(Long dataSourceId, String databaseName, String schemaName, String searchKey, boolean refresh); /** * Querying function information. @@ -26,5 +40,13 @@ public interface FunctionService { * @param functionName * @return */ - DataResult detail(String databaseName, String schemaName, String functionName); + Function detail(String databaseName, String schemaName, String functionName); + + /** + * Search tree nodes for functions. + * + * @param param + * @return + */ + List searchTreeNodes(TreeSearchParam param); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/JdbcDriverService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/JdbcDriverService.java index 1881cdd0a..dc2171894 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/JdbcDriverService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/JdbcDriverService.java @@ -1,7 +1,5 @@ package ai.chat2db.server.domain.api.service; -import ai.chat2db.server.tools.base.wrapper.result.ActionResult; -import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.spi.config.DBConfig; public interface JdbcDriverService { @@ -12,7 +10,7 @@ public interface JdbcDriverService { * @param dbType * @return */ - DataResult getDrivers(String dbType); + DBConfig getDrivers(String dbType); /** * Upload the driver @@ -22,7 +20,7 @@ public interface JdbcDriverService { * @param jdbcDriver * @return */ - ActionResult upload(String dbType, String jdbcDriverClass, String jdbcDriver); + void upload(String dbType, String jdbcDriverClass, String jdbcDriver); /** * Upload the driver @@ -30,5 +28,5 @@ public interface JdbcDriverService { * @param dbType * @return */ - ActionResult download(String dbType); + void download(String dbType); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/LoginAttemptService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/LoginAttemptService.java new file mode 100644 index 000000000..e6f483889 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/LoginAttemptService.java @@ -0,0 +1,8 @@ +package ai.chat2db.server.domain.api.service; + +public interface LoginAttemptService { + void validateAttempt(String clientFingerprint); + void recordFailedAttempt(String clientFingerprint); + void clearAttempts(String clientFingerprint); + long getRemainingLockTime(String clientFingerprint); +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/OperationLogService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/OperationLogService.java index 4872413b9..47cc8fde3 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/OperationLogService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/OperationLogService.java @@ -3,8 +3,8 @@ import ai.chat2db.server.domain.api.param.operation.OperationLogPageQueryParam; import ai.chat2db.server.domain.api.model.OperationLog; import ai.chat2db.server.domain.api.param.operation.OperationLogCreateParam; -import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; +import ai.chat2db.server.tools.base.wrapper.ServicePage; /** * 用户执行ddl @@ -21,7 +21,7 @@ public interface OperationLogService { * @param param * @return */ - DataResult create(OperationLogCreateParam param); + Long create(OperationLogCreateParam param); /** * 查询用户执行的ddl记录 @@ -29,5 +29,5 @@ public interface OperationLogService { * @param param * @return */ - PageResult queryPage(OperationLogPageQueryParam param); + ServicePage queryPage(OperationLogPageQueryParam param); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/OperationService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/OperationService.java index 4715ec3f2..7ba00fb98 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/OperationService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/OperationService.java @@ -5,9 +5,8 @@ import ai.chat2db.server.domain.api.param.operation.OperationQueryParam; import ai.chat2db.server.domain.api.param.operation.OperationSavedParam; import ai.chat2db.server.domain.api.param.operation.OperationUpdateParam; -import ai.chat2db.server.tools.base.wrapper.result.ActionResult; -import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; +import ai.chat2db.server.tools.base.wrapper.ServicePage; import jakarta.validation.constraints.NotNull; /** @@ -25,7 +24,7 @@ public interface OperationService { * @param param * @return */ - DataResult createWithPermission(OperationSavedParam param); + Long createWithPermission(OperationSavedParam param); /** * 更新用户的ddl @@ -33,7 +32,7 @@ public interface OperationService { * @param param * @return */ - ActionResult updateWithPermission(OperationUpdateParam param); + void updateWithPermission(OperationUpdateParam param); /** * 根据id查询 @@ -41,7 +40,7 @@ public interface OperationService { * @param id * @return */ - DataResult find(@NotNull Long id); + Operation find(@NotNull Long id); /** * 根据id查询 @@ -49,21 +48,21 @@ public interface OperationService { * @param id * @return */ - DataResult queryExistent(@NotNull Long id); + Operation queryExistent(@NotNull Long id); /** * 查询一条数据 * * @param param * @return */ - DataResult queryExistent(@NotNull OperationQueryParam param); + Operation queryExistent(@NotNull OperationQueryParam param); /** * 删除 * * @param id * @return */ - ActionResult deleteWithPermission(@NotNull Long id); + void deleteWithPermission(@NotNull Long id); /** * 查询用户执行的ddl记录 @@ -71,5 +70,5 @@ public interface OperationService { * @param param * @return */ - PageResult queryPage(OperationPageQueryParam param); + ServicePage queryPage(OperationPageQueryParam param); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/PinService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/PinService.java index c9479837b..378d27675 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/PinService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/PinService.java @@ -2,7 +2,6 @@ import ai.chat2db.server.domain.api.param.PinTableParam; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; -import ai.chat2db.server.tools.base.wrapper.result.ListResult; import java.util.List; @@ -29,5 +28,5 @@ public interface PinService { * @param param * @return */ - ListResult queryPinTables(PinTableParam param); + List queryPinTables(PinTableParam param); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ProcedureService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ProcedureService.java index 3df6301bf..416a0584c 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ProcedureService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ProcedureService.java @@ -1,10 +1,12 @@ package ai.chat2db.server.domain.api.service; -import ai.chat2db.server.tools.base.wrapper.result.DataResult; -import ai.chat2db.server.tools.base.wrapper.result.ListResult; +import ai.chat2db.server.domain.api.model.TreeNode; +import ai.chat2db.server.domain.api.param.TreeSearchParam; import ai.chat2db.spi.model.Procedure; import jakarta.validation.constraints.NotEmpty; +import java.util.List; + public interface ProcedureService { /** @@ -13,7 +15,19 @@ public interface ProcedureService { * @param databaseName * @return */ - ListResult procedures(@NotEmpty String databaseName, String schemaName); + List procedures(@NotEmpty String databaseName, String schemaName); + + /** + * Querying all procedures under a schema with Lucene cache. + * + * @param dataSourceId data source id + * @param databaseName database name + * @param schemaName schema name + * @param searchKey search keyword + * @param refresh if true, refresh the cache + * @return + */ + List proceduresWithCache(Long dataSourceId, String databaseName, String schemaName, String searchKey, boolean refresh); /** * Querying procedure information. @@ -22,5 +36,13 @@ public interface ProcedureService { * @param procedureName * @return */ - DataResult detail(String databaseName, String schemaName, String procedureName); + Procedure detail(String databaseName, String schemaName, String procedureName); + + /** + * Search tree nodes for procedures. + * + * @param param + * @return + */ + List searchTreeNodes(TreeSearchParam param); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/SchemaDiffService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/SchemaDiffService.java new file mode 100644 index 000000000..736b161d4 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/SchemaDiffService.java @@ -0,0 +1,13 @@ +package ai.chat2db.server.domain.api.service; + +import ai.chat2db.server.domain.api.model.schemaDiff.SchemaDiffResult; +import ai.chat2db.server.domain.api.param.schemaDiff.SchemaCompareParam; +import ai.chat2db.server.domain.api.param.schemaDiff.SchemaMigrateParam; +import ai.chat2db.server.domain.api.param.schemaDiff.MigrateResult; + +public interface SchemaDiffService { + + SchemaDiffResult compare(SchemaCompareParam param); + + MigrateResult migrate(SchemaMigrateParam param); +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TableService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TableService.java index 93f245af2..bea612ac5 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TableService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TableService.java @@ -2,12 +2,23 @@ import java.util.List; -import ai.chat2db.server.domain.api.param.*; -import ai.chat2db.spi.model.*; -import ai.chat2db.server.tools.base.wrapper.result.ActionResult; -import ai.chat2db.server.tools.base.wrapper.result.DataResult; -import ai.chat2db.server.tools.base.wrapper.result.ListResult; -import ai.chat2db.server.tools.base.wrapper.result.PageResult; +import ai.chat2db.server.domain.api.model.TreeNode; +import ai.chat2db.server.domain.api.param.DeprecatedTableParam; +import ai.chat2db.server.domain.api.param.DropParam; +import ai.chat2db.server.domain.api.param.ShowCreateTableParam; +import ai.chat2db.server.domain.api.param.TablePageQueryParam; +import ai.chat2db.server.domain.api.param.TableQueryParam; +import ai.chat2db.server.domain.api.param.TableSelector; +import ai.chat2db.server.domain.api.param.TreeSearchParam; +import ai.chat2db.server.domain.api.param.TypeQueryParam; +import ai.chat2db.spi.model.ExecuteResult; +import ai.chat2db.spi.model.SimpleTable; +import ai.chat2db.spi.model.Sql; +import ai.chat2db.spi.model.Table; +import ai.chat2db.spi.model.TableColumn; +import ai.chat2db.spi.model.TableIndex; +import ai.chat2db.spi.model.TableMeta; +import ai.chat2db.spi.model.Type; /** * 数据源管理服务 @@ -24,7 +35,7 @@ public interface TableService { * @param param * @return */ - DataResult showCreateTable(ShowCreateTableParam param); + String showCreateTable(ShowCreateTableParam param); /** * 删除表 @@ -32,7 +43,16 @@ public interface TableService { * @param param * @return */ - ActionResult drop(DropParam param); + void drop(DropParam param); + + + /** + * 截断表 + * + * @param param + * @return + */ + void truncate(DropParam param); /** * 创建表结构的样例 @@ -40,7 +60,7 @@ public interface TableService { * @param dbType * @return */ - DataResult createTableExample(String dbType); + String createTableExample(String dbType); /** * 修改表结构的样例 @@ -48,7 +68,7 @@ public interface TableService { * @param dbType * @return */ - DataResult alterTableExample(String dbType); + String alterTableExample(String dbType); /** * 查询表信息 @@ -56,7 +76,7 @@ public interface TableService { * @param param * @return */ - DataResult
query(TableQueryParam param, TableSelector selector); + Table query(TableQueryParam param, TableSelector selector); /** * 构建sql @@ -65,7 +85,14 @@ public interface TableService { * @param newTable * @return */ - ListResult buildSql(Table oldTable, Table newTable); + List buildSql(Table oldTable, Table newTable); + /** + * 批量生成sql + * @param oldTables + * @param newTables + * @return + */ + List buildBatchSql(List
oldTables, List
newTables); /** * 分页查询表信息 @@ -73,7 +100,15 @@ public interface TableService { * @param param * @return */ - PageResult
pageQuery(TablePageQueryParam param, TableSelector selector); + List
pageQuery(TablePageQueryParam param, TableSelector selector); + + /** + * 分页查询已废弃的表信息(回收站) + * + * @param param + * @return + */ + List
pageQueryDeprecated(TablePageQueryParam param, TableSelector selector); /** @@ -81,7 +116,7 @@ public interface TableService { * @param param * @return */ - ListResult queryTables(TablePageQueryParam param); + List queryTables(TablePageQueryParam param); /** * 查询表包含的字段 @@ -114,19 +149,52 @@ public interface TableService { */ TableMeta queryTableMeta(TypeQueryParam param); + /** - * save table vector + * Search tree nodes for tables. * * @param param * @return */ - ActionResult saveTableVector(TableVectorParam param); + List searchTreeNodes(TreeSearchParam param); /** - * check if table vector saved status - * + * Deprecated table * @param param * @return */ - DataResult checkTableVector(TableVectorParam param); + void deprecatedTable(DeprecatedTableParam param); + + /** + * Delete deprecated table + * @param param + * @return + */ + void deleteDeprecatedTable(DeprecatedTableParam param); + + /** + * Query user deprecated tables + * @param param + * @return + */ + List queryDeprecatedTables(DeprecatedTableParam param); + + /** + * Batch optimize tables + * @param tableNames + * @param databaseName + * @param schemaName + * @return + */ + List batchOptimizeTables(List tableNames, String databaseName, String schemaName); + + /** + * Batch analyze tables + * @param tableNames + * @param databaseName + * @param schemaName + * @return + */ + List batchAnalyzeTables(List tableNames, String databaseName, String schemaName); + } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TaskService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TaskService.java index e8d44bd8e..3bb2ecb66 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TaskService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TaskService.java @@ -4,9 +4,8 @@ import ai.chat2db.server.domain.api.param.TaskCreateParam; import ai.chat2db.server.domain.api.param.TaskPageParam; import ai.chat2db.server.domain.api.param.TaskUpdateParam; -import ai.chat2db.server.tools.base.wrapper.result.ActionResult; -import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; +import ai.chat2db.server.tools.base.wrapper.ServicePage; public interface TaskService { @@ -16,7 +15,7 @@ public interface TaskService { * @param param task param * @return task id */ - DataResult create(TaskCreateParam param); + Long create(TaskCreateParam param); /** * update task status @@ -24,7 +23,7 @@ public interface TaskService { * @param param task param * @return action result */ - ActionResult updateStatus(TaskUpdateParam param); + void updateStatus(TaskUpdateParam param); /** @@ -33,7 +32,7 @@ public interface TaskService { * @param param task id * @return task */ - PageResult page(TaskPageParam param); + ServicePage page(TaskPageParam param); /** * get task @@ -41,5 +40,13 @@ public interface TaskService { * @param id task id * @return task */ - DataResult get(Long id); + Task get(Long id); + + /** + * clean finished and failed tasks for user + * + * @param userId user id + * @return cleaned task count + */ + int cleanupFinishedTasks(Long userId); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TeamService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TeamService.java index cfd558533..f7f111b1b 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TeamService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TeamService.java @@ -7,10 +7,8 @@ import ai.chat2db.server.domain.api.param.team.TeamPageQueryParam; import ai.chat2db.server.domain.api.param.team.TeamSelector; import ai.chat2db.server.domain.api.param.team.TeamUpdateParam; -import ai.chat2db.server.tools.base.wrapper.result.ActionResult; -import ai.chat2db.server.tools.base.wrapper.result.DataResult; -import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; +import ai.chat2db.server.tools.base.wrapper.ServicePage; import jakarta.validation.constraints.NotNull; /** @@ -27,7 +25,7 @@ public interface TeamService { * @param selector * @return */ - PageResult pageQuery(TeamPageQueryParam param, TeamSelector selector); + ServicePage pageQuery(TeamPageQueryParam param, TeamSelector selector); /** * List Query Data @@ -35,7 +33,7 @@ public interface TeamService { * @param idList * @return */ - ListResult listQuery(List idList); + List listQuery(List idList); /** * Create @@ -43,7 +41,7 @@ public interface TeamService { * @param param * @return */ - DataResult create(TeamCreateParam param); + Long create(TeamCreateParam param); /** * update @@ -51,7 +49,7 @@ public interface TeamService { * @param param * @return */ - DataResult update(TeamUpdateParam param); + Long update(TeamUpdateParam param); /** * delete @@ -59,6 +57,6 @@ public interface TeamService { * @param id * @return */ - ActionResult delete(@NotNull Long id); + void delete(@NotNull Long id); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TeamUserService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TeamUserService.java index cf3c2ffcc..55b9d0548 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TeamUserService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TeamUserService.java @@ -5,9 +5,8 @@ import ai.chat2db.server.domain.api.param.team.user.TeamUserCreatParam; import ai.chat2db.server.domain.api.param.team.user.TeamUserPageQueryParam; import ai.chat2db.server.domain.api.param.team.user.TeamUserSelector; -import ai.chat2db.server.tools.base.wrapper.result.ActionResult; -import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; +import ai.chat2db.server.tools.base.wrapper.ServicePage; import jakarta.validation.constraints.NotNull; /** @@ -24,7 +23,7 @@ public interface TeamUserService { * @param selector * @return */ - PageResult pageQuery(TeamUserPageQueryParam param, TeamUserSelector selector); + ServicePage pageQuery(TeamUserPageQueryParam param, TeamUserSelector selector); /** * Comprehensive Paging Query Data @@ -33,7 +32,7 @@ public interface TeamUserService { * @param selector * @return */ - PageResult comprehensivePageQuery(TeamUserComprehensivePageQueryParam param, TeamUserSelector selector); + ServicePage comprehensivePageQuery(TeamUserComprehensivePageQueryParam param, TeamUserSelector selector); /** * Create @@ -41,7 +40,7 @@ public interface TeamUserService { * @param param * @return */ - DataResult create(TeamUserCreatParam param); + Long create(TeamUserCreatParam param); /** * delete @@ -49,5 +48,5 @@ public interface TeamUserService { * @param id * @return */ - ActionResult delete(@NotNull Long id); + void delete(@NotNull Long id); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TriggerService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TriggerService.java index 6309b5b0b..0eb453e7a 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TriggerService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TriggerService.java @@ -1,10 +1,12 @@ package ai.chat2db.server.domain.api.service; -import ai.chat2db.server.tools.base.wrapper.result.DataResult; -import ai.chat2db.server.tools.base.wrapper.result.ListResult; +import ai.chat2db.server.domain.api.model.TreeNode; +import ai.chat2db.server.domain.api.param.TreeSearchParam; import ai.chat2db.spi.model.Trigger; import jakarta.validation.constraints.NotEmpty; +import java.util.List; + public interface TriggerService { /** @@ -13,7 +15,19 @@ public interface TriggerService { * @param databaseName * @return */ - ListResult triggers(@NotEmpty String databaseName, String schemaName); + List triggers(@NotEmpty String databaseName, String schemaName); + + /** + * Querying all triggers under a schema with Lucene cache. + * + * @param dataSourceId data source id + * @param databaseName database name + * @param schemaName schema name + * @param searchKey search keyword + * @param refresh if true, refresh the cache + * @return + */ + List triggersWithCache(Long dataSourceId, String databaseName, String schemaName, String searchKey, boolean refresh); /** * Querying trigger information. @@ -22,5 +36,13 @@ public interface TriggerService { * @param triggerName * @return */ - DataResult detail(String databaseName, String schemaName, String triggerName); + Trigger detail(String databaseName, String schemaName, String triggerName); + + /** + * Search tree nodes for triggers. + * + * @param param + * @return + */ + List searchTreeNodes(TreeSearchParam param); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/UserService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/UserService.java index 1c98ca4f2..17fe4eb39 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/UserService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/UserService.java @@ -4,13 +4,11 @@ import ai.chat2db.server.domain.api.model.User; import ai.chat2db.server.domain.api.param.user.UserCreateParam; -import ai.chat2db.server.domain.api.param.user.UserSelector; import ai.chat2db.server.domain.api.param.user.UserPageQueryParam; +import ai.chat2db.server.domain.api.param.user.UserSelector; import ai.chat2db.server.domain.api.param.user.UserUpdateParam; -import ai.chat2db.server.tools.base.wrapper.result.ActionResult; -import ai.chat2db.server.tools.base.wrapper.result.DataResult; -import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; +import ai.chat2db.server.tools.base.wrapper.ServicePage; /** * 用户服务 @@ -25,14 +23,14 @@ public interface UserService { * @param id * @return */ - DataResult query(Long id); + User query(Long id); /** * gen * @param userName * @return */ - DataResult query(String userName); + User query(String userName); /** * List Query Data @@ -40,7 +38,7 @@ public interface UserService { * @param idList * @return */ - ListResult listQuery(List idList); + List listQuery(List idList); /** * 查询用户信息 @@ -48,26 +46,26 @@ public interface UserService { * @param param * @return */ - PageResult pageQuery(UserPageQueryParam param, UserSelector selector); + ServicePage pageQuery(UserPageQueryParam param, UserSelector selector); /** * 更新用户信息 * @param user * @return */ - DataResult update(UserUpdateParam user); + Long update(UserUpdateParam user); /** * 删除用户 * @param id * @return */ - ActionResult delete(Long id); + void delete(Long id); /** * 创建一个用户 * @param user * @return */ - DataResult create(UserCreateParam user); + Long create(UserCreateParam user); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ViewService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ViewService.java index 82d297016..f9c783892 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ViewService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ViewService.java @@ -1,10 +1,12 @@ package ai.chat2db.server.domain.api.service; -import ai.chat2db.server.tools.base.wrapper.result.DataResult; -import ai.chat2db.server.tools.base.wrapper.result.ListResult; +import ai.chat2db.server.domain.api.model.TreeNode; +import ai.chat2db.server.domain.api.param.TreeSearchParam; import ai.chat2db.spi.model.Table; import jakarta.validation.constraints.NotEmpty; +import java.util.List; + /** * author jipengfei * date 2021/9/23 15:22 @@ -17,7 +19,19 @@ public interface ViewService { * @param databaseName * @return */ - ListResult
views(@NotEmpty String databaseName, String schemaName); + List
views(@NotEmpty String databaseName, String schemaName); + + /** + * Querying all views under a schema with Lucene cache. + * + * @param dataSourceId data source id + * @param databaseName database name + * @param schemaName schema name + * @param searchKey search keyword + * @param refresh if true, refresh the cache + * @return + */ + List
viewsWithCache(Long dataSourceId, String databaseName, String schemaName, String searchKey, boolean refresh); /** @@ -26,5 +40,13 @@ public interface ViewService { * @param databaseName * @return */ - DataResult
detail(@NotEmpty String databaseName, String schemaName,String tableName); + Table detail(@NotEmpty String databaseName, String schemaName,String tableName); + + /** + * Search tree nodes for views. + * + * @param param + * @return + */ + List searchTreeNodes(TreeSearchParam param); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/vo/DataGenerationPreviewVO.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/vo/DataGenerationPreviewVO.java new file mode 100644 index 000000000..f45d049fe --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/vo/DataGenerationPreviewVO.java @@ -0,0 +1,25 @@ +package ai.chat2db.server.domain.api.vo; + +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Data +public class DataGenerationPreviewVO { + + private String tableName; + + private List> previewData; + + private List columns; + + @Data + public static class ColumnInfo { + private String columnName; + + private String dataType; + + private String comment; + } +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/vo/InferVirtualFkResultVO.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/vo/InferVirtualFkResultVO.java new file mode 100644 index 000000000..854bbe45d --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/vo/InferVirtualFkResultVO.java @@ -0,0 +1,47 @@ +package ai.chat2db.server.domain.api.vo; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 虚拟外键推断结果VO + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class InferVirtualFkResultVO { + + /** + * 新增的虚拟外键数量 + */ + private int addedCount; + + /** + * 删除的虚拟外键数量 + */ + private int deletedCount; + + /** + * 新增的虚拟外键列表 + */ + private java.util.List added; + + /** + * 删除的虚拟外键列表 + */ + private java.util.List deleted; + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class VirtualFkItem { + private String tableName; + private String columnName; + private String referencedTable; + private String referencedColumnName; + } +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/pom.xml b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/pom.xml index ce19bc340..9b3a7b9ae 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/pom.xml +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/pom.xml @@ -11,7 +11,9 @@ 4.0.0 chat2db-server-domain-core - + + 9.10.0 + ai.chat2db @@ -58,6 +60,11 @@ + + ai.chat2db + chat2db-dlc + ${revision} + ai.chat2db chat2db-dm @@ -126,5 +133,38 @@ chat2db-sqlserver ${revision} + + ai.chat2db + chat2db-phoenix + ${revision} + + + org.apache.lucene + lucene-core + ${lucene.vision} + + + org.apache.lucene + lucene-analysis-common + ${lucene.vision} + + + org.apache.lucene + lucene-queryparser + ${lucene.vision} + + + com.github.magese + ik-analyzer + 7.4.0 + + + + + + net.datafaker + datafaker + 2.1.0 + diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/AnnotationBasedDocumentBuilder.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/AnnotationBasedDocumentBuilder.java new file mode 100644 index 000000000..14b12cca1 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/AnnotationBasedDocumentBuilder.java @@ -0,0 +1,260 @@ +package ai.chat2db.server.domain.core.cache; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.lucene.document.Document; +import org.apache.lucene.document.LongPoint; +import org.apache.lucene.document.NumericDocValuesField; +import org.apache.lucene.document.SortedDocValuesField; +import org.apache.lucene.document.StoredField; +import org.apache.lucene.document.StringField; +import org.apache.lucene.document.TextField; +import org.apache.lucene.util.BytesRef; +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.util.ReflectionUtils; + +import ai.chat2db.spi.model.LuceneField; +import ai.chat2db.spi.model.LuceneFieldType; + +/** + * 基于注解的 Lucene 文档构建器 + * 通过读取字段上的 @LuceneField 注解自动构建 Lucene Document + * 使用 MethodHandle + 缓存机制优化性能,支持 AOT 编译 + */ +public class AnnotationBasedDocumentBuilder { + + private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + + /** + * 带注解的字段信息 + * 使用 MethodHandle 替代 Field 提升访问性能,AOT 友好 + */ + static class AnnotatedFieldInfo { + final MethodHandle getter; + final LuceneField annotation; + + AnnotatedFieldInfo(Field field, LuceneField annotation) { + this.annotation = annotation; + try { + this.getter = LOOKUP.unreflectGetter(field); + } catch (IllegalAccessException e) { + throw new RuntimeException("Failed to create MethodHandle for field: " + field.getName(), e); + } + } + + Object getValue(Object source) { + try { + return getter.invoke(source); + } catch (Throwable e) { + throw new RuntimeException("Failed to get field value", e); + } + } + } + + /** + * 缓存类的字段信息,key 为类名,value 为带注解的字段列表 + */ + private final Map> fieldCache = new ConcurrentHashMap<>(); + + /** + * 缓存类的 STRING 类型字段(用于过滤),key 为类名,value 为 STRING 类型字段列表 + */ + private final Map> stringFieldCache = new ConcurrentHashMap<>(); + + /** + * 获取 STRING 类型的字段列表(用于构建查询过滤) + * STRING 类型字段自动用于精确匹配过滤 + */ + public List getStringFields(Class clazz) { + return stringFieldCache.computeIfAbsent(clazz.getName(), k -> collectStringFields(clazz)); + } + + /** + * 收集 STRING 类型的字段 + */ + private List collectStringFields(Class clazz) { + List stringFields = new ArrayList<>(); + collectStringFieldsRecursive(clazz, stringFields); + return stringFields; + } + + /** + * 递归收集 STRING 类型的字段 + */ + private void collectStringFieldsRecursive(Class clazz, List stringFields) { + if (clazz == null || clazz == Object.class) { + return; + } + + collectStringFieldsRecursive(clazz.getSuperclass(), stringFields); + + ReflectionUtils.doWithLocalFields(clazz, field -> { + LuceneField annotation = AnnotatedElementUtils.findMergedAnnotation(field, LuceneField.class); + if (annotation != null && annotation.type() == LuceneFieldType.STRING) { + ReflectionUtils.makeAccessible(field); + stringFields.add(new AnnotatedFieldInfo(field, annotation)); + } + }); + } + + /** + * 基于注解构建 Lucene 文档 + * + * @param source 源对象 + * @return Lucene 文档 + */ + public org.apache.lucene.document.Document buildDocument(Object source) { + if (source == null) { + return new org.apache.lucene.document.Document(); + } + + org.apache.lucene.document.Document doc = new org.apache.lucene.document.Document(); + Class clazz = source.getClass(); + + List annotatedFields = fieldCache.computeIfAbsent(clazz.getName(), k -> collectAnnotatedFields(clazz)); + + for (AnnotatedFieldInfo info : annotatedFields) { + Object value = info.getValue(source); + addFieldToDocument(doc, info.annotation, value); + } + + return doc; + } + + /** + * 收集类及其父类中所有带 @LuceneField 注解的字段 + */ + private List collectAnnotatedFields(Class clazz) { + List fields = new ArrayList<>(); + collectAnnotatedFieldsRecursive(clazz, fields); + return fields; + } + + /** + * 递归收集字段(从父类到子类) + */ + private void collectAnnotatedFieldsRecursive(Class clazz, List fields) { + if (clazz == null || clazz == Object.class) { + return; + } + + collectAnnotatedFieldsRecursive(clazz.getSuperclass(), fields); + + ReflectionUtils.doWithLocalFields(clazz, field -> { + LuceneField annotation = AnnotatedElementUtils.findMergedAnnotation(field, LuceneField.class); + if (annotation != null) { + ReflectionUtils.makeAccessible(field); + fields.add(new AnnotatedFieldInfo(field, annotation)); + } + }); + } + + /** + * 根据注解配置添加字段到文档 + */ + private void addFieldToDocument(org.apache.lucene.document.Document doc, LuceneField annotation, Object value) { + if (value == null) { + return; + } + + String fieldName = annotation.name(); + LuceneFieldType type = annotation.type(); + boolean sort = annotation.sort(); + boolean store = annotation.store(); + + switch (type) { + case TEXT: + addTextField(doc, fieldName, value.toString(), sort, store); + break; + case STRING: + addStringField(doc, fieldName, value.toString(), sort, store); + break; + case LONG: + addLongField(doc, fieldName, (Long) value, sort, store); + break; + case INTEGER: + addIntegerField(doc, fieldName, (Integer) value, sort, store); + break; + case DOUBLE: + addDoubleField(doc, fieldName, (Double) value, sort, store); + break; + default: + throw new IllegalArgumentException("Unknown field type: " + type); + } + } + + /** + * 添加文本字段 + */ + private void addTextField(org.apache.lucene.document.Document doc, String fieldName, String value, boolean sort, boolean store) { + doc.add(new TextField(fieldName, value, store ? org.apache.lucene.document.Field.Store.YES : org.apache.lucene.document.Field.Store.NO)); + + if (sort) { + doc.add(new SortedDocValuesField(fieldName, new BytesRef(value))); + } + } + + /** + * 添加字符串字段 + */ + private void addStringField(org.apache.lucene.document.Document doc, String fieldName, String value, boolean sort, boolean store) { + doc.add(new StringField(fieldName, value, store ? org.apache.lucene.document.Field.Store.YES : org.apache.lucene.document.Field.Store.NO)); + + if (sort) { + doc.add(new SortedDocValuesField(fieldName, new BytesRef(value))); + } + } + + /** + * 添加长整型字段 + */ + private void addLongField(org.apache.lucene.document.Document doc, String fieldName, Long value, boolean sort, boolean store) { + doc.add(new LongPoint(fieldName, value)); + + if (sort) { + doc.add(new NumericDocValuesField(fieldName, value)); + } + + if (store) { + doc.add(new StoredField(fieldName, value)); + } + } + + /** + * 添加整型字段 + */ + private void addIntegerField(org.apache.lucene.document.Document doc, String fieldName, Integer value, boolean sort, boolean store) { + Long longValue = value.longValue(); + doc.add(new LongPoint(fieldName, longValue)); + + if (sort) { + doc.add(new NumericDocValuesField(fieldName, longValue)); + } + + if (store) { + doc.add(new StoredField(fieldName, longValue)); + } + } + + /** + * 添加双精度浮点字段 + */ + private void addDoubleField(org.apache.lucene.document.Document doc, String fieldName, Double value, boolean sort, boolean store) { + doc.add(new org.apache.lucene.document.DoublePoint(fieldName, value)); + + if (sort) { + long encoded = Double.doubleToLongBits(value); + doc.add(new org.apache.lucene.document.SortedNumericDocValuesField(fieldName, encoded)); + } + + if (store) { + doc.add(new StoredField(fieldName, value)); + } + } +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/CacheKey.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/CacheKey.java index 1340c5c83..edb49625c 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/CacheKey.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/CacheKey.java @@ -22,25 +22,25 @@ public static String getSchemasKey(Long dataSourceId, String databaseName) { } public static String getTableKey(Long dataSourceId, String databaseName, String schemaName) { - StringBuffer stringBuffer = new StringBuffer("tables_dataSourceId_" + dataSourceId); + StringBuilder stringBuffer = new StringBuilder("tables_dataSourceId_" + dataSourceId); if (!StringUtils.isEmpty(databaseName)) { - stringBuffer.append("_databaseName_" + databaseName); + stringBuffer.append("_databaseName_").append(databaseName); } if (!StringUtils.isEmpty(schemaName)) { - stringBuffer.append("_schemaName_" + schemaName); + stringBuffer.append("_schemaName_").append(schemaName); } return stringBuffer.toString(); } - public static String getColumnKey(Long dataSourceId, String databaseName, String schemaName,String tableName) { + public static String getColumnKey(Long dataSourceId, String databaseName, String schemaName, String tableName) { StringBuffer stringBuffer = new StringBuffer("columns_dataSourceId_" + dataSourceId); if (!StringUtils.isEmpty(databaseName)) { - stringBuffer.append("_databaseName_" + databaseName); + stringBuffer.append("_databaseName_").append(databaseName); } if (!StringUtils.isEmpty(schemaName)) { - stringBuffer.append("_schemaName_" + schemaName); + stringBuffer.append("_schemaName_").append(schemaName); } - stringBuffer.append("_tableName_"+tableName); + stringBuffer.append("_tableName_" + tableName); return stringBuffer.toString(); } } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/CacheManage.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/CacheManage.java index 778706d20..608587ea4 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/CacheManage.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/CacheManage.java @@ -88,6 +88,11 @@ public static void put(String s, Object value) { myCache.put(s, JSON.toJSONString(value)); } + public static void remove(String key) { + Cache myCache = cacheManager.getCache(CACHE, String.class, String.class); + myCache.remove(key); + } + public static void close() { cacheManager.close(); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/LuceneFieldRuntimeHints.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/LuceneFieldRuntimeHints.java new file mode 100644 index 000000000..825969eda --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/LuceneFieldRuntimeHints.java @@ -0,0 +1,54 @@ +package ai.chat2db.server.domain.core.cache; + +import java.lang.reflect.Field; + +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.lang.NonNull; + +import ai.chat2db.spi.model.LuceneField; + +/** + * AOT RuntimeHints 注册器 + * 为 GraalVM 原生镜像编译注册反射元数据 + * 确保 @LuceneField 注解字段在 AOT 编译时可访问 + */ +public class LuceneFieldRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(@NonNull RuntimeHints hints, ClassLoader classLoader) { + hints.reflection() + .registerType(LuceneFieldHintProcessor.class, MemberCategory.INVOKE_DECLARED_METHODS); + } + + /** + * 提示处理器:在运行时动态注册带 @LuceneField 注解的类 + * 实际使用中,需要扫描所有带 @LuceneField 注解的实体类并注册 + */ + static class LuceneFieldHintProcessor { + + /** + * 注册带 @LuceneField 注解的类及其字段反射权限 + * 应在应用启动时调用此方法 + * + * @param hints RuntimeHints 实例 + * @param classes 需要注册的实体类 + */ + @SafeVarargs + public static void registerClasses(RuntimeHints hints, Class... classes) { + for (Class clazz : classes) { + hints.reflection().registerType(clazz, + MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, + MemberCategory.DECLARED_FIELDS, + MemberCategory.INVOKE_DECLARED_METHODS); + + for (Field field : clazz.getDeclaredFields()) { + if (field.isAnnotationPresent(LuceneField.class)) { + hints.reflection().registerField(field); + } + } + } + } + } +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/LuceneIndexManager.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/LuceneIndexManager.java new file mode 100644 index 000000000..0d15e7a5d --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/LuceneIndexManager.java @@ -0,0 +1,556 @@ +package ai.chat2db.server.domain.core.cache; + +import java.io.IOException; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collections; +import java.util.ConcurrentModificationException; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.stream.Collectors; + +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.lucene.analysis.Analyzer; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.Field; +import org.apache.lucene.document.NumericDocValuesField; +import org.apache.lucene.document.StoredField; +import org.apache.lucene.document.StringField; +import org.apache.lucene.index.DirectoryReader; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.IndexWriter; +import org.apache.lucene.index.IndexWriterConfig; +import org.apache.lucene.index.IndexableField; +import org.apache.lucene.index.StoredFields; +import org.apache.lucene.index.Term; +import org.apache.lucene.queryparser.classic.MultiFieldQueryParser; +import org.apache.lucene.queryparser.classic.QueryParser; +import org.apache.lucene.search.*; +import org.apache.lucene.store.Directory; +import org.apache.lucene.store.FSDirectory; + + +import com.alibaba.fastjson2.JSONObject; +import com.google.common.collect.Sets; + +import ai.chat2db.spi.model.BaseModel; +import ai.chat2db.spi.model.IndexModel; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.SneakyThrows; + +/** + * 类LuceneIndexManager用于管理Lucene全文索引的创建、更新和查询 + * 它实现了AutoCloseable接口,支持使用try-with-resources语句自动关闭资源 + */ +public class LuceneIndexManager implements AutoCloseable { + /** + * 索引、分析器、写入器、读者和搜索者实例变量 + */ + private Directory index; + private Analyzer analyzer; + private IndexWriter writer; + private IndexReader reader; + private IndexSearcher searcher; + + @Getter + private Integer lastDocId; + + + /** + * 读写锁 + */ + @Getter + private ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + + private static final String[] TEXT_FIELDS = {"name", "comment", "aiComment"}; + + /** + * 基于注解的文档构建器 + */ + private final AnnotationBasedDocumentBuilder documentBuilder = new AnnotationBasedDocumentBuilder(); + + /** + * 构造函数,根据给定的ID初始化Lucene索引管理器 + * + * @param id 用于确定索引文件路径的ID + */ + @SneakyThrows + public LuceneIndexManager(@NotNull Long id) { + String indexPath = getIndexPath(id); + this.index = FSDirectory.open(Paths.get(indexPath)); + this.analyzer = new MixedAnalyzer(); + IndexWriterConfig config = new IndexWriterConfig(analyzer); + this.writer = new IndexWriter(index, config); + this.reader = DirectoryReader.open(writer); + this.searcher = new IndexSearcher(reader); + } + + @SneakyThrows + public Long getMaxVersion(E queryModel) { + // 创建查询条件 + BooleanQuery query = buildBooleanQuery(queryModel).build(); + + // 创建按版本号降序排序的排序规则 + // true表示降序 + Sort sort = new Sort(new SortField("version", SortField.Type.LONG, true)); + + // 执行查询,按版本号降序排序,只取第一个文档 + TopDocs topDocs = searcher.search(query, 1, sort); + + if (topDocs.totalHits.value == 0) { + return null; + } + + // 获取匹配的最高版本号文档 + Document document = searcher.doc(topDocs.scoreDocs[0].doc, Collections.singleton("version")); + IndexableField versionField = document.getField("version"); + + return versionField != null ? (Long) versionField.numericValue() : null; + } + + /** + * 根据ID和环境获取索引的文件路径 + * + * @param id 用于确定索引文件路径的ID + * @return 索引的文件路径 + */ + private String getIndexPath(Long id) { + String environment = StringUtils.defaultString(System.getProperty("spring.profiles.active"), "dev"); + String basePath = System.getProperty("user.home") + "/.chat2db/index/"; + switch (environment.toLowerCase()) { + case "test": + return basePath + id + "_test"; + case "dev": + return basePath + id + "_dev"; + default: + return basePath + id; + } + } + + /** + * 释放资源,关闭索引读者、写入器和目录 + * + * @throws IOException + */ + @Override + public void close() throws IOException { + if (reader != null) { + reader.close(); + } + if (writer != null) { + writer.close(); + } + if (index != null) { + index.close(); + } + } + + /** + * 重建Reader和Searcher + */ + @SneakyThrows + private void reload() { + writer.commit(); + if (this.reader != null) { + this.reader.close(); + } + this.reader = DirectoryReader.open(writer); + this.searcher = new IndexSearcher(reader); + } + + /** + * 构建旧数据映射表 + * + * @param query 查询条件 + * @param maxHits 最大命中数 + * @return 以"name"为Key的旧数据映射表 + */ + @SneakyThrows + private Map buildSourceMap(BooleanQuery query, int maxHits) { + TopDocs topDocs = searcher.search(query, maxHits); + long total = topDocs.totalHits.value; + if (total == 0) { + return Collections.emptyMap(); + } + return Arrays.stream(topDocs.scoreDocs) + .map(scoreDoc -> getSource(scoreDoc.doc)) + .map(JSONObject::parseObject) + .collect(Collectors.toMap( + obj -> obj.getString("name"), + obj -> obj, + // 重复时保留最新值 + (oldVal, newVal) -> newVal + )); + } + + /** + * 批量更新文档到Lucene索引,version留空则初始化 + * + * @param sources + * @param version + */ + public void updateDocuments(List sources, Long version) { + this.updateDocuments(sources, version == null ? 1L : version, true); + } + + /** + * 批量更新文档到Lucene索引,version留空则忽略版本控制 + * 逻辑说明: + * 1. 参数校验 + * 2. 类型一致性检查 + * 3. 准备搜索环境 + * 4. 构建旧数据映射表 + * 5. 批量创建文档并更新 + */ + @SneakyThrows + public void updateDocuments(List sources, Long version, boolean all) { + if (CollectionUtils.isEmpty(sources)) { + return; + } + T model = sources.get(0); + lock.writeLock().lock(); + try { + // 获取全部相关旧数据 + String fld = StringUtils.uncapitalize(model.getClassType().getSimpleName() + "Name"); + BooleanQuery.Builder booleanQuery = buildBooleanQuery(model); + if (!all) { + BooleanQuery.Builder nameQuery = new BooleanQuery.Builder(); + sources.forEach(source -> nameQuery.add(new TermQuery(new Term(fld, source.getName())), BooleanClause.Occur.SHOULD)); + booleanQuery.add(nameQuery.build(), BooleanClause.Occur.MUST); + } + BooleanQuery query = booleanQuery.build(); + Map sourceMap = buildSourceMap(query, 1000); + List docs = sources.stream() + .peek(source -> source.setVersion(version)) + .map(source -> createDocument(source, sourceMap)) + .collect(Collectors.toList()); + + writer.updateDocuments(query, docs); + reload(); + } finally { + lock.writeLock().unlock(); + } + } + + /** + * 更新单个文档到Lucene索引 + * 逻辑说明: + * 1. 参数校验 + * 2. 准备搜索环境 + * 3. 构建旧数据映射表 + * 4. 创建新文档并更新 + */ + @SneakyThrows + public void updateDocument(T source) { + if (source == null) { + throw new IllegalArgumentException("Source must not be null"); + } + lock.writeLock().lock(); + try { + String fld = StringUtils.uncapitalize(source.getClassType().getSimpleName() + "Name"); + TermQuery termQuery = new TermQuery(new Term(fld, source.getName())); + BooleanQuery query = buildBooleanQuery(source) + .add(termQuery, BooleanClause.Occur.FILTER) + .build(); + Map sourceMap = buildSourceMap(query, 1); + Document document = createDocument(source, sourceMap); + writer.updateDocuments(query, Collections.singletonList(document)); + reload(); + } finally { + lock.writeLock().unlock(); + } + } + + /** + * 构建更新文档时使用的布尔查询 + * + * @return 构建的布尔查询对象 + */ + private > BooleanQuery.Builder buildBooleanQuery(E model) { + BooleanQuery.Builder booleanQuery = new BooleanQuery.Builder(); + + // 添加类型标识条件 + addTermQuery(booleanQuery, "type", model.getClassType().getSimpleName(), BooleanClause.Occur.FILTER); + + // 使用 STRING 类型的字段自动构建过滤条件 + List stringFields = documentBuilder.getStringFields(model.getClass()); + for (AnnotationBasedDocumentBuilder.AnnotatedFieldInfo info : stringFields) { + Object value = info.getValue(model); + if (value instanceof String) { + addTermQuery(booleanQuery, info.annotation.name(), (String) value, BooleanClause.Occur.FILTER); + } + } + + return booleanQuery; + } + + /** + * 向布尔查询构建器中添加术语查询 + * + * @param booleanQuery 布尔查询构建器 + * @param field 字段名 + * @param value 字段值 + * @param occur 查询条款的发生关系 + */ + private void addTermQuery(BooleanQuery.Builder booleanQuery, String field, String value, + BooleanClause.Occur occur) { + if (StringUtils.isNotBlank(value)) { + Query query = new TermQuery(new Term(field, value)); + booleanQuery.add(query, occur); + } + } + + /** + * 根据实体对象创建Lucene文档 + * 逻辑说明: + * 1. 添加类型标识字段 + * 2. 处理版本号 + * 3. AI注释继承逻辑 + * 4. 使用注解自动构建字段 + * 5. 存储原始数据快照 + * + * @param source 源实体对象 + * @param sourceMap 旧数据映射表(用于字段继承) + * @return 构建完成的Lucene文档 + */ + private Document createDocument(T source, Map sourceMap) { + Document doc = new Document(); + + // 1. 添加类型标识字段 + String typeName = source.getClassType().getSimpleName(); + addStringField(doc, "type", typeName); + + // 2. 处理版本冲突检测和版本号设置 + Long incomingVersion = source.getVersion(); + Long storedVersion = getStoredVersion(source, sourceMap); + + if (storedVersion != null) { + if (incomingVersion != null && incomingVersion < storedVersion) { + throw new ConcurrentModificationException( + String.format("Data version conflict detected incomingVersion:%s storedVersion:%s", + incomingVersion, storedVersion)); + } + long newVersion = storedVersion + 1; + doc.add(new StoredField("version", newVersion)); + doc.add(new NumericDocValuesField("version", newVersion)); + source.setVersion(newVersion); + } else { + doc.add(new StoredField("version", 1L)); + doc.add(new NumericDocValuesField("version", 1L)); + source.setVersion(1L); + } + + // 3. AI注释继承逻辑(新数据为空时从旧数据获取) + handleAiCommentInheritance(source, sourceMap); + + // 4. 使用注解自动构建字段(替代 instanceof 判断) + Document autoDoc = documentBuilder.buildDocument(source); + for (IndexableField field : autoDoc) { + doc.add(field); + } + + // 5. 添加名称字段别名(typeName + "Name") + Optional.ofNullable(source.getName()) + .ifPresent(name -> addStringField(doc, StringUtils.uncapitalize(typeName + "Name"), name)); + + // 6. 存储原始数据快照 + doc.add(new StoredField("source", JSONObject.toJSONString(source))); + return doc; + } + + /** + * 新增版本获取方法 + */ + private Long getStoredVersion(T source, Map sourceMap) { + String nameValue = source.getName(); + if (nameValue == null || sourceMap == null) { + return null; + } + + JSONObject oldData = sourceMap.get(nameValue); + return oldData != null ? oldData.getLong("version") : null; + } + + // 辅助方法:处理AI注释继承 + private void handleAiCommentInheritance(T source, Map sourceMap) { + String nameValue = source.getName(); + if (nameValue == null) { + return; + } + + String aiComment = source.getAiComment(); + if (aiComment == null && sourceMap != null) { + JSONObject oldData = sourceMap.get(nameValue); + if (oldData != null) { + source.setAiComment(oldData.getString("aiComment")); + } + } + } + + /** + * 向文档中添加字符串字段 + * + * @param doc 文档对象 + * @param fieldName 字段名 + * @param value 字段值 + */ + private void addStringField(Document doc, String fieldName, String value) { + if (value != null) { + doc.add(new StringField(fieldName, value, Field.Store.NO)); + } + } + + + @SneakyThrows + public void deleteByDatabaseAndSchema(String databaseName, String schemaName) { + if (StringUtils.isBlank(databaseName)) { + return; + } + lock.writeLock().lock(); + try { + BooleanQuery.Builder builder = new BooleanQuery.Builder() + .add(new TermQuery(new Term("databaseName", databaseName)), BooleanClause.Occur.MUST); + if (StringUtils.isNotBlank(schemaName)) { + builder.add(new TermQuery(new Term("schemaName", schemaName)), BooleanClause.Occur.MUST); + } + writer.deleteDocuments(builder.build()); + reload(); + } finally { + lock.writeLock().unlock(); + } + } + + @SneakyThrows + public > void deleteByDatabaseAndSchema(E queryModel) { + if (queryModel == null || StringUtils.isBlank(queryModel.getDatabaseName())) { + return; + } + lock.writeLock().lock(); + try { + writer.deleteDocuments(buildBooleanQuery(queryModel).build()); + reload(); + } finally { + lock.writeLock().unlock(); + } + } + + /** + * 搜索文档 + * + * @param lastDocId 上一次搜索结果中的最后一个文档ID,用于分页搜索 + * @param queryStr 搜索查询字符串 + * @return 搜索结果的TopDocs对象 + */ + @SneakyThrows + public List search(E queryModel, Integer lastDocId, String queryStr) { + return search(queryModel, lastDocId, queryStr, null, false); + } + + /** + * 搜索文档(支持排序) + * + * @param queryModel 查询模型 + * @param lastDocId 上一次搜索结果中的最后一个文档ID,用于分页搜索 + * @param queryStr 搜索查询字符串 + * @param sortField 排序字段名(如 "name", "rowCount",对应 @LuceneField 注解的 name 属性) + * @param reverse 是否降序 + * @return 搜索结果的TopDocs对象 + */ + @SneakyThrows + public List search(E queryModel, Integer lastDocId, String queryStr, String sortField, boolean reverse) { + lock.readLock().lock(); + try { + BooleanQuery booleanQuery = buildSearchQuery(queryModel, queryStr); + ScoreDoc lastScoreDoc = null; + if (lastDocId != null) { + lastScoreDoc = new ScoreDoc(lastDocId, 1); + } + + TopDocs topDocs; + if (StringUtils.isNotBlank(sortField)) { + // 使用排序搜索(字段名直接对应 @LuceneField 注解的 name 属性) + Sort sort = createSort(sortField, reverse); + if (sort != null) { + topDocs = searcher.searchAfter(lastScoreDoc, booleanQuery, 1000, sort); + } else { + topDocs = searcher.searchAfter(lastScoreDoc, booleanQuery, 1000); + } + } else { + // 不使用排序 + topDocs = searcher.searchAfter(lastScoreDoc, booleanQuery, 1000); + } + long total = topDocs.totalHits.value; + if (total >= 1000) { + this.lastDocId = topDocs.scoreDocs[topDocs.scoreDocs.length - 1].doc; + } + return Arrays.stream(topDocs.scoreDocs) + .map(scoreDoc -> { + T doc = (T) getDocument(queryModel.getClassType(), scoreDoc.doc); + return doc; + }) + .collect(Collectors.toList()); + } finally { + lock.readLock().unlock(); + } + } + + /** + * 创建排序对象 + * + * @param sortField 排序字段名(对应 @LuceneField 注解的 name 属性) + * @param reverse 是否降序 + * @return 排序对象,如果字段不支持排序则返回 null + */ + private Sort createSort(String sortField, boolean reverse) { + // 根据字段名确定排序类型(与 @LuceneField 注解定义保持一致) + if ("name".equals(sortField)) { + return new Sort(new SortField("name", SortField.Type.STRING, reverse)); + } else if ("rowCount".equals(sortField)) { + return new Sort(new SortField("rowCount", SortField.Type.LONG, reverse)); + } + return null; + } + + /** + * 构建搜索查询 + * + * @param queryStr 搜索查询字符串 + * @return 构建的布尔查询对象 + */ + @SneakyThrows + private > BooleanQuery buildSearchQuery(E queryModel, String queryStr) { + BooleanQuery.Builder booleanQuery = buildBooleanQuery(queryModel); + if (StringUtils.isBlank(queryStr)) { + booleanQuery.add(new MatchAllDocsQuery(), BooleanClause.Occur.MUST); + } else { + MultiFieldQueryParser multiParser = new MultiFieldQueryParser(TEXT_FIELDS, analyzer); + multiParser.setDefaultOperator(QueryParser.Operator.AND); + Query query = multiParser.parse(queryStr); + booleanQuery.add(query, BooleanClause.Occur.MUST); + } + return booleanQuery.build(); + } + + @SneakyThrows + private String getSource(int docId) { + StoredFields storedFields = searcher.storedFields(); + Document document = storedFields.document(docId, Sets.newHashSet("source")); + return document.get("source"); + } + + /** + * 获取指定ID的文档,可指定需要加载的字段 + * + * @param docId 文档ID + * @return 加载的文档对象 + */ + + private E getDocument(Class clz, int docId) { + return JSONObject.parseObject(getSource(docId), clz); + } + +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/LuceneIndexManagerFactory.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/LuceneIndexManagerFactory.java new file mode 100644 index 000000000..663de7678 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/LuceneIndexManagerFactory.java @@ -0,0 +1,43 @@ +package ai.chat2db.server.domain.core.cache; + +import java.io.IOException; +import java.util.concurrent.ConcurrentHashMap; + +import org.springframework.beans.factory.DisposableBean; +import org.springframework.stereotype.Component; + +import ai.chat2db.spi.model.IndexModel; +import ai.chat2db.spi.sql.Chat2DBContext; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Component +public class LuceneIndexManagerFactory implements DisposableBean { + + private final ConcurrentHashMap> instances = new ConcurrentHashMap<>(); + + public LuceneIndexManager getManager(Long id) { + if (id == null) { + log.error("dataSourceId is null"); + if (Chat2DBContext.getConnectInfo() != null) { + log.error("dataSourceId is null,use connectInfo:{}", Chat2DBContext.getConnectInfo()); + id = Chat2DBContext.getConnectInfo().getDataSourceId(); + } + } + return (LuceneIndexManager) instances.computeIfAbsent(id, k -> new LuceneIndexManager<>(k)); + } + + @Override + public void destroy() throws Exception { + instances.values().forEach(manager -> { + try { + manager.close(); + } catch (IOException e) { + // 处理关闭异常 + log.error("Failed to close LuceneIndexManager", e); + } + }); + instances.clear(); + } + +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/MixedAnalyzer.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/MixedAnalyzer.java new file mode 100644 index 000000000..e64144c8e --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/MixedAnalyzer.java @@ -0,0 +1,30 @@ +package ai.chat2db.server.domain.core.cache; + +import org.apache.lucene.analysis.Analyzer; +import org.apache.lucene.analysis.TokenStream; +import org.apache.lucene.analysis.Tokenizer; +import org.apache.lucene.analysis.core.LowerCaseFilter; +import org.apache.lucene.analysis.en.PorterStemFilter; +import org.wltea.analyzer.lucene.IKTokenizer; + +/** + * 自定义混合分析器,结合 IKAnalyzer 的中文分词能力和 PorterStemmer 的英文词干提取能力 + * 支持英文单词的单复数形式检索(如 cat/cats, user/users) + */ +public class MixedAnalyzer extends Analyzer { + + @Override + protected TokenStreamComponents createComponents(String fieldName) { + // 使用 IKTokenizer 进行中文分词,同时对英文单词进行分割 + Tokenizer tokenizer = new IKTokenizer(); + + // 转换为小写 + TokenStream stream = new LowerCaseFilter(tokenizer); + + // 应用 PorterStemmer 词干提取,将单词还原为词干形式 + // 例如: cats -> cat, users -> user, running -> run + stream = new PorterStemFilter(stream); + + return new TokenStreamComponents(tokenizer, stream); + } +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/AiConversationConverter.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/AiConversationConverter.java new file mode 100644 index 000000000..e846d4108 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/AiConversationConverter.java @@ -0,0 +1,30 @@ +package ai.chat2db.server.domain.core.converter; + +import java.util.List; + +import ai.chat2db.server.domain.api.model.AiConversation; +import ai.chat2db.server.domain.api.model.AiMessage; +import ai.chat2db.server.domain.api.param.ai.AiConversationCreateParam; +import ai.chat2db.server.domain.api.param.ai.AiMessageSaveParam; +import ai.chat2db.server.domain.repository.entity.AiConversationDO; +import ai.chat2db.server.domain.repository.entity.AiMessageDO; +import org.mapstruct.Mapper; + +/** + * AI 会话/消息 转换器 + */ +@Mapper(componentModel = "spring") +public abstract class AiConversationConverter { + + public abstract AiConversationDO createParam2do(AiConversationCreateParam param); + + public abstract AiConversation do2dto(AiConversationDO conversationDO); + + public abstract List do2dto(List conversationDOS); + + public abstract AiMessage do2MessageDto(AiMessageDO messageDO); + + public abstract List do2MessageDto(List messageDOS); + + public abstract AiMessageDO saveParam2do(AiMessageSaveParam param); +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/DataSourceConverter.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/DataSourceConverter.java index ea308a390..d805d3526 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/DataSourceConverter.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/DataSourceConverter.java @@ -210,7 +210,7 @@ public void fillDetail(List list, DataSourceSelector selector) { return; } List idList = EasyCollectionUtils.toList(list, DataSource::getId); - List queryList = dataSourceService.listQuery(idList, selector).getData(); + List queryList = dataSourceService.listQuery(idList, selector); Map queryMap = EasyCollectionUtils.toIdentityMap(queryList, DataSource::getId); for (DataSource data : list) { if (data == null || data.getId() == null) { diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/DeprecatedTableConverter.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/DeprecatedTableConverter.java new file mode 100644 index 000000000..9ec32d8f0 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/DeprecatedTableConverter.java @@ -0,0 +1,11 @@ +package ai.chat2db.server.domain.core.converter; + +import ai.chat2db.server.domain.api.param.DeprecatedTableParam; +import ai.chat2db.server.domain.repository.entity.DeprecatedTableDO; +import org.mapstruct.Mapper; + +@Mapper(componentModel = "spring") +public abstract class DeprecatedTableConverter { + + public abstract DeprecatedTableDO param2do(DeprecatedTableParam param); +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/EnvironmentConverter.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/EnvironmentConverter.java index b4a6ec943..86bb4e8cf 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/EnvironmentConverter.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/EnvironmentConverter.java @@ -47,7 +47,7 @@ public void fillDetail(List list) { return; } List idList = EasyCollectionUtils.toList(list, Environment::getId); - List queryList = environmentService.listQuery(idList).getData(); + List queryList = environmentService.listQuery(idList); Map queryMap = EasyCollectionUtils.toIdentityMap(queryList, Environment::getId); for (Environment data : list) { if (data == null || data.getId() == null) { diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/TableConverter.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/TableConverter.java deleted file mode 100644 index cc2b629ca..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/TableConverter.java +++ /dev/null @@ -1,17 +0,0 @@ -package ai.chat2db.server.domain.core.converter; - -import ai.chat2db.server.domain.api.param.TableVectorParam; -import ai.chat2db.server.domain.repository.entity.TableVectorMappingDO; -import org.mapstruct.Mapper; - -@Mapper(componentModel = "spring") -public abstract class TableConverter { - - /** - * TableVectorParam to TableVectorMappingDO - * - * @param param - * @return - */ - public abstract TableVectorMappingDO toTableVectorMappingDO(TableVectorParam param); -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/TeamConverter.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/TeamConverter.java index 62a4b4fd6..7a07941d9 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/TeamConverter.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/TeamConverter.java @@ -83,7 +83,7 @@ public void fillDetail(List list) { return; } List idList = EasyCollectionUtils.toList(list, Team::getId); - List queryList = teamService.listQuery(idList).getData(); + List queryList = teamService.listQuery(idList); Map queryMap = EasyCollectionUtils.toIdentityMap(queryList, Team::getId); for (Team data : list) { if (data == null || data.getId() == null) { diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/TeamUserConverter.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/TeamUserConverter.java index 87ec85b0a..c6cbdcb33 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/TeamUserConverter.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/TeamUserConverter.java @@ -75,7 +75,7 @@ public void fillDetail(List list) { return; } List idList = EasyCollectionUtils.toList(list, Environment::getId); - List queryList = environmentService.listQuery(idList).getData(); + List queryList = environmentService.listQuery(idList); Map queryMap = EasyCollectionUtils.toIdentityMap(queryList, Environment::getId); for (Environment data : list) { if (data == null || data.getId() == null) { diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/UserConverter.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/UserConverter.java index 3ca22755b..d7adc53ee 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/UserConverter.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/UserConverter.java @@ -86,7 +86,7 @@ public void fillDetail(List list) { return; } List idList = EasyCollectionUtils.toList(list, User::getId); - List queryList = userService.listQuery(idList).getData(); + List queryList = userService.listQuery(idList); Map queryMap = EasyCollectionUtils.toIdentityMap(queryList, User::getId); for (User data : list) { if (data == null || data.getId() == null) { diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/generator/ExpressionDataGenerator.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/generator/ExpressionDataGenerator.java new file mode 100644 index 000000000..a6f42ec02 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/generator/ExpressionDataGenerator.java @@ -0,0 +1,88 @@ +package ai.chat2db.server.domain.core.generator; + +import lombok.extern.slf4j.Slf4j; +import net.datafaker.Faker; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class ExpressionDataGenerator { + + public Object generate(Faker faker, String expression, ColumnGenerationConfig config) { + if (expression == null || expression.isBlank()) { + return null; + } + try { + Object result = faker.expression(expression); + result = convertByDataType(result, config); + if (result instanceof String str && config.maxLength() != null && str.length() > config.maxLength()) { + return truncate(str, config.maxLength()); + } + return result; + } catch (Exception e) { + log.warn("Failed to evaluate expression: {}", expression, e); + return "[表达式错误: " + expression + "]"; + } + } + + private Object convertByDataType(Object value, ColumnGenerationConfig config) { + if (value == null || config.dataType() == null) { + return value; + } + String dataType = config.dataType().toLowerCase(); + if (isIntegerType(dataType)) { + Boolean boolValue = parseBoolean(value); + if (boolValue != null) { + return boolValue ? 1 : 0; + } + } + if (isBooleanType(dataType)) { + Boolean boolValue = parseBoolean(value); + if (boolValue != null) { + return boolValue; + } + } + return value; + } + + private boolean isIntegerType(String dataType) { + return dataType.contains("tinyint") || dataType.contains("smallint") || dataType.contains("mediumint") + || dataType.equals("int") || dataType.contains("integer") || dataType.contains("bigint"); + } + + private boolean isBooleanType(String dataType) { + return dataType.contains("boolean") || dataType.contains("bool"); + } + + private Boolean parseBoolean(Object value) { + if (value instanceof Boolean boolValue) { + return boolValue; + } + if (value instanceof String str) { + String normalized = str.trim().toLowerCase(); + if ("true".equals(normalized)) { + return true; + } + if ("false".equals(normalized)) { + return false; + } + } + return null; + } + + private String truncate(String text, int maxLength) { + if (maxLength <= 3) { + return text.substring(0, maxLength); + } + return text.substring(0, maxLength - 3) + "..."; + } + + public record ColumnGenerationConfig( + String columnName, + String dataType, + Boolean nullable, + Integer maxLength, + Integer scale + ) { + } +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/AiConversationServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/AiConversationServiceImpl.java new file mode 100644 index 000000000..0d790edd8 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/AiConversationServiceImpl.java @@ -0,0 +1,328 @@ +package ai.chat2db.server.domain.core.impl; + +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.UUID; +import java.util.stream.Collectors; + +import ai.chat2db.server.domain.api.enums.RoleCodeEnum; +import ai.chat2db.server.domain.api.model.AiConversation; +import ai.chat2db.server.domain.api.model.AiConversationDetail; +import ai.chat2db.server.domain.api.model.AiMessage; +import ai.chat2db.server.domain.api.model.DataSource; +import ai.chat2db.server.domain.api.param.ai.AiConversationCreateParam; +import ai.chat2db.server.domain.api.param.ai.AiConversationQueryParam; +import ai.chat2db.server.domain.api.param.ai.AiMessageSaveParam; +import ai.chat2db.server.domain.api.service.AiConversationService; +import ai.chat2db.server.domain.api.service.DataSourceService; +import ai.chat2db.server.domain.core.converter.AiConversationConverter; +import ai.chat2db.server.domain.core.util.PermissionUtils; +import ai.chat2db.server.domain.repository.Dbutils; +import ai.chat2db.server.domain.repository.entity.AiConversationDO; +import ai.chat2db.server.domain.repository.entity.AiMessageDO; +import ai.chat2db.server.domain.repository.mapper.AiConversationMapper; +import ai.chat2db.server.domain.repository.mapper.AiMessageMapper; +import ai.chat2db.server.tools.base.wrapper.ServicePage; +import ai.chat2db.server.tools.common.exception.DataNotFoundException; +import ai.chat2db.server.tools.common.util.ContextUtils; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class AiConversationServiceImpl implements AiConversationService { + + private static final int MAX_ACTIVE_CONVERSATIONS = 100; + + private static final String STATUS_ACTIVE = "ACTIVE"; + private static final String STATUS_ARCHIVED = "ARCHIVED"; + private static final String STATUS_DELETED = "DELETED"; + + private AiConversationMapper getConversationMapper() { + return Dbutils.getMapper(AiConversationMapper.class); + } + + private AiMessageMapper getMessageMapper() { + return Dbutils.getMapper(AiMessageMapper.class); + } + + @Autowired + private AiConversationConverter converter; + + @Autowired + private DataSourceService dataSourceService; + + @Override + public String create(AiConversationCreateParam param) { + Long userId = param.getUserId() != null ? param.getUserId() : ContextUtils.getUserId(); + if (StringUtils.isNotBlank(param.getConversationId())) { + AiConversation existing = findByConversationId(param.getConversationId()); + if (existing != null) { + PermissionUtils.checkOperationPermission(existing.getUserId()); + return existing.getConversationId(); + } + } + enforceQuota(userId); + + AiConversationDO conversationDO = converter.createParam2do(param); + String conversationId = StringUtils.isNotBlank(param.getConversationId()) + ? param.getConversationId() + : UUID.randomUUID().toString(); + conversationDO.setConversationId(conversationId); + conversationDO.setUserId(userId); + conversationDO.setStatus(STATUS_ACTIVE); + conversationDO.setMessageCount(0); + conversationDO.setGmtCreate(LocalDateTime.now()); + conversationDO.setGmtModified(LocalDateTime.now()); + if (StringUtils.isBlank(conversationDO.getTitle())) { + conversationDO.setTitle("新对话"); + } + getConversationMapper().insert(conversationDO); + return conversationId; + } + + @Override + public void updateTitle(String conversationId, String title) { + if (StringUtils.isBlank(conversationId) || StringUtils.isBlank(title)) { + return; + } + String trimmed = title.length() > 50 ? title.substring(0, 50) : title; + getConversationMapper().update(null, + new LambdaUpdateWrapper() + .eq(AiConversationDO::getConversationId, conversationId) + .set(AiConversationDO::getTitle, trimmed) + .set(AiConversationDO::getGmtModified, LocalDateTime.now())); + } + + @Override + public void deleteWithPermission(String conversationId, Long userId) { + AiConversation conv = findByConversationId(conversationId); + if (conv == null) { + return; + } + PermissionUtils.checkOperationPermission(conv.getUserId()); + getConversationMapper().update(null, + new LambdaUpdateWrapper() + .eq(AiConversationDO::getConversationId, conversationId) + .set(AiConversationDO::getStatus, STATUS_DELETED) + .set(AiConversationDO::getGmtModified, LocalDateTime.now())); + } + + @Override + public ServicePage queryPage(AiConversationQueryParam param) { + if (param.getPageNo() == null) { + param.setPageNo(1); + } + if (param.getPageSize() == null) { + param.setPageSize(20); + } + if (StringUtils.isBlank(param.getStatus())) { + param.setStatus(STATUS_ACTIVE); + } + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(AiConversationDO::getUserId, param.getUserId()) + .eq(AiConversationDO::getStatus, param.getStatus()) + .like(StringUtils.isNotBlank(param.getSearchKey()), AiConversationDO::getTitle, param.getSearchKey()) + .eq(param.getDataSourceId() != null, AiConversationDO::getDataSourceId, param.getDataSourceId()) + .orderByDesc(AiConversationDO::getGmtModified); + + IPage page = getConversationMapper().selectPage(new Page<>(param.getPageNo(), param.getPageSize()), wrapper); + List records = converter.do2dto(page.getRecords()); + if (CollectionUtils.isNotEmpty(records)) { + List dataSourceIds = records.stream() + .map(AiConversation::getDataSourceId) + .filter(Objects::nonNull) + .distinct() + .collect(Collectors.toList()); + if (CollectionUtils.isNotEmpty(dataSourceIds)) { + List dataSources = dataSourceService.listQuery(dataSourceIds, null); + records.forEach(c -> dataSources.stream() + .filter(ds -> ds.getId().equals(c.getDataSourceId())) + .findFirst() + .ifPresent(ds -> c.setDataSourceName(ds.getAlias()))); + } + } + return ServicePage.of(records, page.getTotal(), param.getPageNo(), param.getPageSize()); + } + + @Override + public AiConversationDetail getDetail(String conversationId, Long userId) { + AiConversation conversation = findByConversationId(conversationId); + if (conversation == null || STATUS_DELETED.equals(conversation.getStatus())) { + return null; + } + PermissionUtils.checkBaseQueryPermission(conversation.getUserId()); + + List messageDOS = getMessageMapper().selectList( + new LambdaQueryWrapper() + .eq(AiMessageDO::getConversationId, conversationId) + .orderByAsc(AiMessageDO::getSequenceNo) + ); + List messages = converter.do2MessageDto(messageDOS); + + if (conversation.getDataSourceId() != null) { + DataSource dataSource = dataSourceService.queryExistent(conversation.getDataSourceId(), null); + if (dataSource != null) { + conversation.setDataSourceName(dataSource.getAlias()); + } + } + + AiConversationDetail detail = new AiConversationDetail(); + detail.setConversation(conversation); + detail.setMessages(messages); + return detail; + } + + @Override + public AiConversation findByConversationId(String conversationId) { + if (StringUtils.isBlank(conversationId)) { + return null; + } + AiConversationDO conversationDO = getConversationMapper().selectOne( + new LambdaQueryWrapper() + .eq(AiConversationDO::getConversationId, conversationId) + .last("LIMIT 1") + ); + if (conversationDO == null) { + return null; + } + return converter.do2dto(conversationDO); + } + + @Override + public void appendMessage(AiMessageSaveParam param) { + if (param == null || StringUtils.isBlank(param.getConversationId())) { + return; + } + AiMessageDO messageDO = converter.saveParam2do(param); + if (messageDO.getGmtCreate() == null) { + messageDO.setGmtCreate(LocalDateTime.now()); + } + getMessageMapper().insert(messageDO); + } + + @Override + public void appendMessageTurn(String conversationId, + Long userId, + String userMessageId, + String userContent, + String assistantMessageId, + String assistantContent, + String assistantThinking, + String promptType, + String sqlExtracted) { + if (StringUtils.isBlank(conversationId) || StringUtils.isBlank(userContent) + || StringUtils.isBlank(assistantContent)) { + return; + } + + AiConversationDO conv = getConversationMapper().selectOne( + new LambdaQueryWrapper() + .eq(AiConversationDO::getConversationId, conversationId) + .last("LIMIT 1") + ); + if (conv == null) { + return; + } + if (STATUS_DELETED.equals(conv.getStatus())) { + return; + } + + int nextSeq = (conv.getMessageCount() == null ? 0 : conv.getMessageCount()); + + AiMessageDO userMsg = new AiMessageDO(); + userMsg.setConversationId(conversationId); + userMsg.setUserId(userId); + userMsg.setMessageId(userMessageId); + userMsg.setRole("user"); + userMsg.setContent(userContent); + userMsg.setPromptType(promptType); + userMsg.setSequenceNo(nextSeq); + userMsg.setGmtCreate(LocalDateTime.now()); + getMessageMapper().insert(userMsg); + + AiMessageDO assistantMsg = new AiMessageDO(); + assistantMsg.setConversationId(conversationId); + assistantMsg.setUserId(userId); + assistantMsg.setMessageId(assistantMessageId); + assistantMsg.setRole("assistant"); + assistantMsg.setContent(assistantContent); + assistantMsg.setThinking(assistantThinking); + assistantMsg.setPromptType(promptType); + assistantMsg.setSqlExtracted(sqlExtracted); + assistantMsg.setSequenceNo(nextSeq + 1); + assistantMsg.setGmtCreate(LocalDateTime.now()); + getMessageMapper().insert(assistantMsg); + + String preview = assistantContent.length() > 200 + ? assistantContent.substring(0, 200) + "..." + : assistantContent; + preview = preview.replaceAll("\\s+", " ").trim(); + + getConversationMapper().update(null, + new LambdaUpdateWrapper() + .eq(AiConversationDO::getConversationId, conversationId) + .set(AiConversationDO::getMessageCount, nextSeq + 2) + .set(AiConversationDO::getLastMessagePreview, preview) + .set(AiConversationDO::getGmtModified, LocalDateTime.now())); + } + + @Override + public List listRecentMessages(String conversationId, int limit) { + if (StringUtils.isBlank(conversationId) || limit <= 0) { + return Collections.emptyList(); + } + List recent = getMessageMapper().selectList( + new LambdaQueryWrapper() + .eq(AiMessageDO::getConversationId, conversationId) + .orderByDesc(AiMessageDO::getSequenceNo) + .last("LIMIT " + limit) + ); + Collections.reverse(recent); + return converter.do2MessageDto(recent); + } + + private void enforceQuota(Long userId) { + if (userId == null) { + return; + } + if (RoleCodeEnum.DESKTOP.getDefaultUserId().equals(userId)) { + return; + } + Long activeCount = getConversationMapper().selectCount( + new LambdaQueryWrapper() + .eq(AiConversationDO::getUserId, userId) + .eq(AiConversationDO::getStatus, STATUS_ACTIVE) + ); + if (activeCount == null || activeCount < MAX_ACTIVE_CONVERSATIONS) { + return; + } + long toArchive = activeCount - MAX_ACTIVE_CONVERSATIONS + 1; + if (toArchive <= 0) { + return; + } + List oldest = getConversationMapper().selectList( + new LambdaQueryWrapper() + .eq(AiConversationDO::getUserId, userId) + .eq(AiConversationDO::getStatus, STATUS_ACTIVE) + .orderByAsc(AiConversationDO::getGmtModified) + .last("LIMIT " + toArchive) + ); + if (CollectionUtils.isEmpty(oldest)) { + return; + } + List ids = oldest.stream().map(AiConversationDO::getId).collect(Collectors.toList()); + getConversationMapper().update(null, + new LambdaUpdateWrapper() + .in(AiConversationDO::getId, ids) + .set(AiConversationDO::getStatus, STATUS_ARCHIVED) + .set(AiConversationDO::getGmtModified, LocalDateTime.now())); + } +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ChartServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ChartServiceImpl.java index 4225a8fe8..6855b4600 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ChartServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ChartServiceImpl.java @@ -57,40 +57,40 @@ private DashboardChartRelationMapper getDashboardMapper() { private ChartConverter chartConverter; @Override - public DataResult createWithPermission(ChartCreateParam param) { + public Long createWithPermission(ChartCreateParam param) { param.setGmtCreate(LocalDateTime.now()); param.setGmtModified(LocalDateTime.now()); param.setDeleted(YesOrNoEnum.NO.getLetter()); param.setUserId(ContextUtils.getUserId()); ChartDO chartDO = chartConverter.param2do(param); getMapper().insert(chartDO); - return DataResult.of(chartDO.getId()); + return chartDO.getId(); } @Override - public ActionResult updateWithPermission(ChartUpdateParam param) { - Chart data = queryExistent(param.getId()).getData(); + public void updateWithPermission(ChartUpdateParam param) { + Chart data = queryExistent(param.getId()); PermissionUtils.checkOperationPermission(data.getUserId()); param.setGmtModified(LocalDateTime.now()); ChartDO chartDO = chartConverter.updateParam2do(param); getMapper().updateById(chartDO); - return ActionResult.isSuccess(); + } @Override - public DataResult find(Long id) { + public Chart find(Long id) { ChartDO chartDO = getMapper().selectById(id); - if (YesOrNoEnum.YES.getLetter().equals(chartDO.getDeleted())) { - return DataResult.empty(); + if (chartDO == null || YesOrNoEnum.YES.getLetter().equals(chartDO.getDeleted())) { + return null; } Chart chart = chartConverter.do2model(chartDO); setDataSourceInfo(Lists.newArrayList(chart)); - return DataResult.of(chart); + return chart; } @Override - public DataResult queryExistent(ChartQueryParam param) { + public Chart queryExistent(ChartQueryParam param) { EasyLambdaQueryWrapper queryWrapper = new EasyLambdaQueryWrapper<>(); queryWrapper .eq(ChartDO::getDeleted, YesOrNoEnum.NO.getLetter()) @@ -102,20 +102,20 @@ public DataResult queryExistent(ChartQueryParam param) { } Chart data = chartConverter.do2model(page.getRecords().get(0)); setDataSourceInfo(Lists.newArrayList(data)); - return DataResult.of(data); + return data; } @Override - public DataResult queryExistent(Long id) { - DataResult dataResult = find(id); - if (dataResult.getData() == null) { + public Chart queryExistent(Long id) { + Chart chart = find(id); + if (chart == null) { throw new DataNotFoundException(); } - return dataResult; + return chart; } @Override - public ListResult listQuery(ChartListQueryParam param) { + public List listQuery(ChartListQueryParam param) { EasyLambdaQueryWrapper queryWrapper = new EasyLambdaQueryWrapper<>(); queryWrapper .eq(ChartDO::getDeleted, YesOrNoEnum.NO.getLetter()) @@ -124,12 +124,12 @@ public ListResult listQuery(ChartListQueryParam param) { List queryList = getMapper().selectList(queryWrapper); List list = chartConverter.do2model(queryList); setDataSourceInfo(list); - return ListResult.of(list); + return list; } @Override - public ActionResult deleteWithPermission(Long id) { - Chart data = queryExistent(id).getData(); + public void deleteWithPermission(Long id) { + Chart data = queryExistent(id); PermissionUtils.checkOperationPermission(data.getUserId()); ChartDO chartDO = new ChartDO(); @@ -143,19 +143,7 @@ public ActionResult deleteWithPermission(Long id) { if (CollectionUtils.isNotEmpty(relationIds)) { getDashboardMapper().deleteBatchIds(relationIds); } - return ActionResult.isSuccess(); - } - - @Override - public ListResult queryByIds(List ids) { - if (CollectionUtils.isEmpty(ids)) { - return ListResult.empty(); - } - List chartDOS = getMapper().selectBatchIds(ids); - List charts = chartConverter.do2model(chartDOS); - List result = charts.stream().filter(o -> YesOrNoEnum.NO.getLetter().equals(o.getDeleted())).toList(); - setDataSourceInfo(result); - return ListResult.of(result); + } /** @@ -165,8 +153,8 @@ public ListResult queryByIds(List ids) { */ private void setDataSourceInfo(List result) { List dataSourceIds = result.stream().map(Chart::getDataSourceId).toList(); - ListResult dataSourceListResult = dataSourceService.queryByIds(dataSourceIds); - Map dataSourceMap = dataSourceListResult.getData().stream().collect( + List dataSourceListResult = dataSourceService.listQuery(dataSourceIds, null); + Map dataSourceMap = dataSourceListResult.stream().collect( Collectors.toMap(DataSource::getId, Function.identity(), (a, b) -> a)); result.forEach(o -> { if (dataSourceMap.containsKey(o.getDataSourceId())) { diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ConfigServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ConfigServiceImpl.java index a7d383008..7b0634086 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ConfigServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ConfigServiceImpl.java @@ -33,44 +33,44 @@ private SystemConfigMapper getMapper() { private ConfigConverter configConverter; @Override - public ActionResult create(SystemConfigParam param) { + public void create(SystemConfigParam param) { SystemConfigDO systemConfigDO = configConverter.param2do(param); systemConfigDO.setGmtCreate(LocalDateTime.now()); systemConfigDO.setGmtModified(LocalDateTime.now()); getMapper().insert(systemConfigDO); - return ActionResult.isSuccess(); + } @Override - public ActionResult update(SystemConfigParam param) { + public void update(SystemConfigParam param) { SystemConfigDO systemConfigDO = configConverter.param2do(param); UpdateWrapper updateWrapper = new UpdateWrapper<>(); updateWrapper.eq("code", param.getCode()); getMapper().update(systemConfigDO, updateWrapper); - return ActionResult.isSuccess(); + } @Override - public ActionResult createOrUpdate(SystemConfigParam param) { + public void createOrUpdate(SystemConfigParam param) { SystemConfigDO systemConfigDO = getMapper().selectOne( new UpdateWrapper().eq("code", param.getCode())); if (systemConfigDO == null) { - return create(param); + create(param); } else { - return update(param); + update(param); } } @Override - public DataResult find(String code) { + public Config find(String code) { SystemConfigDO systemConfigDO = getMapper().selectOne( new UpdateWrapper().eq("code", code)); - return DataResult.of(configConverter.do2model(systemConfigDO)); + return configConverter.do2model(systemConfigDO); } @Override - public ActionResult delete(String code) { + public void delete(String code) { getMapper().delete(new UpdateWrapper().eq("code", code)); - return ActionResult.isSuccess(); + } } \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ConsoleServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ConsoleServiceImpl.java index 00904a674..70415403f 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ConsoleServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ConsoleServiceImpl.java @@ -17,14 +17,14 @@ @Service public class ConsoleServiceImpl implements ConsoleService { @Override - public ActionResult createConsole(ConsoleConnectParam param) { + public void createConsole(ConsoleConnectParam param) { Chat2DBContext.getDBManage().connectDatabase(Chat2DBContext.getConnection(),param.getDatabaseName()); - return ActionResult.isSuccess(); + } @Override - public ActionResult closeConsole(ConsoleCloseParam param) { - return ActionResult.isSuccess(); + public void closeConsole(ConsoleCloseParam param) { + } } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DashboardServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DashboardServiceImpl.java index b3e15d705..27f9bb5db 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DashboardServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DashboardServiceImpl.java @@ -21,6 +21,7 @@ import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; +import ai.chat2db.server.tools.base.wrapper.ServicePage; import ai.chat2db.server.tools.common.exception.DataNotFoundException; import ai.chat2db.server.tools.common.model.EasyLambdaQueryWrapper; import ai.chat2db.server.tools.common.util.ContextUtils; @@ -53,7 +54,7 @@ private DashboardChartRelationMapper getMapper1() { private DashboardConverter dashboardConverter; @Override - public DataResult createWithPermission(DashboardCreateParam param) { + public Long createWithPermission(DashboardCreateParam param) { param.setGmtCreate(LocalDateTime.now()); param.setGmtModified(LocalDateTime.now()); param.setDeleted(YesOrNoEnum.NO.getLetter()); @@ -61,30 +62,30 @@ public DataResult createWithPermission(DashboardCreateParam param) { DashboardDO dashboardDO = dashboardConverter.param2do(param); getMapper().insert(dashboardDO); insertDashboardRelation(dashboardDO.getId(), param.getChartIds()); - return DataResult.of(dashboardDO.getId()); + return dashboardDO.getId(); } @Override - public ActionResult updateWithPermission(DashboardUpdateParam param) { - Dashboard data = queryExistent(param.getId()).getData(); + public void updateWithPermission(DashboardUpdateParam param) { + Dashboard data = queryExistent(param.getId()); PermissionUtils.checkOperationPermission(data.getUserId()); param.setGmtModified(LocalDateTime.now()); DashboardDO dashboardDO = dashboardConverter.updateParam2do(param); getMapper().updateById(dashboardDO); if (CollectionUtils.isEmpty(param.getChartIds())) { - return ActionResult.isSuccess(); + } deleteDashboardRelation(dashboardDO.getId()); insertDashboardRelation(dashboardDO.getId(), param.getChartIds()); - return ActionResult.isSuccess(); + } @Override - public DataResult find(Long id) { + public Dashboard find(Long id) { DashboardDO dashboardDO = getMapper().selectById(id); if (YesOrNoEnum.YES.getLetter().equals(dashboardDO.getDeleted())) { - return DataResult.empty(); + return null; } Dashboard dashboard = dashboardConverter.do2model(dashboardDO); LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); @@ -92,11 +93,11 @@ public DataResult find(Long id) { List relationDO = getMapper1().selectList(queryWrapper); List chartIds = relationDO.stream().map(DashboardChartRelationDO::getChartId).toList(); dashboard.setChartIds(chartIds); - return DataResult.of(dashboard); + return dashboard; } @Override - public DataResult queryExistent(DashboardQueryParam param) { + public Dashboard queryExistent(DashboardQueryParam param) { EasyLambdaQueryWrapper queryWrapper = new EasyLambdaQueryWrapper<>(); queryWrapper .eq(DashboardDO::getDeleted, YesOrNoEnum.NO.getLetter()) @@ -113,21 +114,21 @@ public DataResult queryExistent(DashboardQueryParam param) { dashboardChartRelationQueryWrapper); List chartIds = relationDO.stream().map(DashboardChartRelationDO::getChartId).toList(); data.setChartIds(chartIds); - return DataResult.of(data); + return data; } @Override - public DataResult queryExistent(Long id) { - DataResult dataResult = find(id); - if (dataResult.getData() == null) { + public Dashboard queryExistent(Long id) { + Dashboard dataResult = find(id); + if (dataResult == null) { throw new DataNotFoundException(); } return dataResult; } @Override - public ActionResult deleteWithPermission(Long id) { - Dashboard data = queryExistent(id).getData(); + public void deleteWithPermission(Long id) { + Dashboard data = queryExistent(id); PermissionUtils.checkOperationPermission(data.getUserId()); DashboardDO dashboardDO = new DashboardDO(); @@ -135,7 +136,7 @@ public ActionResult deleteWithPermission(Long id) { dashboardDO.setDeleted(YesOrNoEnum.YES.getLetter()); getMapper().updateById(dashboardDO); deleteDashboardRelation(id); - return ActionResult.isSuccess(); + } /** @@ -174,7 +175,7 @@ private void insertDashboardRelation(Long dashboardId, List chartIds) { } @Override - public PageResult queryPage(DashboardPageQueryParam param) { + public ServicePage queryPage(DashboardPageQueryParam param) { EasyLambdaQueryWrapper queryWrapper = new EasyLambdaQueryWrapper<>(); queryWrapper .eq(DashboardDO::getDeleted, YesOrNoEnum.NO.getLetter()) @@ -185,6 +186,6 @@ public PageResult queryPage(DashboardPageQueryParam param) { Page page = new Page<>(start, offset); IPage iPage = getMapper().selectPage(page, queryWrapper); List dashboards = dashboardConverter.do2model(iPage.getRecords()); - return PageResult.of(dashboards, iPage.getTotal(), param); + return ServicePage.of(dashboards, iPage.getTotal(), param.getPageNo(), param.getPageSize()); } } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataGenerationRuleServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataGenerationRuleServiceImpl.java new file mode 100644 index 000000000..41828139e --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataGenerationRuleServiceImpl.java @@ -0,0 +1,108 @@ +package ai.chat2db.server.domain.core.impl; + +import ai.chat2db.server.domain.api.param.ColumnConfigParam; +import ai.chat2db.server.domain.api.service.DataGenerationRuleService; +import ai.chat2db.server.domain.repository.Dbutils; +import ai.chat2db.server.domain.repository.entity.DataGenerationRuleDO; +import ai.chat2db.server.domain.repository.mapper.DataGenerationRuleMapper; +import ai.chat2db.server.tools.base.wrapper.result.ActionResult; +import ai.chat2db.server.tools.base.wrapper.result.ListResult; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; + +@Slf4j +@Service +public class DataGenerationRuleServiceImpl implements DataGenerationRuleService { + + private static final ObjectMapper JSON_MAPPER = new ObjectMapper(); + + private DataGenerationRuleMapper getMapper() { + return Dbutils.getMapper(DataGenerationRuleMapper.class); + } + + @Override + public List getColumnConfigs(Long dataSourceId, String databaseName, String schemaName, String tableName) { + try { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("data_source_id", dataSourceId); + queryWrapper.eq("database_name", databaseName); + queryWrapper.eq("table_name", tableName); + if (schemaName != null) { + queryWrapper.eq("schema_name", schemaName); + } else { + queryWrapper.isNull("schema_name"); + } + + DataGenerationRuleDO rule = getMapper().selectOne(queryWrapper); + if (rule == null || rule.getColumnConfigs() == null) { + return Collections.emptyList(); + } + + return JSON_MAPPER.readValue( + rule.getColumnConfigs(), new TypeReference>() {}); + } catch (Exception e) { + log.error("Failed to get column configs", e); + return Collections.emptyList(); + } + } + + @Override + public void saveColumnConfigs(Long dataSourceId, String databaseName, String schemaName, String tableName, Long userId, List configs, Integer rowCount) { + if (configs == null || configs.isEmpty()) { + return; + } + try { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("data_source_id", dataSourceId); + queryWrapper.eq("database_name", databaseName); + queryWrapper.eq("table_name", tableName); + if (schemaName != null) { + queryWrapper.eq("schema_name", schemaName); + } else { + queryWrapper.isNull("schema_name"); + } + + DataGenerationRuleDO existing = getMapper().selectOne(queryWrapper); + LocalDateTime now = LocalDateTime.now(); + String jsonConfigs; + try { + jsonConfigs = JSON_MAPPER.writeValueAsString(configs); + } catch (JsonProcessingException e) { + log.error("Failed to serialize column configs", e); + return; + } + + if (existing != null) { + existing.setColumnConfigs(jsonConfigs); + if (rowCount != null) { + existing.setRowCount(rowCount); + } + existing.setGmtModified(now); + getMapper().updateById(existing); + } else { + DataGenerationRuleDO rule = new DataGenerationRuleDO(); + rule.setDataSourceId(dataSourceId); + rule.setDatabaseName(databaseName); + rule.setSchemaName(schemaName); + rule.setTableName(tableName); + rule.setRowCount(rowCount != null ? rowCount : 100); + rule.setColumnConfigs(jsonConfigs); + rule.setUserId(userId); + rule.setGmtCreate(now); + rule.setGmtModified(now); + getMapper().insert(rule); + } + + } catch (Exception e) { + log.error("Failed to save column configs", e); + } + } +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataGenerationServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataGenerationServiceImpl.java new file mode 100644 index 000000000..8668868aa --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataGenerationServiceImpl.java @@ -0,0 +1,606 @@ +package ai.chat2db.server.domain.core.impl; + +import ai.chat2db.server.domain.api.param.DataGenerationRequest; +import ai.chat2db.server.domain.api.param.ColumnConfigParam; +import ai.chat2db.server.domain.api.param.TableQueryParam; +import ai.chat2db.server.domain.api.param.GeneratorTemplate; +import ai.chat2db.server.domain.api.param.TaskCreateParam; +import ai.chat2db.server.domain.api.param.TaskUpdateParam; +import ai.chat2db.server.domain.api.service.DataGenerationService; +import ai.chat2db.server.domain.api.service.ForeignKeySyncService; +import ai.chat2db.server.domain.api.service.TableService; +import ai.chat2db.server.domain.api.service.TaskService; +import ai.chat2db.server.domain.api.service.DataGenerationRuleService; +import ai.chat2db.server.domain.api.enums.TaskStatusEnum; +import ai.chat2db.server.domain.api.enums.TaskTypeEnum; +import ai.chat2db.server.domain.api.vo.DataGenerationPreviewVO; +import ai.chat2db.server.domain.core.generator.ExpressionDataGenerator; +import ai.chat2db.server.domain.repository.Dbutils; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; +import ai.chat2db.server.tools.base.wrapper.result.ListResult; +import ai.chat2db.server.tools.common.model.Context; +import ai.chat2db.server.tools.common.model.LoginUser; +import ai.chat2db.server.tools.common.util.ContextUtils; +import ai.chat2db.spi.MetaData; +import ai.chat2db.spi.model.ForeignKey; +import ai.chat2db.spi.model.TableColumn; +import ai.chat2db.spi.model.VirtualForeignKey; +import ai.chat2db.spi.sql.Chat2DBContext; +import ai.chat2db.spi.sql.ConnectInfo; +import ai.chat2db.server.tools.base.excption.BusinessException; +import ai.chat2db.server.tools.common.util.I18nUtils; +import lombok.extern.slf4j.Slf4j; +import net.datafaker.Faker; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.stereotype.Service; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +@Slf4j +@Service +public class DataGenerationServiceImpl implements DataGenerationService { + + private static final int MAX_REFERENCED_VALUE_ROWS = 10000; + private static final String SOURCE_TYPE_REAL = "REAL"; + private static final String SOURCE_TYPE_VIRTUAL = "VIRTUAL"; + + @Autowired + private TableService tableService; + + @Autowired + private TaskService taskService; + + @Autowired + private ExpressionDataGenerator expressionDataGenerator; + + @Autowired + private DataGenerationRuleService ruleService; + + @Autowired + private ForeignKeySyncService foreignKeySyncService; + + @Override + public List getTableColumns(DataGenerationRequest request) { + try { + TableQueryParam param = new TableQueryParam(); + param.setDataSourceId(request.getDataSourceId()); + param.setDatabaseName(request.getDatabaseName()); + param.setSchemaName(request.getSchemaName()); + param.setTableName(request.getTableName()); + + List tableColumns = tableService.queryColumns(param); + if (tableColumns == null) { + throw new BusinessException("GET_TABLE_COLUMNS_ERROR", new Object[]{"获取表列信息失败"}); + } + List foreignKeys = loadForeignKeys(request, true); + Map foreignKeyMap = buildForeignKeyMap(foreignKeys); + + List savedConfigs = ruleService.getColumnConfigs( + request.getDataSourceId(), request.getDatabaseName(), request.getSchemaName(), request.getTableName()); + + Map savedMap = new HashMap<>(); + if (savedConfigs != null && !savedConfigs.isEmpty()) { + for (ColumnConfigParam cfg : savedConfigs) { + savedMap.put(cfg.getColumnName(), cfg); + } + } + + List columns = new ArrayList<>(); + for (TableColumn column : tableColumns) { + ColumnConfigParam config = new ColumnConfigParam(); + config.setColumnName(column.getName()); + String dataType = column.getDataType() != null ? column.getDataType() : "VARCHAR"; + config.setDataType(dataType); + config.setComment(column.getComment()); + config.setNullable(column.getNullable() != null && column.getNullable() == 1); + config.setAutoIncrement(column.getAutoIncrement() != null && column.getAutoIncrement()); + config.setMaxLength(column.getColumnSize()); + config.setScale(column.getDecimalDigits()); + applyForeignKeyInfo(config, foreignKeyMap.get(column.getName())); + + ColumnConfigParam saved = savedMap.get(column.getName()); + if (saved != null && saved.getExpression() != null) { + config.setExpression(saved.getExpression()); + } + + columns.add(config); + } + + return columns; + } catch (Exception e) { + log.error("Failed to get table columns", e); + throw new BusinessException("GET_TABLE_COLUMNS_ERROR", new Object[]{"获取表列信息失败: " + e.getMessage()}); + } + } + + @Override + public DataGenerationPreviewVO generatePreview(DataGenerationRequest request) { + try { + saveConfigs(request); + + List columns = resolveColumns(request); + if (columns == null) { + throw new BusinessException("GET_TABLE_COLUMNS_ERROR", new Object[]{"获取表列信息失败"}); + } + + ForeignKeyValueProvider foreignKeyValueProvider = buildForeignKeyValueProvider(request); + List> previewData = generateDataRows(request, columns, foreignKeyValueProvider, 10); + + DataGenerationPreviewVO previewVO = new DataGenerationPreviewVO(); + previewVO.setTableName(request.getTableName()); + previewVO.setPreviewData(previewData); + + List columnInfos = new ArrayList<>(); + for (ColumnConfigParam column : columns) { + DataGenerationPreviewVO.ColumnInfo columnInfo = new DataGenerationPreviewVO.ColumnInfo(); + columnInfo.setColumnName(column.getColumnName()); + columnInfo.setDataType(column.getDataType()); + columnInfo.setComment(column.getComment()); + columnInfos.add(columnInfo); + } + previewVO.setColumns(columnInfos); + + return previewVO; + } catch (Exception e) { + log.error("Failed to generate preview", e); + throw new BusinessException("GENERATE_PREVIEW_ERROR", new Object[]{"生成预览失败: " + e.getMessage()}); + } + } + + @Override + public Long executeDataGeneration(DataGenerationRequest request) { + try { + saveConfigs(request); + + List columns = resolveColumns(request); + if (columns == null) { + throw new BusinessException("GET_TABLE_COLUMNS_ERROR", new Object[]{"获取表列信息失败"}); + } + + TaskCreateParam taskParam = new TaskCreateParam(); + taskParam.setDataSourceId(request.getDataSourceId()); + taskParam.setDatabaseName(request.getDatabaseName()); + taskParam.setSchemaName(request.getSchemaName()); + taskParam.setTableName(request.getTableName()); + taskParam.setTaskType(TaskTypeEnum.GENERATE_TABLE_DATA.name()); + taskParam.setTaskName("数据生成 - " + request.getTableName()); + taskParam.setTaskProgress("0"); + + Long taskId = taskService.create(taskParam); + if (taskId == null) { + throw new BusinessException("CREATE_TASK_ERROR", new Object[]{"创建任务失败"}); + } + + LoginUser loginUser = ContextUtils.getLoginUser(); + ConnectInfo connectInfo = Chat2DBContext.getConnectInfo().copy(); + + CompletableFuture.runAsync(() -> { + buildContext(loginUser, connectInfo); + try { + executeDataGenerationAsync(taskId, request); + } finally { + removeContext(); + } + }); + + return taskId; + } catch (Exception e) { + log.error("Failed to execute data generation", e); + throw new BusinessException("EXECUTE_GENERATION_ERROR", new Object[]{"执行数据生成失败: " + e.getMessage()}); + } + } + + @Override + public List getAllGeneratorTemplates() { + return GeneratorTemplate.getDefaultTemplates(); + } + + private void saveConfigs(DataGenerationRequest request) { + if (request.getColumnConfigs() == null || request.getColumnConfigs().isEmpty()) { + return; + } + try { + ruleService.saveColumnConfigs( + request.getDataSourceId(), request.getDatabaseName(), request.getSchemaName(), + request.getTableName(), 0L, request.getColumnConfigs(), request.getRowCount()); + } catch (Exception e) { + log.warn("Failed to save generation configs, but continuing operation", e); + } + } + + private List resolveColumns(DataGenerationRequest request) { + List result = getTableColumns(request); + if (result == null || result.isEmpty()) { + return null; + } + List dbColumns = result; + + if (request.getColumnConfigs() != null && !request.getColumnConfigs().isEmpty()) { + Map expressionMap = request.getColumnConfigs().stream() + .filter(c -> c.getExpression() != null) + .collect(Collectors.toMap(ColumnConfigParam::getColumnName, ColumnConfigParam::getExpression)); + + for (ColumnConfigParam col : dbColumns) { + String userExpression = expressionMap.get(col.getColumnName()); + if (userExpression != null) { + col.setExpression(userExpression); + } + } + } + + return dbColumns; + } + + private List loadForeignKeys(DataGenerationRequest request, boolean syncRealForeignKeys) { + if (syncRealForeignKeys) { + foreignKeySyncService.syncForeignKeys(request.getDataSourceId(), request.getDatabaseName(), + request.getSchemaName(), request.getTableName()); + } + List foreignKeys = foreignKeySyncService.listAllForeignKeys(request.getDataSourceId(), + request.getDatabaseName(), request.getSchemaName(), request.getTableName()); + if (foreignKeys == null || foreignKeys.isEmpty()) { + return Collections.emptyList(); + } + return foreignKeys.stream() + .filter(fk -> sameName(fk.getTableName(), request.getTableName())) + .filter(fk -> fk.getColumn() != null && fk.getReferencedTable() != null + && fk.getReferencedColumn() != null) + .collect(Collectors.toList()); + } + + private Map buildForeignKeyMap(List foreignKeys) { + Map result = new HashMap<>(); + for (ForeignKey foreignKey : foreignKeys) { + ForeignKey existing = result.get(foreignKey.getColumn()); + if (existing == null || SOURCE_TYPE_VIRTUAL.equals(getForeignKeySourceType(existing))) { + result.put(foreignKey.getColumn(), foreignKey); + } + } + return result; + } + + private void applyForeignKeyInfo(ColumnConfigParam column, ForeignKey foreignKey) { + if (foreignKey == null) { + column.setForeignKey(false); + return; + } + column.setForeignKey(true); + column.setForeignKeySourceType(getForeignKeySourceType(foreignKey)); + column.setReferencedTable(foreignKey.getReferencedTable()); + column.setReferencedColumnName(foreignKey.getReferencedColumn()); + } + + private ForeignKeyValueProvider buildForeignKeyValueProvider(DataGenerationRequest request) { + List foreignKeys = loadForeignKeys(request, false); + if (foreignKeys.isEmpty()) { + return ForeignKeyValueProvider.empty(); + } + + Map> groupedForeignKeys = foreignKeys.stream() + .collect(Collectors.groupingBy(fk -> buildForeignKeyGroupKey(request, fk), LinkedHashMap::new, + Collectors.toList())); + Map columnGroupMap = new HashMap<>(); + for (List groupForeignKeys : groupedForeignKeys.values()) { + ForeignKeyValueGroup valueGroup = buildForeignKeyValueGroup(request, groupForeignKeys); + for (String columnName : valueGroup.childColumns()) { + columnGroupMap.put(columnName, valueGroup); + } + } + return new ForeignKeyValueProvider(columnGroupMap); + } + + private ForeignKeyValueGroup buildForeignKeyValueGroup(DataGenerationRequest request, List foreignKeys) { + ForeignKey first = foreignKeys.get(0); + MetaData metaData = Chat2DBContext.getMetaData(); + String tableName = buildQualifiedTableName(first.getReferencedTable(), + first.getDatabaseName() != null ? first.getDatabaseName() : request.getDatabaseName(), + first.getSchemaName() != null ? first.getSchemaName() : request.getSchemaName(), metaData); + + List referencedColumns = foreignKeys.stream() + .map(ForeignKey::getReferencedColumn) + .collect(Collectors.toList()); + List childColumns = foreignKeys.stream() + .map(ForeignKey::getColumn) + .collect(Collectors.toList()); + + StringBuilder sql = new StringBuilder("SELECT DISTINCT "); + for (int i = 0; i < referencedColumns.size(); i++) { + if (i > 0) { + sql.append(", "); + } + sql.append(metaData.getMetaDataName(referencedColumns.get(i))); + } + sql.append(" FROM ").append(tableName).append(" WHERE "); + for (int i = 0; i < referencedColumns.size(); i++) { + if (i > 0) { + sql.append(" AND "); + } + sql.append(metaData.getMetaDataName(referencedColumns.get(i))).append(" IS NOT NULL"); + } + + List> referencedRows = queryReferencedRows(sql.toString(), referencedColumns.size()); + if (referencedRows.isEmpty()) { + throw new RuntimeException("外键引用表无可用数据:" + buildForeignKeyRelationText(first)); + } + return new ForeignKeyValueGroup(childColumns, referencedRows); + } + + private List> queryReferencedRows(String sql, int columnCount) { + List> rows = new ArrayList<>(); + try (Statement statement = Chat2DBContext.getConnection().createStatement()) { + statement.setMaxRows(MAX_REFERENCED_VALUE_ROWS); + try (ResultSet resultSet = statement.executeQuery(sql)) { + while (resultSet.next()) { + List row = new ArrayList<>(); + for (int i = 1; i <= columnCount; i++) { + row.add(resultSet.getObject(i)); + } + rows.add(row); + } + } + } catch (SQLException e) { + throw new RuntimeException("查询外键引用数据失败:" + e.getMessage(), e); + } + return rows; + } + + private String buildForeignKeyGroupKey(DataGenerationRequest request, ForeignKey foreignKey) { + String foreignKeyName = foreignKey.getName(); + if (foreignKeyName == null || foreignKeyName.isBlank()) { + foreignKeyName = foreignKey.getColumn() + "->" + foreignKey.getReferencedColumn(); + } + return String.join("|", + getForeignKeySourceType(foreignKey), + nullToEmpty(foreignKeyName), + nullToEmpty(foreignKey.getReferencedTable()), + nullToEmpty(foreignKey.getDatabaseName() != null ? foreignKey.getDatabaseName() : request.getDatabaseName()), + nullToEmpty(foreignKey.getSchemaName() != null ? foreignKey.getSchemaName() : request.getSchemaName()) + ); + } + + private String buildForeignKeyRelationText(ForeignKey foreignKey) { + return foreignKey.getTableName() + "." + foreignKey.getColumn() + + " -> " + foreignKey.getReferencedTable() + "." + foreignKey.getReferencedColumn(); + } + + private String getForeignKeySourceType(ForeignKey foreignKey) { + return foreignKey instanceof VirtualForeignKey ? SOURCE_TYPE_VIRTUAL : SOURCE_TYPE_REAL; + } + + private String buildQualifiedTableName(String tableName, String databaseName, String schemaName, MetaData metaData) { + String qualifiedName = metaData.getMetaDataName(tableName); + if (schemaName != null && !schemaName.isBlank()) { + qualifiedName = metaData.getMetaDataName(schemaName) + "." + qualifiedName; + } + if (databaseName != null && !databaseName.isBlank()) { + qualifiedName = metaData.getMetaDataName(databaseName) + "." + qualifiedName; + } + return qualifiedName; + } + + private boolean sameName(String left, String right) { + return left != null && right != null && left.equalsIgnoreCase(right); + } + + private String nullToEmpty(String value) { + return value == null ? "" : value; + } + + private List> generateDataRows(DataGenerationRequest request, + List columns, + ForeignKeyValueProvider foreignKeyValueProvider, + int rowCount) { + List> dataRows = new ArrayList<>(); + Faker faker = new Faker(LocaleContextHolder.getLocale()); + + for (int i = 0; i < rowCount; i++) { + Map row = new LinkedHashMap<>(); + Map> foreignKeyRowCache = new HashMap<>(); + for (ColumnConfigParam column : columns) { + if (Boolean.TRUE.equals(column.getAutoIncrement())) { + continue; + } + if (foreignKeyValueProvider.hasForeignKeyValue(column.getColumnName())) { + row.put(column.getColumnName(), foreignKeyValueProvider.nextValue(column.getColumnName(), + foreignKeyRowCache)); + continue; + } + String expression = column.getExpression(); + + ExpressionDataGenerator.ColumnGenerationConfig config = + new ExpressionDataGenerator.ColumnGenerationConfig( + column.getColumnName(), + column.getDataType(), + column.getNullable(), + column.getMaxLength(), + column.getScale() + ); + + Object value = expressionDataGenerator.generate(faker, expression, config); + row.put(column.getColumnName(), value); + } + dataRows.add(row); + } + + return dataRows; + } + + private void executeDataGenerationAsync(Long taskId, DataGenerationRequest request) { + Exception error = null; + try { + updateTaskProgress(taskId, TaskStatusEnum.PROCESSING, 0); + + List columns = resolveColumns(request); + if (columns == null) { + throw new RuntimeException("获取表列信息失败"); + } + ForeignKeyValueProvider foreignKeyValueProvider = buildForeignKeyValueProvider(request); + + int totalRows = request.getRowCount() != null ? request.getRowCount() : 100; + int batchSize = request.getBatchSize() != null ? request.getBatchSize() : 1000; + int processedRows = 0; + + while (processedRows < totalRows) { + int currentBatchSize = Math.min(batchSize, totalRows - processedRows); + List> batchData = generateDataRows(request, columns, foreignKeyValueProvider, + currentBatchSize); + insertBatchData(request, batchData); + processedRows += currentBatchSize; + + int progress = (processedRows * 100) / totalRows; + updateTaskProgress(taskId, TaskStatusEnum.PROCESSING, progress); + } + + log.info("Data generation completed successfully for table: {}", request.getTableName()); + } catch (Exception e) { + log.error("Data generation failed for table: " + request.getTableName(), e); + error = e; + } finally { + updateGenerationStatus(taskId, error); + } + } + + private void updateGenerationStatus(Long taskId, Exception throwable) { + try { + TaskUpdateParam updateParam = new TaskUpdateParam(); + updateParam.setId(taskId); + if (throwable != null) { + updateParam.setTaskStatus(TaskStatusEnum.ERROR.name()); + if (throwable instanceof BusinessException businessException) { + updateParam.setContent(I18nUtils.getMessage(businessException.getCode(), businessException.getArgs())); + } else { + updateParam.setContent(throwable.getMessage()); + } + } else { + updateParam.setTaskStatus(TaskStatusEnum.FINISH.name()); + updateParam.setTaskProgress("100"); + } + taskService.updateStatus(updateParam); + } catch (Exception e) { + log.error("Failed to update generation status", e); + } + } + + private void insertBatchData(DataGenerationRequest request, List> batchData) { + if (batchData == null || batchData.isEmpty()) { + return; + } + + List columnNames = new ArrayList<>(batchData.get(0).keySet()); + if (columnNames.isEmpty()) { + return; + } + + MetaData metaData = Chat2DBContext.getMetaData(); + StringBuilder sql = new StringBuilder("INSERT INTO "); + sql.append(buildTableName(request, metaData)).append(" ("); + for (int i = 0; i < columnNames.size(); i++) { + if (i > 0) sql.append(", "); + sql.append(metaData.getMetaDataName(columnNames.get(i))); + } + sql.append(") VALUES ("); + for (int i = 0; i < columnNames.size(); i++) { + if (i > 0) sql.append(", "); + sql.append("?"); + } + sql.append(")"); + + Connection connection = Chat2DBContext.getConnection(); + try (PreparedStatement ps = connection.prepareStatement(sql.toString())) { + boolean originalAutoCommit = connection.getAutoCommit(); + connection.setAutoCommit(false); + try { + for (Map row : batchData) { + for (int i = 0; i < columnNames.size(); i++) { + ps.setObject(i + 1, row.get(columnNames.get(i))); + } + ps.addBatch(); + } + ps.executeBatch(); + connection.commit(); + } catch (SQLException e) { + connection.rollback(); + log.error("Batch insert error, SQL: {}", sql, e); + throw new BusinessException("dataGeneration.batchInsertFailed", new Object[]{e.getMessage()}, e); + } finally { + connection.setAutoCommit(originalAutoCommit); + } + } catch (SQLException e) { + log.error("Batch insert error, SQL: {}", sql, e); + throw new BusinessException("dataGeneration.batchInsertFailed", new Object[]{e.getMessage()}, e); + } + } + + private static class ForeignKeyValueProvider { + + private final Map columnGroupMap; + + private final Random random = new Random(); + + private ForeignKeyValueProvider(Map columnGroupMap) { + this.columnGroupMap = columnGroupMap; + } + + private static ForeignKeyValueProvider empty() { + return new ForeignKeyValueProvider(Collections.emptyMap()); + } + + private boolean hasForeignKeyValue(String columnName) { + return columnGroupMap.containsKey(columnName); + } + + private Object nextValue(String columnName, Map> rowCache) { + ForeignKeyValueGroup group = columnGroupMap.get(columnName); + List values = rowCache.computeIfAbsent(group, key -> key.nextValues(random)); + int index = group.childColumns().indexOf(columnName); + return values.get(index); + } + } + + private record ForeignKeyValueGroup(List childColumns, List> referencedRows) { + + private List nextValues(Random random) { + return referencedRows.get(random.nextInt(referencedRows.size())); + } + } + + private String buildTableName(DataGenerationRequest request, MetaData metaData) { + return buildQualifiedTableName(request.getTableName(), request.getDatabaseName(), request.getSchemaName(), + metaData); + } + + private void updateTaskProgress(Long taskId, TaskStatusEnum status, int progress) { + try { + TaskUpdateParam updateParam = new TaskUpdateParam(); + updateParam.setId(taskId); + updateParam.setTaskStatus(status.name()); + updateParam.setTaskProgress(String.valueOf(progress)); + taskService.updateStatus(updateParam); + } catch (Exception e) { + log.error("Failed to update task progress", e); + } + } + + private void removeContext() { + Dbutils.removeSession(); + ContextUtils.removeContext(); + Chat2DBContext.removeContext(); + } + + private void buildContext(LoginUser loginUser, ConnectInfo connectInfo) { + ContextUtils.setContext(Context.builder() + .loginUser(loginUser) + .build()); + Dbutils.setSession(); + Chat2DBContext.putContext(connectInfo); + } +} + diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataSourceAccessBusinessServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataSourceAccessBusinessServiceImpl.java index d2909f030..09438fa94 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataSourceAccessBusinessServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataSourceAccessBusinessServiceImpl.java @@ -32,12 +32,12 @@ private DataSourceAccessCustomMapper getMapper() { return Dbutils.getMapper(DataSourceAccessCustomMapper.class); } @Override - public ActionResult checkPermission(@NotNull DataSource dataSource) { + public void checkPermission(@NotNull DataSource dataSource) { LoginUser loginUser = ContextUtils.getLoginUser(); // private if (DataSourceKindEnum.PRIVATE.getCode().equals(dataSource.getKind())) { if (loginUser.getId().equals(dataSource.getUserId())) { - return ActionResult.isSuccess(); + return; } else { throw new PermissionDeniedBusinessException(); } @@ -45,7 +45,7 @@ public ActionResult checkPermission(@NotNull DataSource dataSource) { // Administrators can edit anything if (loginUser.getAdmin()) { - return ActionResult.isSuccess(); + return; } // Verify if user have permission @@ -54,14 +54,13 @@ public ActionResult checkPermission(@NotNull DataSource dataSource) { dataSourceAccessPageQueryParam.setAccessObjectType(AccessObjectTypeEnum.USER.getCode()); dataSourceAccessPageQueryParam.setAccessObjectId(loginUser.getId()); dataSourceAccessPageQueryParam.queryOne(); - if (dataSourceAccessService.pageQuery(dataSourceAccessPageQueryParam, null).hasData()) { - return ActionResult.isSuccess(); + if (dataSourceAccessService.pageQuery(dataSourceAccessPageQueryParam, null).isNotEmpty()) { + return; } // Verify if the team has permission if (getMapper().checkTeamPermission(dataSource.getId(), loginUser.getId()) != null) { - return ActionResult.isSuccess(); - + return; } throw new PermissionDeniedBusinessException(); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataSourceAccessServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataSourceAccessServiceImpl.java index bc2e5fa61..a0c90bde1 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataSourceAccessServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataSourceAccessServiceImpl.java @@ -24,6 +24,7 @@ import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; +import ai.chat2db.server.tools.base.wrapper.ServicePage; import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.server.tools.common.util.EasyCollectionUtils; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; @@ -64,7 +65,7 @@ private DataSourceAccessMapper getAccessMapper() { private TeamService teamService; @Override - public PageResult pageQuery(DataSourceAccessPageQueryParam param, DataSourceAccessSelector selector) { + public ServicePage pageQuery(DataSourceAccessPageQueryParam param, DataSourceAccessSelector selector) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(DataSourceAccessDO::getDataSourceId, param.getDataSourceId()) .eq(DataSourceAccessDO::getAccessObjectType, param.getAccessObjectType()) @@ -79,11 +80,11 @@ public PageResult pageQuery(DataSourceAccessPageQueryParam par fillData(list, selector); - return PageResult.of(list, iPage.getTotal(), param); + return ServicePage.of(list, iPage.getTotal(), param.getPageNo(), param.getPageSize()); } @Override - public PageResult comprehensivePageQuery(DataSourceAccessComprehensivePageQueryParam param, + public ServicePage comprehensivePageQuery(DataSourceAccessComprehensivePageQueryParam param, DataSourceAccessSelector selector) { Page page = new Page<>(param.getPageNo(), param.getPageSize()); page.setSearchCount(param.getEnableReturnCount()); @@ -96,21 +97,21 @@ public PageResult comprehensivePageQuery(DataSourceAccessCompr fillData(list, selector); - return PageResult.of(list, iPage.getTotal(), param); + return ServicePage.of(list, iPage.getTotal(), param.getPageNo(), param.getPageSize()); } @Override - public DataResult create(DataSourceAccessCreatParam param) { + public Long create(DataSourceAccessCreatParam param) { DataSourceAccessDO data = dataSourceAccessConverter.param2do(param, ContextUtils.getUserId()); getAccessMapper().insert(data); - return DataResult.of(data.getId()); + return data.getId(); } @Override - public ActionResult delete(Long id) { + public void delete(Long id) { getAccessMapper().deleteById(id); - return ActionResult.isSuccess(); + } private void fillData(List list, DataSourceAccessSelector selector) { @@ -144,9 +145,9 @@ private void fillAccessObject(List list, DataSourceAccessSelec userIdList.add(data.getAccessObjectId()); } } - List userList = userService.listQuery(userIdList).getData(); + List userList = userService.listQuery(userIdList); Map userMap = EasyCollectionUtils.toIdentityMap(userList, User::getId); - List teamList = teamService.listQuery(teamIdList).getData(); + List teamList = teamService.listQuery(teamIdList); Map teamMap = EasyCollectionUtils.toIdentityMap(teamList, Team::getId); for (DataSourceAccess data : list) { DataSourceAccessObject dataSourceAccessObject = data.getAccessObject(); diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataSourceServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataSourceServiceImpl.java index 79313e7bf..fce043feb 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataSourceServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataSourceServiceImpl.java @@ -10,6 +10,7 @@ import ai.chat2db.server.domain.api.param.datasource.DataSourcePageQueryParam; import ai.chat2db.server.domain.api.param.datasource.DataSourcePreConnectParam; import ai.chat2db.server.domain.api.param.datasource.DataSourceSelector; +import ai.chat2db.server.domain.api.param.datasource.DataSourceSortUpdateParam; import ai.chat2db.server.domain.api.param.datasource.DataSourceTestParam; import ai.chat2db.server.domain.api.param.datasource.DataSourceUpdateParam; import ai.chat2db.server.domain.api.param.datasource.DatabaseQueryAllParam; @@ -21,14 +22,20 @@ import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.domain.repository.entity.DataSourceAccessDO; import ai.chat2db.server.domain.repository.entity.DataSourceDO; +import ai.chat2db.server.domain.repository.entity.DataSourceSortDO; import ai.chat2db.server.domain.repository.mapper.DataSourceAccessMapper; import ai.chat2db.server.domain.repository.mapper.DataSourceCustomMapper; import ai.chat2db.server.domain.repository.mapper.DataSourceMapper; +import ai.chat2db.server.domain.repository.mapper.DataSourceSortMapper; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; +import ai.chat2db.plugin.redis.RedisConnectionProvider; +import ai.chat2db.server.tools.base.enums.DataSourceTypeEnum; +import ai.chat2db.server.tools.base.wrapper.ServicePage; import ai.chat2db.server.tools.common.exception.DataNotFoundException; +import ai.chat2db.server.tools.base.excption.BusinessException; import ai.chat2db.server.tools.common.exception.ParamBusinessException; import ai.chat2db.server.tools.common.exception.PermissionDeniedBusinessException; import ai.chat2db.server.tools.common.model.LoginUser; @@ -42,6 +49,7 @@ import ai.chat2db.spi.model.Database; import ai.chat2db.spi.model.KeyValue; import ai.chat2db.spi.sql.Chat2DBContext; +import ai.chat2db.spi.sql.ConnectInfo; import ai.chat2db.spi.sql.IDriverManager; import ai.chat2db.spi.sql.SQLExecutor; import ai.chat2db.spi.util.JdbcUtils; @@ -88,8 +96,12 @@ private DataSourceAccessMapper getAccessMapper() { return Dbutils.getMapper(DataSourceAccessMapper.class); } + private DataSourceSortMapper getSortMapper() { + return Dbutils.getMapper(DataSourceSortMapper.class); + } + @Override - public DataResult createWithPermission(DataSourceCreateParam param) { + public Long createWithPermission(DataSourceCreateParam param) { DataSourceKindEnum dataSourceKind = EasyEnumUtils.getEnum(DataSourceKindEnum.class, param.getKind()); if (dataSourceKind == null) { throw new ParamBusinessException("kind"); @@ -106,13 +118,15 @@ public DataResult createWithPermission(DataSourceCreateParam param) { getMapper().insert(dataSourceDO); preWarmingData(dataSourceDO.getId()); - return DataResult.of(dataSourceDO.getId()); + return dataSourceDO.getId(); } private void preWarmingData(Long dataSourceId) { - DataResult dataResult = queryById(dataSourceId); - if (dataResult.success() && dataResult.getData() != null) { - DataSource dataSource = dataResult.getData(); + DataSource dataSource = queryById(dataSourceId); + if (dataSource != null) { + if (DataSourceTypeEnum.REDIS.getCode().equals(dataSource.getType())) { + return; + } DriverConfig driverConfig = dataSource.getDriverConfig(); if (driverConfig == null || StringUtils.isBlank(driverConfig.getJdbcDriver())) { return; @@ -132,21 +146,21 @@ private void preWarmingData(Long dataSourceId) { } @Override - public DataResult updateWithPermission(DataSourceUpdateParam param) { - DataSource dataSource = queryExistent(param.getId(), null).getData(); + public Long updateWithPermission(DataSourceUpdateParam param) { + DataSource dataSource = queryExistent(param.getId(), null); PermissionUtils.checkOperationPermission(dataSource.getUserId()); JdbcUtils.removePropertySameAsDefault(param.getDriverConfig()); DataSourceDO dataSourceDO = dataSourceConverter.param2do(param); dataSourceDO.setGmtModified(DateUtil.date()); getMapper().updateById(dataSourceDO); - return DataResult.of(dataSourceDO.getId()); + return dataSourceDO.getId(); } @Override - public ActionResult deleteWithPermission(Long id) { + public void deleteWithPermission(Long id) { - DataSource dataSource = queryExistent(id, null).getData(); + DataSource dataSource = queryExistent(id, null); PermissionUtils.checkOperationPermission(dataSource.getUserId()); getMapper().deleteById(id); @@ -155,30 +169,30 @@ public ActionResult deleteWithPermission(Long id) { dataSourceAccessQueryWrapper.eq(DataSourceAccessDO::getDataSourceId, id) ; getAccessMapper().delete(dataSourceAccessQueryWrapper); - return ActionResult.isSuccess(); + } @Override - public DataResult queryById(Long id) { + public DataSource queryById(Long id) { DataSourceDO dataSourceDO = getMapper().selectById(id); - return DataResult.of(dataSourceConverter.do2dto(dataSourceDO)); + return dataSourceConverter.do2dto(dataSourceDO); } @Override - public DataResult queryExistent(Long id, DataSourceSelector selector) { - DataResult dataResult = queryById(id); - if (dataResult.getData() == null) { + public DataSource queryExistent(Long id, DataSourceSelector selector) { + DataSource dataSource = queryById(id); + if (dataSource == null) { throw new DataNotFoundException(); } - fillData(Lists.newArrayList(dataResult.getData()), selector); + fillData(Lists.newArrayList(dataSource), selector); - return dataResult; + return dataSource; } @Override - public DataResult copyByIdWithPermission(Long id) { - DataSource dataSource = queryExistent(id, null).getData(); + public Long copyByIdWithPermission(Long id) { + DataSource dataSource = queryExistent(id, null); PermissionUtils.checkOperationPermission(dataSource.getUserId()); DataSourceDO dataSourceDO = getMapper().selectById(id); @@ -188,11 +202,33 @@ public DataResult copyByIdWithPermission(Long id) { dataSourceDO.setGmtCreate(DateUtil.date()); dataSourceDO.setGmtModified(DateUtil.date()); getMapper().insert(dataSourceDO); - return DataResult.of(dataSourceDO.getId()); + return dataSourceDO.getId(); + } + + @Override + public void updateSortWithPermission(DataSourceSortUpdateParam param) { + if (param == null || CollectionUtils.isEmpty(param.getIdList())) { + return; + } + + Long userId = ContextUtils.getUserId(); + LambdaQueryWrapper deleteWrapper = new LambdaQueryWrapper<>(); + deleteWrapper.eq(DataSourceSortDO::getUserId, userId); + getSortMapper().delete(deleteWrapper); + + for (int index = 0; index < param.getIdList().size(); index++) { + DataSourceSortDO sortDO = new DataSourceSortDO(); + sortDO.setGmtCreate(DateUtil.date()); + sortDO.setGmtModified(DateUtil.date()); + sortDO.setUserId(userId); + sortDO.setDataSourceId(param.getIdList().get(index)); + sortDO.setSort(index); + getSortMapper().insert(sortDO); + } } @Override - public PageResult queryPage(DataSourcePageQueryParam param, DataSourceSelector selector) { + public ServicePage queryPage(DataSourcePageQueryParam param, DataSourceSelector selector) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); if (StringUtils.isNotBlank(param.getSearchKey())) { queryWrapper.and(wrapper -> wrapper.like(DataSourceDO::getAlias, "%" + param.getSearchKey() + "%") @@ -207,11 +243,11 @@ public PageResult queryPage(DataSourcePageQueryParam param, DataSour fillData(dataSources, selector); - return PageResult.of(dataSources, iPage.getTotal(), param); + return ServicePage.of(dataSources, iPage.getTotal(), param.getPageNo(), param.getPageSize()); } @Override - public PageResult queryPageWithPermission(DataSourcePageQueryParam param, DataSourceSelector selector) { + public ServicePage queryPageWithPermission(DataSourcePageQueryParam param, DataSourceSelector selector) { LoginUser loginUser = ContextUtils.getLoginUser(); IPage iPage = getCustomMapper().selectPageWithPermission( @@ -223,29 +259,36 @@ public PageResult queryPageWithPermission(DataSourcePageQueryParam p fillData(dataSources, selector); - return PageResult.of(dataSources, iPage.getTotal(), param); + return ServicePage.of(dataSources, iPage.getTotal(), param.getPageNo(), param.getPageSize()); } @Override - public ListResult queryByIds(List ids) { - return listQuery(ids, null); - } - - @Override - public ListResult listQuery(List idList, DataSourceSelector selector) { + public List listQuery(List idList, DataSourceSelector selector) { if (CollectionUtils.isEmpty(idList)) { - return ListResult.empty(); + return java.util.Collections.emptyList(); } List dataList = getMapper().selectBatchIds(idList); List list = dataSourceConverter.do2dto(dataList); fillData(list, selector); - return ListResult.of(list); + return list; } @Override - public ActionResult preConnect(DataSourcePreConnectParam param) { + public void preConnect(DataSourcePreConnectParam param) { + if (DataSourceTypeEnum.REDIS.getCode().equals(param.getType())) { + try { + RedisConnectionProvider.testConnect(toRedisConnectInfo(param)); + } catch (Exception e) { + Throwable t = e; + while (t.getCause() != null) { + t = t.getCause(); + } + throw new BusinessException("CONNECT_ERROR", new Object[]{t.getMessage()}); + } + return; + } DataSourceTestParam testParam = dataSourceConverter.param2param(param); DriverConfig driverConfig = testParam.getDriverConfig(); @@ -257,25 +300,46 @@ public ActionResult preConnect(DataSourcePreConnectParam param) { testParam.getUsername(), testParam.getPassword(), testParam.getDbType(), driverConfig, param.getSsh(), KeyValue.toMap(param.getExtendInfo())); if (BooleanUtils.isNotTrue(dataSourceConnect.getSuccess())) { - return ActionResult.fail(dataSourceConnect.getMessage(), dataSourceConnect.getDescription(), - dataSourceConnect.getErrorDetail()); + throw new BusinessException("CONNECT_ERROR", new Object[]{dataSourceConnect.getMessage()}); + } + + } + + private ConnectInfo toRedisConnectInfo(DataSourcePreConnectParam param) { + ConnectInfo connectInfo = new ConnectInfo(); + connectInfo.setUrl(param.getUrl()); + connectInfo.setHost(param.getHost()); + if (StringUtils.isNotBlank(param.getPort())) { + connectInfo.setPort(Integer.valueOf(param.getPort())); } - return ActionResult.isSuccess(); + connectInfo.setUser(param.getUser()); + connectInfo.setPassword(param.getPassword()); + connectInfo.setDbType(param.getType()); + connectInfo.setSsh(param.getSsh()); + connectInfo.setSsl(param.getSsl()); + connectInfo.setExtendInfo(param.getExtendInfo()); + connectInfo.setDriverConfig(param.getDriverConfig()); + return connectInfo; } @Override - public ListResult connect(Long id) { + public List connect(Long id) { DatabaseQueryAllParam queryAllParam = new DatabaseQueryAllParam(); queryAllParam.setDataSourceId(id); - List databases = Chat2DBContext.getMetaData().databases(Chat2DBContext.getConnection()); - return ListResult.of(databases); + List databases; + if (DataSourceTypeEnum.REDIS.getCode().equals(Chat2DBContext.getConnectInfo().getDbType())) { + databases = Chat2DBContext.getMetaData().databases(null); + } else { + databases = Chat2DBContext.getMetaData().databases(Chat2DBContext.getConnection()); + } + return databases; } @Override - public ActionResult close(Long id) { + public void close(Long id) { DataSourceCloseParam closeParam = new DataSourceCloseParam(); closeParam.setDataSourceId(id); - return ActionResult.isSuccess(); + } private void fillData(List list, DataSourceSelector selector) { @@ -313,4 +377,18 @@ private void fillEnvironment(List list, DataSourceSelector selector) environmentConverter.fillDetail(EasyCollectionUtils.toList(list, DataSource::getEnvironment)); } + @Override + public String queryDatabaseType(Long dataSourceId) { + try { + DataSourceMapper mapper = getMapper(); + DataSourceDO dataSourceDO = mapper.selectById(dataSourceId); + if (dataSourceDO != null && StringUtils.isNotBlank(dataSourceDO.getType())) { + return dataSourceDO.getType(); + } + } catch (Exception e) { + log.error("query database type error, dataSourceId:{}", dataSourceId, e); + } + return "MYSQL"; + } + } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DatabaseServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DatabaseServiceImpl.java index 1c3677b64..9b9c6d988 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DatabaseServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DatabaseServiceImpl.java @@ -11,24 +11,28 @@ import ai.chat2db.server.domain.api.param.MetaDataQueryParam; import ai.chat2db.server.domain.api.param.SchemaOperationParam; import ai.chat2db.server.domain.api.param.SchemaQueryParam; +import ai.chat2db.server.domain.api.param.TablePageQueryParam; +import ai.chat2db.server.domain.api.param.TableSelector; import ai.chat2db.server.domain.api.service.DatabaseService; +import ai.chat2db.server.domain.api.service.TableService; import ai.chat2db.server.domain.core.cache.CacheManage; -import ai.chat2db.server.tools.base.wrapper.result.ActionResult; -import ai.chat2db.server.tools.base.wrapper.result.DataResult; -import ai.chat2db.server.tools.base.wrapper.result.ListResult; +import ai.chat2db.server.domain.core.cache.LuceneIndexManager; +import ai.chat2db.server.domain.core.cache.LuceneIndexManagerFactory; +import ai.chat2db.server.tools.base.enums.DataSourceTypeEnum; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.SqlBuilder; -import ai.chat2db.spi.model.Database; -import ai.chat2db.spi.model.MetaSchema; -import ai.chat2db.spi.model.Schema; -import ai.chat2db.spi.model.Sql; +import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.Chat2DBContext; import cn.hutool.core.thread.ThreadUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; +import java.util.ArrayList; +import java.util.stream.Collectors; + import static ai.chat2db.server.domain.core.cache.CacheKey.getDataBasesKey; import static ai.chat2db.server.domain.core.cache.CacheKey.getDataSourceKey; import static ai.chat2db.server.domain.core.cache.CacheKey.getSchemasKey; @@ -42,14 +46,30 @@ @Service public class DatabaseServiceImpl implements DatabaseService { + @Autowired + private TableService tableService; + + @Autowired + private LuceneIndexManagerFactory luceneIndexManagerFactory; + @Override - public ListResult queryAll(DatabaseQueryAllParam param) { + public List queryAll(DatabaseQueryAllParam param) { List databases = CacheManage.getList(getDataBasesKey(param.getDataSourceId()), Database.class, (key) -> param.isRefresh(), - (key) -> getDatabases(param.getDbType(), param.getConnection() == null ? Chat2DBContext.getConnection() - : param.getConnection()) + (key) -> getDatabases(param.getDbType(), getConnection(param.getDbType(), param.getConnection())) ); - return ListResult.of(databases); + return databases; + } + + private Connection getConnection(String dbType, Connection connection) { + if (connection != null) { + return connection; + } + if (DataSourceTypeEnum.REDIS.getCode().equals(dbType) + || DataSourceTypeEnum.REDIS.getCode().equals(Chat2DBContext.getConnectInfo().getDbType())) { + return null; + } + return Chat2DBContext.getConnection(); } private List getDatabases(String dbType, Connection connection) { @@ -57,15 +77,15 @@ private List getDatabases(String dbType, Connection connection) { } @Override - public ListResult querySchema(SchemaQueryParam param) { + public List querySchema(SchemaQueryParam param) { List schemas = CacheManage.getList(getSchemasKey(param.getDataSourceId(), param.getDataBaseName()), Schema.class, (key) -> param.isRefresh(), (key) -> { - Connection connection = param.getConnection() == null ? Chat2DBContext.getConnection() - : param.getConnection(); + Connection connection = getConnection(Chat2DBContext.getConnectInfo().getDbType(), + param.getConnection()); return getSchemaList(param.getDataBaseName(), connection); }); - return ListResult.of(schemas); + return schemas; } @@ -80,6 +100,9 @@ private void sortSchema(List schemas, Connection connection) { if (CollectionUtils.isEmpty(schemas)) { return; } + if (connection == null) { + return; + } String ulr = null; try { ulr = connection.getMetaData().getURL(); @@ -90,7 +113,7 @@ private void sortSchema(List schemas, Connection connection) { int num = -1; for (int i = 0; i < schemas.size(); i++) { String schema = schemas.get(i).getName(); - if (StringUtils.isNotBlank(ulr) && schema!=null && ulr.contains(schema)) { + if (StringUtils.isNotBlank(ulr) && schema != null && ulr.contains(schema)) { num = i; break; } @@ -101,12 +124,12 @@ private void sortSchema(List schemas, Connection connection) { } @Override - public DataResult queryDatabaseSchema(MetaDataQueryParam param) { + public MetaSchema queryDatabaseSchema(MetaDataQueryParam param) { MetaSchema metaSchema = new MetaSchema(); MetaData metaData = Chat2DBContext.getMetaData(); MetaSchema ms = CacheManage.get(getDataSourceKey(param.getDataSourceId()), MetaSchema.class, (key) -> param.isRefresh(), (key) -> { - Connection connection = Chat2DBContext.getConnection(); + Connection connection = getConnection(Chat2DBContext.getConnectInfo().getDbType(), null); List databases = metaData.databases(connection); if (!CollectionUtils.isEmpty(databases)) { CountDownLatch countDownLatch = ThreadUtil.newCountDownLatch(databases.size()); @@ -134,47 +157,188 @@ public DataResult queryDatabaseSchema(MetaDataQueryParam param) { return metaSchema; }); - return DataResult.of(ms); + return ms; + } + + private boolean isRedis() { + return DataSourceTypeEnum.REDIS.getCode().equals(Chat2DBContext.getConnectInfo().getDbType()); } @Override - public ActionResult deleteDatabase(DatabaseCreateParam param) { - Chat2DBContext.getDBManage().dropDatabase(Chat2DBContext.getConnection(), param.getName()); - return ActionResult.isSuccess(); + public void deleteDatabase(DatabaseCreateParam param) { + String databaseName = param.getName(); + Long dataSourceId = Chat2DBContext.getConnectInfo().getDataSourceId(); + + // 1. 执行 DROP DATABASE SQL + Chat2DBContext.getDBManage().dropDatabase(Chat2DBContext.getConnection(), databaseName); + + // 2. 级联删除 Lucene 索引中该数据库相关的所有文档 + try { + LuceneIndexManager luceneMgr = luceneIndexManagerFactory.getManager(dataSourceId); + luceneMgr.deleteByDatabaseAndSchema(databaseName, null); + } catch (Exception e) { + log.warn("Failed to clean Lucene index for database: {}", databaseName, e); + } + + // 3. 清除 EHCache 中该数据库相关的缓存 + CacheManage.remove(getDataBasesKey(dataSourceId)); + CacheManage.remove(getSchemasKey(dataSourceId, databaseName)); } @Override - public DataResult createDatabase(Database database) { + public Sql createDatabase(Database database) { String sql = Chat2DBContext.getSqlBuilder().buildCreateDatabaseSql(database); - return DataResult.of(Sql.builder().sql(sql).build()); + return Sql.builder().sql(sql).build(); } @Override - public ActionResult modifyDatabase(DatabaseCreateParam param) { + public void modifyDatabase(DatabaseCreateParam param) { Chat2DBContext.getDBManage().modifyDatabase(Chat2DBContext.getConnection(), param.getName(), param.getName()); - return ActionResult.isSuccess(); + } @Override - public ActionResult deleteSchema(SchemaOperationParam param) { + public void deleteSchema(SchemaOperationParam param) { Chat2DBContext.getDBManage().dropSchema(Chat2DBContext.getConnection(), param.getDatabaseName(), param.getSchemaName()); - return ActionResult.isSuccess(); + } @Override - public DataResult createSchema(Schema schema) { + public Sql createSchema(Schema schema) { String sql = Chat2DBContext.getSqlBuilder().buildCreateSchemaSql(schema); - return DataResult.of(Sql.builder().sql(sql).build()); + return Sql.builder().sql(sql).build(); } @Override - public ActionResult modifySchema(SchemaOperationParam param) { + public void modifySchema(SchemaOperationParam param) { Chat2DBContext.getDBManage().modifySchema(Chat2DBContext.getConnection(), param.getDatabaseName(), param.getSchemaName(), param.getNewSchemaName()); - return ActionResult.isSuccess(); + + } + + @Override + public String queryTableDdl(Long dataSourceId, String databaseName, String schemaName, String tableName) { + try { + TablePageQueryParam param = new TablePageQueryParam(); + param.setDataSourceId(dataSourceId); + param.setDatabaseName(databaseName); + param.setSchemaName(schemaName); + param.setTableName(tableName); + + TableSelector tableSelector = new TableSelector(); + tableSelector.setColumnList(true); + tableSelector.setIndexList(true); + tableSelector.setForeignKey(true); + + List
tables = tableService.pageQuery(param, tableSelector); + if (!CollectionUtils.isEmpty(tables)) { + SqlBuilder sqlBuilder = Chat2DBContext.getSqlBuilder(); + return sqlBuilder.buildCreateTableSql(tables.get(0)); + } + } catch (Exception e) { + log.error("query table ddl error, tableName:{}", tableName, e); + } + return ""; + } + + @Override + public String buildTableColumn(Long dataSourceId, String databaseName, String schemaName, List tableNames) { + if (CollectionUtils.isEmpty(tableNames)) { + log.error("tableNames is empty"); + return ""; + } + try { + return tableNames.stream() + .map(tableName -> queryTableDdl(dataSourceId, databaseName, schemaName, tableName)) + .filter(StringUtils::isNotBlank) + .collect(Collectors.joining("\n")); + } catch (Exception e) { + log.error("query tables:{} error, do nothing", tableNames); + } + return ""; } -} \ No newline at end of file + @Override + public String queryDatabaseTables(Long dataSourceId, String databaseName, String schemaName) { + try { + TablePageQueryParam queryParam = new TablePageQueryParam(); + queryParam.setDataSourceId(dataSourceId); + queryParam.setDatabaseName(databaseName); + queryParam.setSchemaName(schemaName); + queryParam.queryAll(); + + TableSelector tableSelector = TableSelector.builder() + .indexList(false) + .columnList(true) + .foreignKey(true) + .build(); + + List
tables = tableService.pageQuery(queryParam, tableSelector); + return tables.stream().map(table -> { + StringBuilder sb = new StringBuilder(table.getName()); + String comment = StringUtils.defaultString(table.getComment(), table.getAiComment()); + List foreignKeys = table.getForeignKeyList(); + + List virtualForeignKeys = table.getVirtualForeignKeyList(); + if (StringUtils.isNotEmpty(comment) || !foreignKeys.isEmpty() || !virtualForeignKeys.isEmpty()) { + sb.append("(").append(comment); + + if (!foreignKeys.isEmpty()) { + if (StringUtils.isNotEmpty(comment)) { + sb.append(";"); + } + String foreignKeysString = foreignKeys.stream() + .map(foreignKey -> foreignKey.getColumn() + "->" + + foreignKey.getReferencedTable() + ":" + + foreignKey.getReferencedColumn()) + .collect(Collectors.joining(",")); + + sb.append("foreignKeys:").append(foreignKeysString); + } + if (!virtualForeignKeys.isEmpty()) { + if (StringUtils.isNotEmpty(comment) || !foreignKeys.isEmpty()) { + sb.append(";"); + } + String virtualForeignKeysString = virtualForeignKeys.stream() + .map(virtualForeignKey -> virtualForeignKey.getColumn() + "->" + + virtualForeignKey.getReferencedTable() + ":" + + virtualForeignKey.getReferencedColumn()) + .collect(Collectors.joining(",")); + sb.append("virtualForeignKeys:").append(virtualForeignKeysString); + } + sb.append(")"); + } + return sb.toString(); + }).collect(Collectors.joining(",")); + } catch (Exception e) { + log.error("query table error:{}, do nothing", e.getMessage()); + return ""; + } + } + + @Override + public String queryRedisSchema(Long dataSourceId, String databaseName, String schemaName, List tableNames) { + if (CollectionUtils.isEmpty(tableNames)) { + SchemaQueryParam param = new SchemaQueryParam(); + param.setDataSourceId(dataSourceId); + param.setDataBaseName(databaseName); + + List schemaListResult = querySchema(param); + List keyNames = new ArrayList<>(); + String properties = schemaListResult + .stream() + .peek(schema -> keyNames.add(schema.getName())) + .map(schema -> schema.getName() + ":*(" + schema.getKeyType() + ")") + .collect(Collectors.joining(",")); + return properties; + } + return tableNames.stream() + .map(name -> name + ":*") + .collect(Collectors.joining(",")); + } + + +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DeprecatedTableServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DeprecatedTableServiceImpl.java new file mode 100644 index 000000000..8218d0a05 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DeprecatedTableServiceImpl.java @@ -0,0 +1,81 @@ +package ai.chat2db.server.domain.core.impl; + +import ai.chat2db.server.domain.api.param.DeprecatedTableParam; +import ai.chat2db.server.domain.api.service.DeprecatedTableService; +import ai.chat2db.server.domain.core.converter.DeprecatedTableConverter; +import ai.chat2db.server.domain.repository.Dbutils; +import ai.chat2db.server.domain.repository.entity.DeprecatedTableDO; +import ai.chat2db.server.domain.repository.mapper.DeprecatedTableMapper; +import ai.chat2db.server.tools.base.wrapper.result.ActionResult; +import ai.chat2db.server.tools.common.util.ContextUtils; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@Service +public class DeprecatedTableServiceImpl implements DeprecatedTableService { + + @Autowired + private DeprecatedTableConverter deprecatedTableConverter; + + private DeprecatedTableMapper getMapper() { + return Dbutils.getMapper(DeprecatedTableMapper.class); + } + + @Override + public ActionResult deprecatedTable(DeprecatedTableParam param) { + DeprecatedTableDO entity = deprecatedTableConverter.param2do(param); + entity.setUserId(ContextUtils.getUserId()); + getMapper().insert(entity); + return ActionResult.isSuccess(); + } + + @Override + public ActionResult deleteDeprecatedTable(DeprecatedTableParam param) { + param.setUserId(ContextUtils.getUserId()); + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); + updateWrapper.eq(DeprecatedTableDO::getUserId, param.getUserId()); + updateWrapper.eq(DeprecatedTableDO::getDataSourceId, param.getDataSourceId()); + if (StringUtils.isNotBlank(param.getDatabaseName())) { + updateWrapper.eq(DeprecatedTableDO::getDatabaseName, param.getDatabaseName()); + } + if (StringUtils.isNotBlank(param.getSchemaName())) { + updateWrapper.eq(DeprecatedTableDO::getSchemaName, param.getSchemaName()); + } + if (StringUtils.isNotBlank(param.getTableName())) { + updateWrapper.eq(DeprecatedTableDO::getTableName, param.getTableName()); + } + getMapper().delete(updateWrapper); + return ActionResult.isSuccess(); + } + + @Override + public List queryDeprecatedTables(DeprecatedTableParam param) { + List result = new ArrayList<>(); + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(DeprecatedTableDO::getUserId, param.getUserId()); + queryWrapper.eq(DeprecatedTableDO::getDataSourceId, param.getDataSourceId()); + if (StringUtils.isNotBlank(param.getDatabaseName())) { + queryWrapper.eq(DeprecatedTableDO::getDatabaseName, param.getDatabaseName()); + } + if (StringUtils.isNotBlank(param.getSchemaName())) { + queryWrapper.eq(DeprecatedTableDO::getSchemaName, param.getSchemaName()); + } + if (StringUtils.isNotBlank(param.getTableName())) { + queryWrapper.eq(DeprecatedTableDO::getTableName, param.getTableName()); + } + queryWrapper.orderByDesc(DeprecatedTableDO::getGmtModified); + List list = getMapper().selectList(queryWrapper); + if (!CollectionUtils.isEmpty(list)) { + result = list.stream().map(deprecatedTableDO -> deprecatedTableDO.getTableName()).collect(Collectors.toList()); + } + return result; + } +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java index fb37dc4bd..a125776ba 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java @@ -8,8 +8,6 @@ import ai.chat2db.server.domain.core.converter.CommandConverter; import ai.chat2db.server.domain.core.util.MetaNameUtils; import ai.chat2db.server.tools.base.excption.BusinessException; -import ai.chat2db.server.tools.base.wrapper.result.DataResult; -import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.common.util.EasyCollectionUtils; import ai.chat2db.spi.CommandExecutor; import ai.chat2db.spi.SqlBuilder; @@ -17,7 +15,6 @@ import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.sql.ConnectInfo; -import ai.chat2db.spi.sql.SQLExecutor; import ai.chat2db.spi.util.JdbcUtils; import ai.chat2db.spi.util.SqlUtils; import com.alibaba.druid.DbType; @@ -57,11 +54,10 @@ public class DlTemplateServiceImpl implements DlTemplateService { private CommandConverter commandConverter; @Override - public ListResult execute(DlExecuteParam param) { + public List execute(DlExecuteParam param) { CommandExecutor executor = Chat2DBContext.getMetaData().getCommandExecutor(); Command command = commandConverter.param2model(param); List results = executor.execute(command); - ListResult listResult = ListResult.of(results); for (ExecuteResult executeResult : results) { List
headers = executeResult.getHeaderList(); if (executeResult.getSuccess() && executeResult.isCanEdit() && CollectionUtils.isNotEmpty(headers)) { @@ -69,28 +65,16 @@ public ListResult execute(DlExecuteParam param) { param.getDatabaseName()); executeResult.setHeaderList(headers); } - if (!executeResult.getSuccess()) { - listResult.setSuccess(false); - listResult.errorCode(executeResult.getDescription()); - listResult.setErrorMessage(executeResult.getMessage()); - } addOperationLog(executeResult); } - return listResult; - -// if ("SQLSERVER".equalsIgnoreCase(type)) { -// RemoveSpecialGO(param); -// } - - + return results; } @Override - public DataResult executeUpdate(DlExecuteParam param) { + public ExecuteResult executeUpdate(DlExecuteParam param) { CommandExecutor executor = Chat2DBContext.getMetaData().getCommandExecutor(); - DataResult dataResult = new DataResult<>(); - dataResult.setSuccess(true); - //RemoveSpecialGO(param); + ExecuteResult result = new ExecuteResult(); + result.setSuccess(true); DbType dbType = JdbcUtils.parse2DruidDbType(Chat2DBContext.getConnectInfo().getDbType()); List sqlList = SqlUtils.parse(param.getSql(), dbType); @@ -99,26 +83,25 @@ public DataResult executeUpdate(DlExecuteParam param) { connection.setAutoCommit(false); for (String originalSql : sqlList) { ExecuteResult executeResult = executor.executeUpdate(originalSql, connection, 1); - dataResult.setData(executeResult); + result = executeResult; addOperationLog(executeResult); } connection.commit(); } catch (Exception e) { log.error("executeUpdate error", e); - dataResult.setSuccess(false); - dataResult.setErrorCode("connection error"); - dataResult.setErrorMessage(e.getMessage()); + result.setSuccess(false); + result.setMessage(e.getMessage()); } - return dataResult; + return result; } @Override - public DataResult count(DlCountParam param) { + public Long count(DlCountParam param) { if (StringUtils.isBlank(param.getSql())) { - return DataResult.of(0L); + return 0L; } DbType dbType = JdbcUtils.parse2DruidDbType(Chat2DBContext.getConnectInfo().getDbType()); @@ -145,7 +128,7 @@ public DataResult count(DlCountParam param) { List> dataList = executeResult.getDataList(); if (CollectionUtils.isEmpty(dataList)) { - return DataResult.of(0L); + return 0L; } String count = EasyCollectionUtils.stream(executeResult.getDataList()) .findFirst() @@ -153,22 +136,22 @@ public DataResult count(DlCountParam param) { .stream() .findFirst() .orElse("0"); - return DataResult.of(Long.valueOf(count)); + return Long.valueOf(count); } @Override - public DataResult updateSelectResult(UpdateSelectResultParam param) { + public String updateSelectResult(UpdateSelectResultParam param) { SqlBuilder sqlBuilder = Chat2DBContext.getSqlBuilder(); String sql = sqlBuilder.generateSqlBasedOnResults(param.getTableName(), param.getHeaderList(), param.getOperations()); - return DataResult.of(sql); + return sql; } @Override - public DataResult getOrderBySql(OrderByParam param) { + public String getOrderBySql(OrderByParam param) { SqlBuilder sqlBuilder = Chat2DBContext.getSqlBuilder(); String orderSql = sqlBuilder.buildOrderBySql(param.getOriginSql(), param.getOrderByList()); - return DataResult.of(orderSql); + return orderSql; } @@ -179,6 +162,7 @@ private List
setColumnInfo(List
headers, String tableName, Strin tableQueryParam.setSchemaName(schemaName); tableQueryParam.setDatabaseName(databaseName); tableQueryParam.setRefresh(true); + tableQueryParam.setDataSourceId(Chat2DBContext.getConnectInfo().getDataSourceId()); List columns = tableService.queryColumns(tableQueryParam); if (CollectionUtils.isEmpty(columns)) { return headers; diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/EnvironmentServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/EnvironmentServiceImpl.java index 4eb490e58..22c2cc197 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/EnvironmentServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/EnvironmentServiceImpl.java @@ -11,6 +11,7 @@ import ai.chat2db.server.domain.repository.mapper.EnvironmentMapper; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; +import ai.chat2db.server.tools.base.wrapper.ServicePage; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; @@ -38,19 +39,19 @@ private EnvironmentMapper getMapper() { private EnvironmentConverter environmentConverter; @Override - public ListResult listQuery(List idList) { + public List listQuery(List idList) { if (CollectionUtils.isEmpty(idList)) { - return ListResult.empty(); + return java.util.Collections.emptyList(); } LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.in(EnvironmentDO::getId, idList); List dataList = getMapper().selectList(queryWrapper); List list = environmentConverter.do2dto(dataList); - return ListResult.of(list); + return list; } @Override - public PageResult pageQuery(EnvironmentPageQueryParam param) { + public ServicePage pageQuery(EnvironmentPageQueryParam param) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); if (StringUtils.isNotBlank(param.getSearchKey())) { queryWrapper.and(wrapper -> wrapper.like(EnvironmentDO::getName, "%" + param.getSearchKey() + "%") @@ -60,6 +61,6 @@ public PageResult pageQuery(EnvironmentPageQueryParam param) { IPage iPage = getMapper().selectPage(new Page<>(param.getPageNo(), param.getPageSize()), queryWrapper); List dataList = environmentConverter.do2dto(iPage.getRecords()); - return PageResult.of(dataList, iPage.getTotal(), param); + return ServicePage.of(dataList, iPage.getTotal(), param.getPageNo(), param.getPageSize()); } } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ErDiagramServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ErDiagramServiceImpl.java new file mode 100644 index 000000000..ded893f7f --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ErDiagramServiceImpl.java @@ -0,0 +1,455 @@ +package ai.chat2db.server.domain.core.impl; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import ai.chat2db.server.domain.api.param.CreateVirtualFKParam; +import ai.chat2db.server.domain.api.param.ErDiagramQueryParam; +import ai.chat2db.server.domain.api.param.TablePageQueryParam; +import ai.chat2db.server.domain.api.param.TableSelector; +import ai.chat2db.server.domain.api.service.ErDiagramService; +import ai.chat2db.server.domain.api.service.ForeignKeySyncService; +import ai.chat2db.server.domain.api.service.TableService; +import ai.chat2db.server.domain.api.vo.InferVirtualFkResultVO; +import ai.chat2db.spi.model.ErDiagram; +import ai.chat2db.spi.model.ForeignKey; +import ai.chat2db.spi.model.Table; +import ai.chat2db.spi.model.TableIndex; +import ai.chat2db.spi.model.TableIndexColumn; +import ai.chat2db.spi.model.TableColumn; +import ai.chat2db.spi.model.VirtualForeignKey; +import lombok.extern.slf4j.Slf4j; + +@Service +@Slf4j +public class ErDiagramServiceImpl implements ErDiagramService { + + @Autowired + private TableService tableService; + + @Autowired + private ForeignKeySyncService foreignKeySyncService; + + @Override + public ErDiagram queryErDiagram(ErDiagramQueryParam param) { + if (Boolean.TRUE.equals(param.getSyncForeignKeys())) { + foreignKeySyncService.syncForeignKeys( + param.getDataSourceId(), + param.getDatabaseName(), + param.getSchemaName(), + null + ); + } + + List
tables = queryTables(param); + List nodes = buildNodes(tables); + Set tableNameSet = tables.stream().map(Table::getName).collect(Collectors.toSet()); + boolean includeVirtual = param.getIncludeVirtualFk() == null || param.getIncludeVirtualFk(); + List edges = buildEdges(tables, tableNameSet, includeVirtual); + + if (Boolean.TRUE.equals(param.getOnlyRelatedTables())) { + Set relatedTableIds = edges.stream() + .flatMap(e -> java.util.stream.Stream.of(e.getSource(), e.getTarget())) + .collect(Collectors.toSet()); + nodes = nodes.stream() + .filter(n -> relatedTableIds.contains(n.getId())) + .collect(Collectors.toList()); + edges = edges.stream() + .filter(e -> relatedTableIds.contains(e.getSource()) && relatedTableIds.contains(e.getTarget())) + .collect(Collectors.toList()); + } + + return ErDiagram.builder().nodes(nodes).edges(edges).build(); + } + + private List
queryTables(ErDiagramQueryParam param) { + TablePageQueryParam tablePageQueryParam = TablePageQueryParam.builder() + .dataSourceId(param.getDataSourceId()) + .databaseName(param.getDatabaseName()) + .schemaName(param.getSchemaName()) + .searchKey(param.getTableNameFilter()) + .build(); + TableSelector selector = new TableSelector(); + selector.setColumnList(true); + selector.setIndexList(true); + selector.setForeignKey(true); + return tableService.pageQuery(tablePageQueryParam, selector); + } + + private List buildNodes(List
tables) { + return tables.stream().map(table -> ErDiagram.Node.builder() + .id(table.getName()) + .name(table.getName()) + .comment(table.getComment()) + .columnCount(CollectionUtils.isEmpty(table.getColumnList()) ? 0 : table.getColumnList().size()) + .build() + ).collect(Collectors.toList()); + } + + private List buildEdges(List
tables, Set tableNameSet, boolean includeVirtual) { + List edges = new ArrayList<>(); + for (Table table : tables) { + if (CollectionUtils.isNotEmpty(table.getForeignKeyList())) { + for (ForeignKey fk : table.getForeignKeyList()) { + if (tableNameSet.contains(fk.getReferencedTable())) { + edges.add(buildEdge(table.getName(), fk, false)); + } + } + } + if (includeVirtual && CollectionUtils.isNotEmpty(table.getVirtualForeignKeyList())) { + for (VirtualForeignKey vfk : table.getVirtualForeignKeyList()) { + if (tableNameSet.contains(vfk.getReferencedTable())) { + edges.add(buildEdge(table.getName(), vfk, true)); + } + } + } + } + return edges; + } + + private ErDiagram.Edge buildEdge(String tableName, ForeignKey fk, boolean virtual) { + String defaultId = virtual + ? "VFK_" + tableName + "_" + fk.getColumn() + : tableName + "_" + fk.getColumn() + "_" + fk.getReferencedTable(); + return ErDiagram.Edge.builder() + .id(fk.getName() != null ? fk.getName() : defaultId) + .source(tableName) + .target(fk.getReferencedTable()) + .sourceColumn(fk.getColumn()) + .targetColumn(fk.getReferencedColumn()) + .label(fk.getColumn() + " -> " + fk.getReferencedColumn()) + .virtual(virtual) + .build(); + } + + /** + * 推断虚拟外键关系 + * 基于列命名约定自动发现表之间的潜在外键关联,并同步到虚拟外键存储中 + * + * @param param ER图查询参数,包含数据源、数据库/模式、表过滤等 + * @return 推断结果,包含新增和删除的虚拟外键信息及数量 + */ + @Override + public InferVirtualFkResultVO inferVirtualForeignKeys(ErDiagramQueryParam param) { + // 1. 获取当前数据源/库/模式下已存在的所有虚拟外键 + List existingVirtualForeignKeys = foreignKeySyncService.queryAllVirtualForeignKeys( + param.getDataSourceId(), + param.getDatabaseName(), + param.getSchemaName() + ); + + // 2. 查询需要参与推断的表列表(包含列和索引信息) + List
tables = queryTablesForInference(param); + + // 3. 提取有效表名列表,用于后续清理无效虚拟外键 + List existingTableNames = tables.stream() + .map(Table::getName) + .collect(Collectors.toList()); + + // 4. 构建规范化后的表名->表对象映射,用于快速查找 + Map tableMap = tables.stream() + .filter(table -> table.getName() != null) + .collect(Collectors.toMap( + table -> normalizeName(table.getName()), + table -> table, + (first, second) -> first + )); + Map targetTableCache = new HashMap<>(tableMap); + + // 5. 判断是否需要清理无效虚拟外键:当没有表名过滤条件时才执行清理 + boolean cleanInvalidVirtualForeignKeys = param.getTableNameFilter() == null + || param.getTableNameFilter().isBlank(); + // 6. 筛选出指向不存在表的无效虚拟外键(关联的表或引用表已不在当前表集合中) + // 复用推断开始时的虚拟外键快照,避免后续按表重复查询 H2;有表过滤时不做清理,防止误删过滤范围外的外键。 + List invalidVirtualForeignKeys = cleanInvalidVirtualForeignKeys + ? existingVirtualForeignKeys.stream() + .filter(vfk -> !tableMap.containsKey(normalizeName(vfk.getTableName())) + || !tableMap.containsKey(normalizeName(vfk.getReferencedTable()))) + .collect(Collectors.toList()) + : Collections.emptyList(); + + // 7. 执行清理操作,删除指向已不存在表的虚拟外键记录 + int cleanedCount = cleanInvalidVirtualForeignKeys + ? foreignKeySyncService.cleanInvalidVirtualForeignKeys( + param.getDataSourceId(), + param.getDatabaseName(), + param.getSchemaName(), + existingTableNames + ) + : 0; + log.info("Cleaned {} invalid virtual foreign keys before inference", cleanedCount); + + // 8. 过滤出仍然有效的虚拟外键(排除已被标记为无效的) + List validVirtualForeignKeys = existingVirtualForeignKeys.stream() + .filter(vfk -> !invalidVirtualForeignKeys.contains(vfk)) + .collect(Collectors.toList()); + // 9. 按表名分组有效虚拟外键,用于后续跳过已有虚拟外键的表 + Map> virtualForeignKeysByTable = validVirtualForeignKeys.stream() + .filter(vfk -> vfk.getTableName() != null) + .collect(Collectors.groupingBy(vfk -> normalizeName(vfk.getTableName()))); + + // 10. 收集已有虚拟外键的表名集合,避免重复推断 + Set tablesWithVirtualForeignKeys = validVirtualForeignKeys.stream() + .map(VirtualForeignKey::getTableName) + .filter(Objects::nonNull) + .map(this::normalizeName) + .collect(Collectors.toSet()); + + // 11. 遍历所有表,对尚未有虚拟外键的表进行推断 + List inferredCreateParams = new ArrayList<>(); + for (Table table : tables) { + // 跳过已有虚拟外键的表 + if (table.getName() != null + && tablesWithVirtualForeignKeys.contains(normalizeName(table.getName()))) { + continue; + } + // 基于列命名约定推断该表的潜在外键关系 + List inferredFKs = findVirtualForeignKeys( + table, + targetTableCache, + param, + virtualForeignKeysByTable.getOrDefault(normalizeName(table.getName()), Collections.emptyList()) + ); + for (VirtualForeignKey vfk : inferredFKs) { + inferredCreateParams.add(CreateVirtualFKParam.builder() + .dataSourceId(param.getDataSourceId()) + .databaseName(param.getDatabaseName()) + .schemaName(param.getSchemaName()) + .tableName(table.getName()) + .columnName(vfk.getColumn()) + .referencedTable(vfk.getReferencedTable()) + .referencedColumnName(vfk.getReferencedColumn()) + .comment("Inferred from column naming convention") + .sourceType("INFERRED") + .build()); + } + } + List addedList = foreignKeySyncService.createInferredVirtualFKs(inferredCreateParams); + + // 12. 构建删除项结果列表(被清理的无效虚拟外键) + List deletedItems = invalidVirtualForeignKeys.stream() + .map(vfk -> InferVirtualFkResultVO.VirtualFkItem.builder() + .tableName(vfk.getTableName()) + .columnName(vfk.getColumn()) + .referencedTable(vfk.getReferencedTable()) + .referencedColumnName(vfk.getReferencedColumn()) + .build()) + .collect(Collectors.toList()); + + // 13. 构建新增项结果列表(本次推断成功创建的虚拟外键) + List addedItems = addedList.stream() + .map(vfk -> InferVirtualFkResultVO.VirtualFkItem.builder() + .tableName(vfk.getTableName()) + .columnName(vfk.getColumn()) + .referencedTable(vfk.getReferencedTable()) + .referencedColumnName(vfk.getReferencedColumn()) + .build()) + .collect(Collectors.toList()); + + // 14. 组装并返回推断结果 + InferVirtualFkResultVO result = InferVirtualFkResultVO.builder() + .addedCount(addedItems.size()) + .deletedCount(deletedItems.size()) + .added(addedItems) + .deleted(deletedItems) + .build(); + + return result; + } + + /** + * 查询用于推断的表列表 + * 获取指定数据源/数据库/模式下的表信息(包含列和索引), + * 并加载真实外键列表回填到表对象中 + * + * @param param ER图查询参数 + * @return 包含列和真实外键信息的表列表 + */ + private List
queryTablesForInference(ErDiagramQueryParam param) { + // 1. 构建表分页查询参数 + TablePageQueryParam tablePageQueryParam = TablePageQueryParam.builder() + .dataSourceId(param.getDataSourceId()) + .databaseName(param.getDatabaseName()) + .schemaName(param.getSchemaName()) + .searchKey(param.getTableNameFilter()) + .build(); + // 2. 设置选择器,需要获取列列表和索引列表 + TableSelector selector = new TableSelector(); + selector.setColumnList(true); + selector.setIndexList(true); + + // 3. 执行分页查询获取表列表 + List
tables = tableService.pageQuery(tablePageQueryParam, selector); + // 4. 查询该数据源/库/模式下的所有真实物理外键 + List realForeignKeys = foreignKeySyncService.queryRealForeignKeys( + param.getDataSourceId(), + param.getDatabaseName(), + param.getSchemaName(), + null + ); + // 5. 按表名分组真实外键,便于后续快速查找 + Map> realForeignKeysByTable = realForeignKeys.stream() + .filter(fk -> fk.getTableName() != null) + .collect(Collectors.groupingBy(fk -> normalizeName(fk.getTableName()))); + + // 6. 将真实外键批量回填到对应的表对象中 + // 真实外键一次批量加载后回填到表对象;虚拟外键由调用方的全量快照处理。 + for (Table table : tables) { + table.setForeignKeyList(realForeignKeysByTable.getOrDefault( + normalizeName(table.getName()), + Collections.emptyList() + )); + } + return tables; + } + + /** + * 发现可能的虚拟外键关系(根据命名规范推断) + * 参考 TableServiceImpl.findVirtualForeignKeys 实现 + */ + private List findVirtualForeignKeys(Table table, + Map targetTableCache, + ErDiagramQueryParam param, + List storedVirtualFKs) { + List result = new ArrayList<>(); + + // 预加载已明确声明的外键列名(用于排除已存在的外键) + Set explicitForeignKeys = defaultList(table.getForeignKeyList()).stream() + .map(ForeignKey::getColumn) + .collect(Collectors.toCollection(java.util.LinkedHashSet::new)); + + // 排除唯一索引列 + if (CollectionUtils.isNotEmpty(table.getIndexList())) { + table.getIndexList().stream() + .filter(index -> Boolean.TRUE.equals(index.getUnique())) + .map(TableIndex::getColumnList) + .flatMap(List::stream) + .map(TableIndexColumn::getColumnName) + .forEach(explicitForeignKeys::add); + } + + // 调用方传入预加载的虚拟外键,避免每张表单独查询一次 H2。 + Set existingVirtualFKColumns = storedVirtualFKs.stream() + .map(VirtualForeignKey::getColumn) + .collect(Collectors.toSet()); + + // 筛选候选列 + if (CollectionUtils.isNotEmpty(table.getColumnList())) { + for (TableColumn column : table.getColumnList()) { + if (isPotentialVirtualKeyCandidate(column) + && !explicitForeignKeys.contains(column.getName()) + && !existingVirtualFKColumns.contains(column.getName())) { + VirtualForeignKey vfk = analyzeColumnRelation(table, column, targetTableCache, param); + if (vfk != null) { + result.add(vfk); + } + } + } + } + + return result; + } + + /** + * 判断列是否为虚拟外键候选列 + */ + private boolean isPotentialVirtualKeyCandidate(TableColumn column) { + return column.getName() != null + && column.getName().endsWith("_id") + && Boolean.FALSE.equals(column.getPrimaryKey()) + && column.getName().length() > 3; + } + + /** + * 分析列关联关系并构建虚拟外键 + */ + private VirtualForeignKey analyzeColumnRelation(Table currentTable, + TableColumn currentColumn, + Map targetTableCache, + ErDiagramQueryParam param) { + String columnName = currentColumn.getName(); + String referencedTableName = columnName.substring(0, columnName.length() - 3); + String currentTableName = currentTable.getName(); + + // 排除自关联 + if (referencedTableName.equalsIgnoreCase(currentTableName)) { + return null; + } + + // 先从已加载表中匹配;若当前请求有过滤导致目标表缺失,则按引用表名缓存一次补查结果。 + Table targetTable = resolveTargetTable(currentTable, currentColumn, referencedTableName, targetTableCache, param); + if (targetTable == null) { + return null; + } + + // 查找目标表的关联列 + String referencedColumnName = "id"; + if (CollectionUtils.isNotEmpty(targetTable.getColumnList())) { + for (TableColumn tableColumn : targetTable.getColumnList()) { + if (columnName.equalsIgnoreCase(tableColumn.getName())) { + referencedColumnName = tableColumn.getName(); + break; + } else if ("id".equals(referencedColumnName) && Boolean.TRUE.equals(tableColumn.getPrimaryKey())) { + referencedColumnName = tableColumn.getName(); + } + } + } + + return VirtualForeignKey.builder() + .name(String.format("VFK_%s_%s", currentTableName, columnName)) + .tableName(currentTableName) + .column(columnName) + .referencedTable(targetTable.getName()) + .referencedColumn(referencedColumnName) + .virtualProperty("Inferred from column naming convention") + .build(); + } + + private Table resolveTargetTable(Table currentTable, + TableColumn currentColumn, + String referencedTableName, + Map targetTableCache, + ErDiagramQueryParam param) { + String cacheKey = normalizeName(referencedTableName); + if (targetTableCache.containsKey(cacheKey)) { + return targetTableCache.get(cacheKey); + } + + TableSelector selector = new TableSelector(); + selector.setColumnList(true); + List
tables = tableService.pageQuery( + TablePageQueryParam.builder() + .dataSourceId(param.getDataSourceId()) + .databaseName(currentColumn.getDatabaseName()) + .schemaName(currentColumn.getSchemaName()) + .searchKey(referencedTableName) + .build(), + selector + ); + Table targetTable = tables.stream() + .filter(t -> currentTable.getName() == null || !currentTable.getName().equalsIgnoreCase(t.getName())) + .findFirst() + .orElse(null); + targetTableCache.put(cacheKey, targetTable); + return targetTable; + } + + private String normalizeName(String name) { + return name == null ? "" : name.toLowerCase(Locale.ROOT); + } + + private List defaultList(List list) { + return list == null ? Collections.emptyList() : list; + } +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ForeignKeySyncServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ForeignKeySyncServiceImpl.java new file mode 100644 index 000000000..b5ad64fb8 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ForeignKeySyncServiceImpl.java @@ -0,0 +1,688 @@ +package ai.chat2db.server.domain.core.impl; + +import ai.chat2db.server.domain.api.param.CreateVirtualFKParam; +import ai.chat2db.server.domain.api.param.UpdateVirtualFKParam; +import ai.chat2db.server.domain.api.service.ForeignKeySyncService; +import ai.chat2db.server.domain.repository.Dbutils; +import ai.chat2db.server.domain.repository.entity.ForeignKeyDO; +import ai.chat2db.server.domain.repository.entity.VirtualForeignKeyDO; +import ai.chat2db.server.domain.repository.mapper.ForeignKeyMapper; +import ai.chat2db.server.domain.repository.mapper.VirtualForeignKeyMapper; +import ai.chat2db.server.tools.base.excption.BusinessException; +import ai.chat2db.server.tools.base.wrapper.result.ActionResult; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; +import ai.chat2db.server.tools.common.util.ContextUtils; +import ai.chat2db.spi.MetaData; +import ai.chat2db.spi.SqlBuilder; +import ai.chat2db.spi.model.ForeignKey; +import ai.chat2db.spi.model.Table; +import ai.chat2db.spi.model.TableColumn; +import ai.chat2db.spi.model.VirtualForeignKey; +import ai.chat2db.spi.sql.Chat2DBContext; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.sql.Connection; +import java.time.LocalDateTime; +import java.util.*; +import java.util.stream.Collectors; + +@Slf4j +@Service +public class ForeignKeySyncServiceImpl implements ForeignKeySyncService { + + private static final String SOURCE_TYPE_REAL = "REAL"; + private static final String SOURCE_TYPE_VIRTUAL_MANUAL = "MANUAL"; + private static final String SOURCE_TYPE_VIRTUAL_INFERRED = "INFERRED"; + + private ForeignKeyMapper getFKMapper() { + return Dbutils.getMapper(ForeignKeyMapper.class); + } + + private VirtualForeignKeyMapper getVFKMapper() { + return Dbutils.getMapper(VirtualForeignKeyMapper.class); + } + + /** + * 同步数据库表的外键定义到本地H2数据库 + * 该方法执行以下操作: + * 1. 从数据库元数据中获取当前表的所有外键定义 + * 2. 从本地H2数据库查询已存储的外键记录 + * 3. 比较差异:添加数据库中存在但本地不存在的外键 + * 4. 删除本地存在但数据库中已不存在的外键 + * 5. 更新同步时间戳和版本号 + * + * @param dataSourceId 数据源ID + * @param databaseName 数据库名称(可为空) + * @param schemaName 数据库模式名称(可为空) + * @param tableName 表名称 + * @return SyncResult 同步结果对象,包含新增、删除和保留的外键数量 + */ + @Override + public SyncResult syncForeignKeys(Long dataSourceId, String databaseName, String schemaName, String tableName) { + try { + Connection connection = Chat2DBContext.getConnection(); + MetaData metaData = Chat2DBContext.getMetaData(); + List dbForeignKeys = metaData.foreignKeys(connection, databaseName, schemaName, tableName); + + List existingFKs = queryRealFKsFromH2(dataSourceId, databaseName, schemaName, tableName); + + Set dbFKKeys = dbForeignKeys.stream() + .map(this::buildUniqueKey) + .collect(Collectors.toSet()); + Set existingFKKeys = existingFKs.stream() + .map(this::buildUniqueKeyFromDO) + .collect(Collectors.toSet()); + + int added = 0, deleted = 0; + + for (ForeignKey fk : dbForeignKeys) { + if (!existingFKKeys.contains(buildUniqueKey(fk))) { + insertForeignKey(fk, dataSourceId); + added++; + } + } + + for (ForeignKeyDO existing : existingFKs) { + if (!dbFKKeys.contains(buildUniqueKeyFromDO(existing))) { + LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); + wrapper.eq(ForeignKeyDO::getId, existing.getId()); + getFKMapper().delete(wrapper); + deleted++; + } + } + + String syncVersion = UUID.randomUUID().toString().substring(0, 8); + LocalDateTime now = LocalDateTime.now(); + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); + updateWrapper.eq(ForeignKeyDO::getDataSourceId, dataSourceId) + .eq(StringUtils.isNotBlank(databaseName), ForeignKeyDO::getDatabaseName, databaseName) + .eq(StringUtils.isNotBlank(schemaName), ForeignKeyDO::getSchemaName, schemaName) + .eq(StringUtils.isNotBlank(tableName), ForeignKeyDO::getTableName, tableName); + ForeignKeyDO updateDO = new ForeignKeyDO(); + updateDO.setSyncTime(now); + updateDO.setSyncVersion(syncVersion); + getFKMapper().update(updateDO, updateWrapper); + + return new SyncResult(added, deleted, dbForeignKeys.size() - added); + } catch (Exception e) { + log.error("syncForeignKeys error", e); + return new SyncResult(0, 0, 0); + } + } + + /** + * 查询指定表的所有外键(包括真实外键和虚拟外键) + * 该方法合并查询本地H2数据库中存储的真实外键和用户定义的虚拟外键 + * + * @param dataSourceId 数据源ID + * @param databaseName 数据库名称(可为空) + * @param schemaName 数据库模式名称(可为空) + * @param tableName 表名称 + * @return List 包含所有外键的列表 + */ + @Override + public List listAllForeignKeys(Long dataSourceId, String databaseName, String schemaName, String tableName) { + List result = new ArrayList<>(); + + List realFKs = queryRealFKsFromH2(dataSourceId, databaseName, schemaName, tableName); + for (ForeignKeyDO fk : realFKs) { + result.add(convertDOToModel(fk)); + } + + List virtualFKs = queryVirtualFKsFromH2(dataSourceId, databaseName, schemaName, tableName); + for (VirtualForeignKeyDO vk : virtualFKs) { + result.add(convertVirtualDOToModel(vk)); + } + + return result; + } + + /** + * 创建虚拟外键 + * 虚拟外键是用户手动定义的逻辑外键关系,不实际存在于数据库中 + * 该方法会检查虚拟外键是否已存在,避免重复创建 + * + * @param param 创建虚拟外键的参数对象 + * @return DataResult 创建结果,包含成功或失败信息 + */ + @Override + public VirtualForeignKey createVirtualFK(CreateVirtualFKParam param) { + validateVirtualForeignKeyRelation( + param.getDatabaseName(), + param.getSchemaName(), + param.getTableName(), + param.getColumnName(), + param.getReferencedTable(), + param.getReferencedColumnName()); + + if (existsVirtualForeignKey(null, + param.getDataSourceId(), + param.getDatabaseName(), + param.getSchemaName(), + param.getTableName(), + param.getColumnName())) { + throw new BusinessException("VIRTUAL_FK_EXISTS", new Object[]{}); + } + + VirtualForeignKeyDO entity = buildVirtualForeignKeyDO(param); + getVFKMapper().insert(entity); + return convertVirtualDOToModel(entity); + } + + @Override + public List createInferredVirtualFKs(List params) { + if (CollectionUtils.isEmpty(params)) { + return Collections.emptyList(); + } + + CreateVirtualFKParam first = params.get(0); + List existingFKs = queryVirtualFKsFromH2( + first.getDataSourceId(), + first.getDatabaseName(), + first.getSchemaName(), + null); + Set existingKeys = existingFKs.stream() + .map(vfk -> buildVirtualForeignKeyKey( + vfk.getDataSourceId(), + vfk.getDatabaseName(), + vfk.getSchemaName(), + vfk.getTableName(), + vfk.getColumnName())) + .collect(Collectors.toSet()); + + List created = new ArrayList<>(); + for (CreateVirtualFKParam param : params) { + String key = buildVirtualForeignKeyKey( + param.getDataSourceId(), + param.getDatabaseName(), + param.getSchemaName(), + param.getTableName(), + param.getColumnName()); + if (!existingKeys.add(key)) { + continue; + } + + VirtualForeignKeyDO entity = buildVirtualForeignKeyDO(param); + getVFKMapper().insert(entity); + created.add(convertVirtualDOToModel(entity)); + } + return created; + } + + /** + * 更新虚拟外键信息 + * 该方法支持更新虚拟外键的注释、引用表、引用列和名称等属性 + * + * @param param 更新虚拟外键的参数对象 + * @return DataResult 更新结果,包含成功或失败信息 + */ + @Override + public VirtualForeignKey updateVirtualFK(UpdateVirtualFKParam param) { + VirtualForeignKeyDO existing = getVFKMapper().selectById(param.getId()); + if (existing == null) { + throw new BusinessException("VIRTUAL_FK_NOT_FOUND", new Object[]{"虚拟外键不存在"}); + } + + VirtualForeignKeyDO updateDO = new VirtualForeignKeyDO(); + updateDO.setId(param.getId()); + String tableName = StringUtils.defaultIfBlank(param.getTableName(), existing.getTableName()); + String columnName = StringUtils.defaultIfBlank(param.getColumnName(), existing.getColumnName()); + String referencedTable = StringUtils.defaultIfBlank(param.getReferencedTable(), existing.getReferencedTable()); + String referencedColumnName = StringUtils.defaultIfBlank( + param.getReferencedColumnName(), existing.getReferencedColumnName()); + + validateVirtualForeignKeyRelation( + existing.getDatabaseName(), + existing.getSchemaName(), + tableName, + columnName, + referencedTable, + referencedColumnName); + + if (existsVirtualForeignKey(param.getId(), + existing.getDataSourceId(), + existing.getDatabaseName(), + existing.getSchemaName(), + tableName, + columnName)) { + throw new BusinessException("VIRTUAL_FK_EXISTS", new Object[]{}); + } + + updateDO.setComment(param.getComment()); + if (StringUtils.isNotBlank(param.getTableName())) { + updateDO.setTableName(param.getTableName()); + } + if (StringUtils.isNotBlank(param.getColumnName())) { + updateDO.setColumnName(param.getColumnName()); + } + if (StringUtils.isNotBlank(param.getReferencedTable())) { + updateDO.setReferencedTable(param.getReferencedTable()); + } + if (StringUtils.isNotBlank(param.getReferencedColumnName())) { + updateDO.setReferencedColumnName(param.getReferencedColumnName()); + } + if (StringUtils.isNotBlank(param.getVkName())) { + updateDO.setVkName(param.getVkName()); + } + + getVFKMapper().updateById(updateDO); + + VirtualForeignKeyDO updated = getVFKMapper().selectById(param.getId()); + return VirtualForeignKey.builder() + .name(updated.getVkName()) + .tableName(updated.getTableName()) + .column(updated.getColumnName()) + .referencedTable(updated.getReferencedTable()) + .referencedColumn(updated.getReferencedColumnName()) + .comment(updated.getComment()) + .virtualProperty("User-defined virtual foreign key") + .build(); + } + + /** + * 删除虚拟外键 + * 该方法根据ID删除指定的虚拟外键记录 + * + * @param id 虚拟外键的ID + */ + @Override + public void deleteVirtualFK(Long id) { + VirtualForeignKeyDO existing = getVFKMapper().selectById(id); + if (existing == null) { + throw new BusinessException("VIRTUAL_FK_NOT_FOUND", new Object[]{"虚拟外键不存在"}); + } + getVFKMapper().deleteById(id); + } + + /** + * 删除真实外键(物理外键) + * 该方法不仅从本地H2数据库删除外键记录,还会生成对应的DROP FOREIGN KEY SQL语句 + * + * @param id 真实外键的ID + * @return String DROP SQL语句 + */ + @Override + public String deleteRealFK(Long id) { + ForeignKeyDO existing = getFKMapper().selectById(id); + if (existing == null) { + throw new BusinessException("FK_NOT_FOUND", new Object[]{"外键不存在"}); + } + + SqlBuilder sqlBuilder = Chat2DBContext.getSqlBuilder(); + ForeignKey fk = ForeignKey.builder() + .name(existing.getFkName()) + .tableName(existing.getTableName()) + .column(existing.getColumnName()) + .referencedTable(existing.getReferencedTable()) + .referencedColumn(existing.getReferencedColumnName()) + .updateRule(existing.getUpdateRule() != null ? existing.getUpdateRule() : 0) + .deleteRule(existing.getDeleteRule() != null ? existing.getDeleteRule() : 0) + .build(); + + String dropFKSql = sqlBuilder.buildDropForeignKeySql(fk); + + getFKMapper().deleteById(id); + + return dropFKSql; + } + + /** + * 生成外键的DDL语句(CREATE/ALTER/DROP) + * 该方法比较新旧表结构,生成必要的外键变更SQL语句 + * + * @param oldTable 旧表结构(可为空) + * @param newTable 新表结构(可为空) + * @return List 包含所有需要执行的DDL语句的列表 + */ + @Override + public List generateForeignKeyDDL(Table oldTable, Table newTable) { + List ddlList = new ArrayList<>(); + + List oldFKs = oldTable != null && oldTable.getForeignKeyList() != null + ? oldTable.getForeignKeyList() : Collections.emptyList(); + List newFKs = newTable != null && newTable.getForeignKeyList() != null + ? newTable.getForeignKeyList() : Collections.emptyList(); + + Map oldFKMap = oldFKs.stream() + .collect(Collectors.toMap(this::buildUniqueKey, f -> f, (o1, o2) -> o1)); + Map newFKMap = newFKs.stream() + .collect(Collectors.toMap(this::buildUniqueKey, f -> f, (o1, o2) -> o1)); + + SqlBuilder sqlBuilder = Chat2DBContext.getSqlBuilder(); + + for (ForeignKey newFK : newFKs) { + if (!oldFKMap.containsKey(buildUniqueKey(newFK))) { + String ddl = sqlBuilder.buildAddForeignKeySql(newFK); + if (StringUtils.isNotBlank(ddl)) { + ddlList.add(ddl); + } + } + } + + for (ForeignKey oldFK : oldFKs) { + if (!newFKMap.containsKey(buildUniqueKey(oldFK))) { + String ddl = sqlBuilder.buildDropForeignKeySql(oldFK); + if (StringUtils.isNotBlank(ddl)) { + ddlList.add(ddl); + } + } + } + + return ddlList; + } + + /** + * 查询指定表的真实外键(物理外键) + * 该方法仅查询数据库中实际存在的外键,不包含虚拟外键 + * + * @param dataSourceId 数据源ID + * @param databaseName 数据库名称(可为空) + * @param schemaName 数据库模式名称(可为空) + * @param tableName 表名称 + * @return List 真实外键列表 + */ + @Override + public List queryRealForeignKeys(Long dataSourceId, String databaseName, String schemaName, String tableName) { + List doList = queryRealFKsFromH2(dataSourceId, databaseName, schemaName, tableName); + return doList.stream() + .map(this::convertDOToModel) + .collect(Collectors.toList()); + } + + /** + * 查询指定表的虚拟外键 + * 该方法查询用户手动定义的虚拟外键,不包含数据库中真实存在的外键 + * + * @param dataSourceId 数据源ID + * @param databaseName 数据库名称(可为空) + * @param schemaName 数据库模式名称(可为空) + * @param tableName 表名称 + * @return List 虚拟外键列表 + */ + @Override + public List queryVirtualForeignKeys(Long dataSourceId, String databaseName, String schemaName, String tableName) { + List doList = queryVirtualFKsFromH2(dataSourceId, databaseName, schemaName, tableName); + return doList.stream() + .map(this::convertVirtualDOToModel) + .collect(Collectors.toList()); + } + + @Override + public List queryAllVirtualForeignKeys(Long dataSourceId, String databaseName, String schemaName) { + List doList = queryVirtualFKsFromH2(dataSourceId, databaseName, schemaName, null); + return doList.stream() + .map(this::convertVirtualDOToModel) + .collect(Collectors.toList()); + } + + @Override + public int cleanInvalidVirtualForeignKeys(Long dataSourceId, String databaseName, String schemaName, List existingTableNames) { + if (CollectionUtils.isEmpty(existingTableNames)) { + return 0; + } + + Set existingTableSet = existingTableNames.stream() + .filter(StringUtils::isNotBlank) + .map(String::toLowerCase) + .collect(Collectors.toSet()); + + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(VirtualForeignKeyDO::getDataSourceId, dataSourceId) + .eq(StringUtils.isNotBlank(databaseName), VirtualForeignKeyDO::getDatabaseName, databaseName) + .eq(StringUtils.isNotBlank(schemaName), VirtualForeignKeyDO::getSchemaName, schemaName); + + List allVirtualFKs = getVFKMapper().selectList(wrapper); + if (CollectionUtils.isEmpty(allVirtualFKs)) { + return 0; + } + + List idsToDelete = new ArrayList<>(); + for (VirtualForeignKeyDO vfk : allVirtualFKs) { + boolean tableExists = existingTableSet.contains(vfk.getTableName().toLowerCase()); + boolean referencedTableExists = existingTableSet.contains(vfk.getReferencedTable().toLowerCase()); + + if (!tableExists || !referencedTableExists) { + idsToDelete.add(vfk.getId()); + } + } + + if (!idsToDelete.isEmpty()) { + getVFKMapper().deleteBatchIds(idsToDelete); + log.info("Cleaned {} invalid virtual foreign keys for dataSourceId={}, databaseName={}, schemaName={}", + idsToDelete.size(), dataSourceId, databaseName, schemaName); + } + + return idsToDelete.size(); + } + + /** + * 从H2数据库查询真实外键记录 + * 根据数据源ID和表信息查询本地存储的真实外键 + * + * @param dataSourceId 数据源ID + * @param databaseName 数据库名称(可为空) + * @param schemaName 数据库模式名称(可为空) + * @param tableName 表名称 + * @return List 真实外键实体列表 + */ + private List queryRealFKsFromH2(Long dataSourceId, String databaseName, String schemaName, String tableName) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(ForeignKeyDO::getDataSourceId, dataSourceId) + .eq(StringUtils.isNotBlank(tableName), ForeignKeyDO::getTableName, tableName) + .eq(StringUtils.isNotBlank(databaseName), ForeignKeyDO::getDatabaseName, databaseName) + .eq(StringUtils.isNotBlank(schemaName), ForeignKeyDO::getSchemaName, schemaName); + return getFKMapper().selectList(wrapper); + } + + /** + * 从H2数据库查询虚拟外键记录 + * 根据数据源ID和表信息查询本地存储的虚拟外键 + * + * @param dataSourceId 数据源ID + * @param databaseName 数据库名称(可为空) + * @param schemaName 数据库模式名称(可为空) + * @param tableName 表名称 + * @return List 虚拟外键实体列表 + */ + private List queryVirtualFKsFromH2(Long dataSourceId, String databaseName, String schemaName, String tableName) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(VirtualForeignKeyDO::getDataSourceId, dataSourceId) + .eq(StringUtils.isNotBlank(tableName), VirtualForeignKeyDO::getTableName, tableName) + .eq(StringUtils.isNotBlank(databaseName), VirtualForeignKeyDO::getDatabaseName, databaseName) + .eq(StringUtils.isNotBlank(schemaName), VirtualForeignKeyDO::getSchemaName, schemaName); + return getVFKMapper().selectList(wrapper); + } + + private void validateVirtualForeignKeyRelation(String databaseName, + String schemaName, + String tableName, + String columnName, + String referencedTable, + String referencedColumnName) { + MetaData metaData = Chat2DBContext.getMetaData(); + Connection connection = Chat2DBContext.getConnection(); + + String actualTableName = findActualTableName(metaData, connection, databaseName, schemaName, tableName); + String actualReferencedTable = findActualTableName( + metaData, connection, databaseName, schemaName, referencedTable); + + if (StringUtils.isBlank(actualTableName) || StringUtils.isBlank(actualReferencedTable)) { + throw new BusinessException("VIRTUAL_FK_TABLE_NOT_FOUND", new Object[]{"虚拟外键关联表不存在"}); + } + + if (!existsColumnIgnoreCase(metaData, connection, databaseName, schemaName, actualTableName, columnName) + || !existsColumnIgnoreCase( + metaData, connection, databaseName, schemaName, actualReferencedTable, referencedColumnName)) { + throw new BusinessException("VIRTUAL_FK_COLUMN_NOT_FOUND", new Object[]{"虚拟外键关联字段不存在"}); + } + } + + private String findActualTableName(MetaData metaData, + Connection connection, + String databaseName, + String schemaName, + String tableName) { + if (StringUtils.isBlank(tableName)) { + return null; + } + List
tables = metaData.tables(connection, databaseName, schemaName, null); + return Optional.ofNullable(tables).orElse(Collections.emptyList()).stream() + .map(Table::getName) + .filter(name -> StringUtils.equalsIgnoreCase(name, tableName)) + .findFirst() + .orElse(null); + } + + private boolean existsColumnIgnoreCase(MetaData metaData, + Connection connection, + String databaseName, + String schemaName, + String tableName, + String columnName) { + if (StringUtils.isAnyBlank(tableName, columnName)) { + return false; + } + List columns = metaData.columns(connection, databaseName, schemaName, tableName); + return Optional.ofNullable(columns).orElse(Collections.emptyList()).stream() + .map(TableColumn::getName) + .anyMatch(name -> StringUtils.equalsIgnoreCase(name, columnName)); + } + + private boolean existsVirtualForeignKey(Long excludeId, + Long dataSourceId, + String databaseName, + String schemaName, + String tableName, + String columnName) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(VirtualForeignKeyDO::getDataSourceId, dataSourceId) + .eq(StringUtils.isNotBlank(databaseName), VirtualForeignKeyDO::getDatabaseName, databaseName) + .and(StringUtils.isBlank(databaseName), item -> item + .isNull(VirtualForeignKeyDO::getDatabaseName) + .or() + .eq(VirtualForeignKeyDO::getDatabaseName, "")) + .eq(StringUtils.isNotBlank(schemaName), VirtualForeignKeyDO::getSchemaName, schemaName) + .and(StringUtils.isBlank(schemaName), item -> item + .isNull(VirtualForeignKeyDO::getSchemaName) + .or() + .eq(VirtualForeignKeyDO::getSchemaName, "")) + .eq(VirtualForeignKeyDO::getTableName, tableName) + .eq(VirtualForeignKeyDO::getColumnName, columnName) + .ne(excludeId != null, VirtualForeignKeyDO::getId, excludeId); + return getVFKMapper().selectCount(wrapper) > 0; + } + + private VirtualForeignKeyDO buildVirtualForeignKeyDO(CreateVirtualFKParam param) { + VirtualForeignKeyDO entity = new VirtualForeignKeyDO(); + entity.setDataSourceId(param.getDataSourceId()); + entity.setDatabaseName(param.getDatabaseName()); + entity.setSchemaName(param.getSchemaName()); + entity.setTableName(param.getTableName()); + entity.setColumnName(param.getColumnName()); + entity.setVkName("VFK_" + param.getTableName() + "_" + param.getColumnName()); + entity.setReferencedTable(param.getReferencedTable()); + entity.setReferencedColumnName(param.getReferencedColumnName()); + entity.setComment(param.getComment()); + entity.setSourceType(param.getSourceType()); + entity.setUserId(ContextUtils.getUserId()); + return entity; + } + + private String buildVirtualForeignKeyKey(Long dataSourceId, + String databaseName, + String schemaName, + String tableName, + String columnName) { + return String.join(":", + Objects.toString(dataSourceId, ""), + normalizeVirtualForeignKeyPart(databaseName), + normalizeVirtualForeignKeyPart(schemaName), + normalizeVirtualForeignKeyPart(tableName), + normalizeVirtualForeignKeyPart(columnName)); + } + + private String normalizeVirtualForeignKeyPart(String value) { + return StringUtils.defaultString(value).toLowerCase(Locale.ROOT); + } + + /** + * 将外键信息插入到H2数据库 + * 将从数据库元数据获取的外键信息转换为实体并保存到本地H2数据库 + * + * @param fk 外键模型对象 + * @param dataSourceId 数据源ID + */ + private void insertForeignKey(ForeignKey fk, Long dataSourceId) { + ForeignKeyDO entity = new ForeignKeyDO(); + entity.setDataSourceId(dataSourceId); + entity.setDatabaseName(fk.getDatabaseName()); + entity.setSchemaName(fk.getSchemaName()); + entity.setTableName(fk.getTableName()); + entity.setColumnName(fk.getColumn()); + entity.setFkName(fk.getName()); + entity.setReferencedTable(fk.getReferencedTable()); + entity.setReferencedColumnName(fk.getReferencedColumn()); + entity.setReferencedSchema(fk.getSchemaName()); + entity.setReferencedDatabase(fk.getDatabaseName()); + entity.setUpdateRule(fk.getUpdateRule()); + entity.setDeleteRule(fk.getDeleteRule()); + entity.setComment(fk.getComment()); + entity.setSyncTime(LocalDateTime.now()); + getFKMapper().insert(entity); + } + + private ForeignKey convertDOToModel(ForeignKeyDO fk) { + return ForeignKey.builder() + .id(fk.getId()) + .name(fk.getFkName()) + .tableName(fk.getTableName()) + .schemaName(fk.getSchemaName()) + .databaseName(fk.getDatabaseName()) + .column(fk.getColumnName()) + .referencedTable(fk.getReferencedTable()) + .referencedColumn(fk.getReferencedColumnName()) + .updateRule(fk.getUpdateRule() != null ? fk.getUpdateRule() : 0) + .deleteRule(fk.getDeleteRule() != null ? fk.getDeleteRule() : 0) + .comment(fk.getComment()) + .build(); + } + + private VirtualForeignKey convertVirtualDOToModel(VirtualForeignKeyDO vk) { + return VirtualForeignKey.builder() + .id(vk.getId()) + .name(vk.getVkName()) + .tableName(vk.getTableName()) + .schemaName(vk.getSchemaName()) + .databaseName(vk.getDatabaseName()) + .column(vk.getColumnName()) + .referencedTable(vk.getReferencedTable()) + .referencedColumn(vk.getReferencedColumnName()) + .comment(vk.getComment()) + .virtualProperty(vk.getSourceType() != null ? vk.getSourceType() : "User-defined virtual foreign key") + .build(); + } + + private String buildUniqueKey(ForeignKey fk) { + return String.join(":", + StringUtils.defaultString(fk.getTableName()), + StringUtils.defaultString(fk.getColumn()), + StringUtils.defaultString(fk.getReferencedTable()), + StringUtils.defaultString(fk.getReferencedColumn()) + ); + } + + private String buildUniqueKeyFromDO(ForeignKeyDO fk) { + return String.join(":", + StringUtils.defaultString(fk.getTableName()), + StringUtils.defaultString(fk.getColumnName()), + StringUtils.defaultString(fk.getReferencedTable()), + StringUtils.defaultString(fk.getReferencedColumnName()) + ); + } +} + diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/FunctionServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/FunctionServiceImpl.java index 040bca7d0..00f3b21a5 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/FunctionServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/FunctionServiceImpl.java @@ -1,21 +1,127 @@ package ai.chat2db.server.domain.core.impl; +import java.sql.Connection; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import ai.chat2db.server.domain.api.model.TreeNode; +import ai.chat2db.server.domain.api.param.TreeSearchParam; import ai.chat2db.server.domain.api.service.FunctionService; +import ai.chat2db.server.domain.core.cache.LuceneIndexManager; +import ai.chat2db.server.domain.core.cache.LuceneIndexManagerFactory; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; +import ai.chat2db.spi.MetaData; import ai.chat2db.spi.model.Function; import ai.chat2db.spi.sql.Chat2DBContext; -import org.springframework.stereotype.Service; +import lombok.extern.slf4j.Slf4j; @Service +@Slf4j public class FunctionServiceImpl implements FunctionService { + + @Autowired + private LuceneIndexManagerFactory managerFactory; + @Override - public ListResult functions(String databaseName, String schemaName) { - return ListResult.of(Chat2DBContext.getMetaData().functions(Chat2DBContext.getConnection(),databaseName, schemaName)); + public List functions(String databaseName, String schemaName) { + return Chat2DBContext.getMetaData().functions(Chat2DBContext.getConnection(),databaseName, schemaName); } @Override - public DataResult detail(String databaseName, String schemaName, String functionName) { - return DataResult.of(Chat2DBContext.getMetaData().function(Chat2DBContext.getConnection(), databaseName, schemaName, functionName)); + public List functionsWithCache(Long dataSourceId, String databaseName, String schemaName, String searchKey, boolean refresh) { + LuceneIndexManager mgr = managerFactory.getManager(dataSourceId); + Function queryModel = Function.builder() + .databaseName(databaseName) + .schemaName(schemaName) + .build(); + Long version = mgr.getMaxVersion(queryModel); + + if (refresh || version == null) { + loadAndCacheMetadata(mgr, databaseName, schemaName, version); + } + + List functions = mgr.search(queryModel, null, searchKey); + return functions; + } + + @Override + public Function detail(String databaseName, String schemaName, String functionName) { + return Chat2DBContext.getMetaData().function(Chat2DBContext.getConnection(), databaseName, schemaName, functionName); + } + + @Override + public List searchTreeNodes(TreeSearchParam param) { + LuceneIndexManager mgr = managerFactory.getManager(param.getDataSourceId()); + Function queryModel = Function.builder() + .databaseName(param.getDatabaseName()) + .schemaName(param.getSchemaName()) + .build(); + Long version = mgr.getMaxVersion(queryModel); + + if (param.isRefresh() || version == null) { + loadAndCacheMetadata(mgr, param.getDatabaseName(), param.getSchemaName(), version); + } + + List functions = mgr.search(queryModel, null, param.getSearchKey()); + List result = new ArrayList<>(); + for (Function function : functions) { + TreeNode node = buildTreeNode(function); + result.add(node); + } + return result; + } + + private void loadAndCacheMetadata(LuceneIndexManager mgr, String databaseName, String schemaName, Long version) { + mgr.getLock().writeLock().lock(); + try { + Connection conn = Chat2DBContext.getConnection(); + MetaData meta = Chat2DBContext.getMetaData(); + List functions = meta.functions(conn, databaseName, schemaName); + if (CollectionUtils.isEmpty(functions)) { + mgr.deleteByDatabaseAndSchema(databaseName, schemaName); + return; + } + mgr.updateDocuments(functions, version); + log.info("[Function] Cached {} functions for database: {}", functions.size(), databaseName); + } catch (Exception e) { + log.error("[Function] loadAndCacheMetadata error", e); + } finally { + mgr.getLock().writeLock().unlock(); + } + } + + private TreeNode buildTreeNode(Function function) { + List parentPath = new ArrayList<>(); + if (StringUtils.isNotBlank(function.getDatabaseName())) { + parentPath.add(function.getDatabaseName()); + } + if (StringUtils.isNotBlank(function.getSchemaName())) { + parentPath.add(function.getSchemaName()); + } + + Map extraParams = new HashMap<>(); + extraParams.put("databaseName", function.getDatabaseName()); + extraParams.put("schemaName", function.getSchemaName()); + extraParams.put("functionName", function.getName()); + + return TreeNode.builder() + .uuid("function-" + function.getName()) + .key(function.getName()) + .name(function.getName()) + .treeNodeType("function") + .comment(function.getComment()) + .isLeaf(true) + .parentPath(parentPath) + .extraParams(extraParams) + .build(); } } + diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/JdbcDriverServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/JdbcDriverServiceImpl.java index cf95d0398..9840fc16c 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/JdbcDriverServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/JdbcDriverServiceImpl.java @@ -17,6 +17,7 @@ import com.google.common.collect.Lists; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -43,7 +44,7 @@ private JdbcDriverMapper getMapper() { } @Override - public DataResult getDrivers(String dbType) { + public DBConfig getDrivers(String dbType) { Map driverConfigMap = new LinkedHashMap<>(); LambdaQueryWrapper query = new LambdaQueryWrapper(); query.eq(JdbcDriverDO::getDbType, dbType); @@ -60,6 +61,9 @@ public DataResult getDrivers(String dbType) { } for (DriverConfig driverConfig : driverConfigs) { + if (driverConfig == null || StringUtils.isBlank(driverConfig.getJdbcDriver())) { + continue; + } boolean flag = driverExists(driverConfig); if (flag && driverConfigMap.get(driverConfig.getJdbcDriver()) == null) { driverConfigMap.put(driverConfig.getJdbcDriver(), driverConfig); @@ -70,7 +74,7 @@ public DataResult getDrivers(String dbType) { } } dbConfig.setDriverConfigList(driverConfigMap.isEmpty() ? null : Lists.newArrayList(driverConfigMap.values())); - return DataResult.of(dbConfig); + return dbConfig; } @@ -88,7 +92,7 @@ private boolean driverExists(DriverConfig driverConfig) { } @Override - public ActionResult upload(String dbType, String jdbcDriverClass, String localPath) { + public void upload(String dbType, String jdbcDriverClass, String localPath) { JdbcDriverDO driverDO = new JdbcDriverDO(); driverDO.setJdbcDriverClass(jdbcDriverClass); driverDO.setDbType(dbType); @@ -100,11 +104,11 @@ public ActionResult upload(String dbType, String jdbcDriverClass, String localPa throw new RuntimeException("Driver error,please check the driver file", e); } getMapper().insert(driverDO); - return ActionResult.isSuccess(); + } @Override - public ActionResult download(String dbType) { + public void download(String dbType) { DBConfig dbConfig = Chat2DBContext.PLUGIN_MAP.get(dbType).getDBConfig(); List driverConfigList = dbConfig.getDriverConfigList(); for (DriverConfig driverConfig : driverConfigList) { @@ -117,6 +121,6 @@ public ActionResult download(String dbType) { } } } - return ActionResult.isSuccess(); + } } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/LoginAttemptServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/LoginAttemptServiceImpl.java new file mode 100644 index 000000000..e8b7b19a8 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/LoginAttemptServiceImpl.java @@ -0,0 +1,77 @@ +package ai.chat2db.server.domain.core.impl; + +import java.time.Duration; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Date; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; + +import ai.chat2db.server.domain.api.service.LoginAttemptService; +import ai.chat2db.server.domain.repository.Dbutils; +import ai.chat2db.server.domain.repository.entity.LoginAttempt; +import ai.chat2db.server.domain.repository.mapper.LoginAttemptMapper; +import ai.chat2db.server.tools.base.excption.BusinessException; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service +public class LoginAttemptServiceImpl implements LoginAttemptService { + @Value("${security.login.max-attempts:5}") + private int maxAttempts; + @Value("${security.login.lock-duration:30}") + private int lockDurationMinutes; + @Override + public void validateAttempt(String clientFingerprint) { + LoginAttempt attempt = getDbhubLoginAttemptMapper().findByFingerprint(clientFingerprint); + + if (attempt != null && attempt.getLockedUntil() != null + && attempt.getLockedUntil().after(new Date())) { + throw new BusinessException("auth.login.locked"); + } + } + + @Override + public void recordFailedAttempt(String clientFingerprint) { + LoginAttemptMapper mapper = getDbhubLoginAttemptMapper(); + LoginAttempt attempt = mapper.findByFingerprint(clientFingerprint); + if (attempt == null) { + attempt = new LoginAttempt(); + attempt.setClientFingerprint(clientFingerprint); + attempt.setAttempts(1); + attempt.setLastAttemptTime(new Date()); + mapper.insert(attempt); + } else { + mapper.incrementAttempts(clientFingerprint); + attempt = mapper.findByFingerprint(clientFingerprint); + } + if (attempt.getAttempts() >= maxAttempts) { + Date lockedUntil = Date.from(Instant.now().plus(lockDurationMinutes, ChronoUnit.MINUTES)); + attempt.setLockedUntil(lockedUntil); + mapper.updateById(attempt); + } + } + @Override + public void clearAttempts(String clientFingerprint) { + getDbhubLoginAttemptMapper().delete( + new QueryWrapper() + .eq("client_fingerprint", clientFingerprint) + ); + } + @Override + public long getRemainingLockTime(String clientFingerprint) { + LoginAttempt attempt = getDbhubLoginAttemptMapper().findByFingerprint(clientFingerprint); + if (attempt == null || attempt.getLockedUntil() == null) { + return 0; + } + return Duration.between(Instant.now(), attempt.getLockedUntil().toInstant()) + .getSeconds() / 60; + } + + private LoginAttemptMapper getDbhubLoginAttemptMapper() { + return Dbutils.getMapper(LoginAttemptMapper.class); + } +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/OperationLogServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/OperationLogServiceImpl.java index acf8d1e98..1053563c6 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/OperationLogServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/OperationLogServiceImpl.java @@ -20,6 +20,7 @@ import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; +import ai.chat2db.server.tools.base.wrapper.ServicePage; import ai.chat2db.server.tools.common.model.EasyLambdaQueryWrapper; import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.server.tools.common.util.EasySqlUtils; @@ -50,17 +51,17 @@ private OperationLogMapper getMapper() { private DataSourceService dataSourceService; @Override - public DataResult create(OperationLogCreateParam param) { + public Long create(OperationLogCreateParam param) { OperationLogDO userExecutedDdlDO = operationLogConverter.param2do(param); userExecutedDdlDO.setGmtCreate(LocalDateTime.now()); userExecutedDdlDO.setGmtModified(LocalDateTime.now()); userExecutedDdlDO.setUserId(ContextUtils.getUserId()); getMapper().insert(userExecutedDdlDO); - return DataResult.of(userExecutedDdlDO.getId()); + return userExecutedDdlDO.getId(); } @Override - public PageResult queryPage(OperationLogPageQueryParam param) { + public ServicePage queryPage(OperationLogPageQueryParam param) { EasyLambdaQueryWrapper queryWrapper = new EasyLambdaQueryWrapper<>(); queryWrapper.likeWhenPresent(OperationLogDO::getDdl, EasySqlUtils.buildLikeRightFuzzy(param.getSearchKey())) .eqWhenPresent(OperationLogDO::getUserId, param.getUserId()) @@ -76,17 +77,17 @@ public PageResult queryPage(OperationLogPageQueryParam param) { IPage executedDdlDOIPage = getMapper().selectPage(page, queryWrapper); List executedDdlDTOS = operationLogConverter.do2dto(executedDdlDOIPage.getRecords()); if (CollectionUtils.isEmpty(executedDdlDTOS)) { - return PageResult.empty(param.getPageNo(), param.getPageSize()); + return ServicePage.empty(param.getPageNo(), param.getPageSize()); } List dataSourceIds = executedDdlDTOS.stream().map(OperationLog::getDataSourceId).toList(); - ListResult dataSourceListResult = dataSourceService.queryByIds(dataSourceIds); - Map dataSourceMap = dataSourceListResult.getData().stream().collect( + List dataSources = dataSourceService.listQuery(dataSourceIds, null); + Map dataSourceMap = dataSources.stream().collect( Collectors.toMap(DataSource::getId, Function.identity(), (a, b) -> a)); executedDdlDTOS.stream().forEach(executeDdl -> { if (dataSourceMap.containsKey(executeDdl.getDataSourceId())) { executeDdl.setDataSourceName(dataSourceMap.get(executeDdl.getDataSourceId()).getAlias()); } }); - return PageResult.of(executedDdlDTOS, executedDdlDOIPage.getTotal(), param); + return ServicePage.of(executedDdlDTOS, executedDdlDOIPage.getTotal(), param.getPageNo(), param.getPageSize()); } } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/OperationServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/OperationServiceImpl.java index 5b68ec98a..e13718b59 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/OperationServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/OperationServiceImpl.java @@ -25,6 +25,7 @@ import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; +import ai.chat2db.server.tools.base.wrapper.ServicePage; import ai.chat2db.server.tools.common.exception.DataNotFoundException; import ai.chat2db.server.tools.common.model.EasyLambdaQueryWrapper; import ai.chat2db.server.tools.common.util.ContextUtils; @@ -58,48 +59,48 @@ private OperationSavedMapper getMapper() { private DataSourceService dataSourceService; @Override - public DataResult createWithPermission(OperationSavedParam param) { + public Long createWithPermission(OperationSavedParam param) { OperationSavedDO userSavedDdlDO = operationConverter.param2do(param); userSavedDdlDO.setGmtCreate(LocalDateTime.now()); userSavedDdlDO.setGmtModified(LocalDateTime.now()); userSavedDdlDO.setUserId(ContextUtils.getUserId()); getMapper().insert(userSavedDdlDO); - return DataResult.of(userSavedDdlDO.getId()); + return userSavedDdlDO.getId(); } @Override - public ActionResult updateWithPermission(OperationUpdateParam param) { - Operation data = queryExistent(param.getId()).getData(); + public void updateWithPermission(OperationUpdateParam param) { + Operation data = queryExistent(param.getId()); PermissionUtils.checkOperationPermission(data.getUserId()); OperationSavedDO userSavedDdlDO = operationConverter.param2do(param); userSavedDdlDO.setGmtModified(LocalDateTime.now()); getMapper().updateById(userSavedDdlDO); - return ActionResult.isSuccess(); + } @Override - public DataResult find(Long id) { + public Operation find(Long id) { OperationSavedDO operationSavedDO = getMapper().selectById(id); List dataSourceIds = Lists.newArrayList(operationSavedDO.getDataSourceId()); Map dataSourceMap = getDataSourceInfo(dataSourceIds); Operation operation = operationConverter.do2dto(operationSavedDO); operation.setDataSourceName(dataSourceMap.containsKey(operation.getDataSourceId()) ? dataSourceMap.get( operation.getDataSourceId()).getAlias() : null); - return DataResult.of(operation); + return operation; } @Override - public DataResult queryExistent(Long id) { - DataResult dataResult = find(id); - if (dataResult.getData() == null) { + public Operation queryExistent(Long id) { + Operation dataResult = find(id); + if (dataResult == null) { throw new DataNotFoundException(); } return dataResult; } @Override - public DataResult queryExistent(OperationQueryParam param) { + public Operation queryExistent(OperationQueryParam param) { EasyLambdaQueryWrapper queryWrapper = new EasyLambdaQueryWrapper<>(); queryWrapper.eqWhenPresent(OperationSavedDO::getId, param.getId()) .eqWhenPresent(OperationSavedDO::getUserId, param.getUserId()); @@ -107,20 +108,20 @@ public DataResult queryExistent(OperationQueryParam param) { if (CollectionUtils.isEmpty(page.getRecords())) { throw new DataNotFoundException(); } - return DataResult.of(operationConverter.do2dto(page.getRecords().get(0))); + return operationConverter.do2dto(page.getRecords().get(0)); } @Override - public ActionResult deleteWithPermission(Long id) { - Operation data = queryExistent(id).getData(); + public void deleteWithPermission(Long id) { + Operation data = queryExistent(id); PermissionUtils.checkOperationPermission(data.getUserId()); getMapper().deleteById(id); - return ActionResult.isSuccess(); + } @Override - public PageResult queryPage(OperationPageQueryParam param) { + public ServicePage queryPage(OperationPageQueryParam param) { QueryWrapper queryWrapper = new QueryWrapper<>(); if (StringUtils.isNotBlank(param.getSearchKey())) { queryWrapper.like("name", param.getSearchKey()); @@ -156,30 +157,30 @@ public PageResult queryPage(OperationPageQueryParam param) { IPage iPage = getMapper().selectPage(page, queryWrapper); List userSavedDdlDOS = operationConverter.do2dto(iPage.getRecords()); if (CollectionUtils.isEmpty(userSavedDdlDOS)) { - return PageResult.empty(param.getPageNo(), param.getPageSize()); + return ServicePage.empty(param.getPageNo(), param.getPageSize()); } List dataSourceIds = userSavedDdlDOS.stream().map(Operation::getDataSourceId).toList(); Map dataSourceMap = getDataSourceInfo(dataSourceIds); userSavedDdlDOS.forEach(userSavedDdl -> userSavedDdl.setDataSourceName( dataSourceMap.containsKey(userSavedDdl.getDataSourceId()) ? dataSourceMap.get( userSavedDdl.getDataSourceId()).getAlias() : null)); - return PageResult.of(userSavedDdlDOS, iPage.getTotal(), param); + return ServicePage.of(userSavedDdlDOS, iPage.getTotal(), param.getPageNo(), param.getPageSize()); } /** * 查询数据源信息 * * @param dataSourceIds - * @return - */ + * @return */ private Map getDataSourceInfo(List dataSourceIds) { if (CollectionUtils.isEmpty(dataSourceIds)) { return Maps.newHashMap(); } - ListResult dataSourceListResult = dataSourceService.queryByIds(dataSourceIds); - Map dataSourceMap = dataSourceListResult.getData().stream().collect( + List dataSourceListResult = dataSourceService.listQuery(dataSourceIds, null); + Map dataSourceMap = dataSourceListResult.stream().collect( Collectors.toMap(DataSource::getId, Function.identity(), (a, b) -> a)); return dataSourceMap; } } + diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/PinServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/PinServiceImpl.java index 6d0a4daaf..f26b5693b 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/PinServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/PinServiceImpl.java @@ -7,7 +7,6 @@ import ai.chat2db.server.domain.repository.entity.PinTableDO; import ai.chat2db.server.domain.repository.mapper.PinTableMapper; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; -import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.common.util.ContextUtils; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; @@ -58,7 +57,7 @@ public ActionResult deletePinTable(PinTableParam param) { } @Override - public ListResult queryPinTables(PinTableParam param) { + public List queryPinTables(PinTableParam param) { List result = new ArrayList<>(); LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(PinTableDO::getUserId, param.getUserId()); @@ -77,6 +76,6 @@ public ListResult queryPinTables(PinTableParam param) { if (!CollectionUtils.isEmpty(list)) { result = list.stream().map(pinTableDO -> pinTableDO.getTableName()).collect(Collectors.toList()); } - return ListResult.of(result); + return result; } } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ProcedureServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ProcedureServiceImpl.java index c6bafe71e..ef955ae6a 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ProcedureServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ProcedureServiceImpl.java @@ -1,22 +1,125 @@ package ai.chat2db.server.domain.core.impl; +import java.sql.Connection; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import ai.chat2db.server.domain.api.model.TreeNode; +import ai.chat2db.server.domain.api.param.TreeSearchParam; import ai.chat2db.server.domain.api.service.ProcedureService; +import ai.chat2db.server.domain.core.cache.LuceneIndexManager; +import ai.chat2db.server.domain.core.cache.LuceneIndexManagerFactory; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; +import ai.chat2db.spi.MetaData; import ai.chat2db.spi.model.Procedure; import ai.chat2db.spi.sql.Chat2DBContext; -import org.springframework.stereotype.Service; +import lombok.extern.slf4j.Slf4j; @Service +@Slf4j public class ProcedureServiceImpl implements ProcedureService { + @Autowired + private LuceneIndexManagerFactory managerFactory; + @Override - public ListResult procedures(String databaseName, String schemaName) { - return ListResult.of(Chat2DBContext.getMetaData().procedures(Chat2DBContext.getConnection(),databaseName, schemaName)); + public List procedures(String databaseName, String schemaName) { + return Chat2DBContext.getMetaData().procedures(Chat2DBContext.getConnection(),databaseName, schemaName); } @Override - public DataResult detail(String databaseName, String schemaName, String procedureName) { - return DataResult.of(Chat2DBContext.getMetaData().procedure(Chat2DBContext.getConnection(), databaseName, schemaName, procedureName)); + public List proceduresWithCache(Long dataSourceId, String databaseName, String schemaName, String searchKey, boolean refresh) { + LuceneIndexManager mgr = managerFactory.getManager(dataSourceId); + Procedure queryModel = Procedure.builder() + .databaseName(databaseName) + .schemaName(schemaName) + .build(); + Long version = mgr.getMaxVersion(queryModel); + + if (refresh || version == null) { + loadAndCacheMetadata(mgr, databaseName, schemaName, version); + } + + return mgr.search(queryModel, null, searchKey); + } + + @Override + public Procedure detail(String databaseName, String schemaName, String procedureName) { + return Chat2DBContext.getMetaData().procedure(Chat2DBContext.getConnection(), databaseName, schemaName, procedureName); + } + + @Override + public List searchTreeNodes(TreeSearchParam param) { + LuceneIndexManager mgr = managerFactory.getManager(param.getDataSourceId()); + Procedure queryModel = Procedure.builder() + .databaseName(param.getDatabaseName()) + .schemaName(param.getSchemaName()) + .build(); + Long version = mgr.getMaxVersion(queryModel); + + if (param.isRefresh() || version == null) { + loadAndCacheMetadata(mgr, param.getDatabaseName(), param.getSchemaName(), version); + } + + List procedures = mgr.search(queryModel, null, param.getSearchKey()); + List result = new ArrayList<>(); + for (Procedure procedure : procedures) { + TreeNode node = buildTreeNode(procedure); + result.add(node); + } + return result; + } + + private void loadAndCacheMetadata(LuceneIndexManager mgr, String databaseName, String schemaName, Long currentVersion) { + mgr.getLock().writeLock().lock(); + try { + Connection conn = Chat2DBContext.getConnection(); + MetaData meta = Chat2DBContext.getMetaData(); + List procedures = meta.procedures(conn, databaseName, schemaName); + if (CollectionUtils.isEmpty(procedures)) { + mgr.deleteByDatabaseAndSchema(databaseName, schemaName); + return; + } + mgr.updateDocuments(procedures, currentVersion); + log.info("[Procedure] Cached {} procedures for database: {}", procedures.size(), databaseName); + } catch (Exception e) { + log.error("[Procedure] loadAndCacheMetadata error", e); + } finally { + mgr.getLock().writeLock().unlock(); + } + } + + private TreeNode buildTreeNode(Procedure procedure) { + List parentPath = new ArrayList<>(); + if (StringUtils.isNotBlank(procedure.getDatabaseName())) { + parentPath.add(procedure.getDatabaseName()); + } + if (StringUtils.isNotBlank(procedure.getSchemaName())) { + parentPath.add(procedure.getSchemaName()); + } + + Map extraParams = new HashMap<>(); + extraParams.put("databaseName", procedure.getDatabaseName()); + extraParams.put("schemaName", procedure.getSchemaName()); + extraParams.put("procedureName", procedure.getName()); + + return TreeNode.builder() + .uuid("procedure-" + procedure.getName()) + .key(procedure.getName()) + .name(procedure.getName()) + .treeNodeType("procedure") + .comment(procedure.getComment()) + .isLeaf(true) + .parentPath(parentPath) + .extraParams(extraParams) + .build(); } -} +} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/SchemaDiffServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/SchemaDiffServiceImpl.java new file mode 100644 index 000000000..dda2fc891 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/SchemaDiffServiceImpl.java @@ -0,0 +1,1460 @@ +package ai.chat2db.server.domain.core.impl; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.function.BiPredicate; +import java.util.function.Function; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import ai.chat2db.spi.SqlBuilder; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import ai.chat2db.server.domain.api.model.DataSource; +import ai.chat2db.server.domain.api.model.schemaDiff.ColumnDiff; +import ai.chat2db.server.domain.api.model.schemaDiff.DiffSummary; +import ai.chat2db.server.domain.api.model.schemaDiff.FieldDiff; +import ai.chat2db.server.domain.api.model.schemaDiff.ForeignKeyDiff; +import ai.chat2db.server.domain.api.model.schemaDiff.IndexDiff; +import ai.chat2db.server.domain.api.model.schemaDiff.SchemaDiffResult; +import ai.chat2db.server.domain.api.model.schemaDiff.TableDiff; +import ai.chat2db.server.domain.api.model.schemaDiff.TableDiffType; +import ai.chat2db.server.domain.api.param.DeprecatedTableParam; +import ai.chat2db.server.domain.api.param.schemaDiff.CompareOption; +import ai.chat2db.server.domain.api.param.schemaDiff.MigrateResult; +import ai.chat2db.server.domain.api.param.schemaDiff.MigrationStatementResult; +import ai.chat2db.server.domain.api.param.schemaDiff.SchemaCompareParam; +import ai.chat2db.server.domain.api.param.schemaDiff.SchemaMigrateParam; +import ai.chat2db.server.domain.api.service.DataSourceAccessBusinessService; +import ai.chat2db.server.domain.api.service.DataSourceService; +import ai.chat2db.server.domain.api.service.DeprecatedTableService; +import ai.chat2db.server.domain.api.service.SchemaDiffService; +import ai.chat2db.server.tools.common.util.ContextUtils; +import ai.chat2db.spi.MetaData; +import ai.chat2db.spi.Plugin; +import ai.chat2db.spi.enums.EditStatus; +import ai.chat2db.spi.model.ForeignKey; +import ai.chat2db.spi.model.Table; +import ai.chat2db.spi.model.TableColumn; +import ai.chat2db.spi.model.TableIndex; +import ai.chat2db.spi.sql.Chat2DBContext; +import ai.chat2db.spi.sql.ConnectInfo; +import lombok.extern.slf4j.Slf4j; + +/** + * Schema diff and migration service implementation. + * Provides functionality to compare database structures between source and target, + * and execute DDL migration statements. + */ +@Service +@Slf4j +public class SchemaDiffServiceImpl implements SchemaDiffService { + + private static final Pattern MYSQL_INTEGER_COLUMN_TYPE_WITH_WIDTH = Pattern.compile( + "(?i)^(tinyint|smallint|mediumint|int|integer|bigint)\\s*\\(\\s*\\d+\\s*\\)(\\s+unsigned)?$"); + + @Autowired + private DataSourceService dataSourceService; + + @Autowired + private DataSourceAccessBusinessService dataSourceAccessBusinessService; + + @Autowired + private DeprecatedTableService deprecatedTableService; + + /** + * Compare database structures between source and target. + * Supports case-sensitive and case-insensitive comparison modes. + * + * @param param comparison parameters including source/target connection info and options + * @return schema diff result containing table differences and DDL statements + */ + /** + * 数据库比较上下文,封装比较过程中的所有必要信息 + */ + private static class CompareContext { + final Plugin sourcePlugin; + final Plugin targetPlugin; + final MetaData sourceMeta; + final MetaData targetMeta; + final Connection sourceConn; + final Connection targetConn; + final CompareOption option; + final Set sourceDeprecated; + final Set targetDeprecated; + final List sourceTableNames; + final List targetTableNames; + final Map sourceTableDetails; + final Map targetTableDetails; + + CompareContext(Plugin sourcePlugin, Plugin targetPlugin, MetaData sourceMeta, MetaData targetMeta, + Connection sourceConn, Connection targetConn, CompareOption option, + Set sourceDeprecated, Set targetDeprecated, + List sourceTableNames, List targetTableNames, + Map sourceTableDetails, Map targetTableDetails) { + this.sourcePlugin = sourcePlugin; + this.targetPlugin = targetPlugin; + this.sourceMeta = sourceMeta; + this.targetMeta = targetMeta; + this.sourceConn = sourceConn; + this.targetConn = targetConn; + this.option = option; + this.sourceDeprecated = sourceDeprecated; + this.targetDeprecated = targetDeprecated; + this.sourceTableNames = sourceTableNames; + this.targetTableNames = targetTableNames; + this.sourceTableDetails = sourceTableDetails; + this.targetTableDetails = targetTableDetails; + } + } + + /** + * 表比较结果统计 + */ + private static class CompareStats { + int onlyInSource = 0; + int onlyInTarget = 0; + int modified = 0; + int unchanged = 0; + } + + @Override + public SchemaDiffResult compare(SchemaCompareParam param) { + long startTime = System.currentTimeMillis(); + log.info("Schema compare started, source: {}, target: {}", + param.getSourceDataSourceId(), param.getTargetDataSourceId()); + + // 步骤1:获取数据库插件 + final Plugin sourcePlugin = getPlugin(param.getSourceDataSourceId()); + final Plugin targetPlugin = getPlugin(param.getTargetDataSourceId()); + final MetaData sourceMeta = sourcePlugin.getMetaData(); + final MetaData targetMeta = targetPlugin.getMetaData(); + + // 步骤2:创建数据库连接(使用 try-with-resources 自动关闭) + long connStartTime = System.currentTimeMillis(); + try (Connection sourceConn = createConnection(param.getSourceDataSourceId(), param.getSourceDatabaseName(), + param.getSourceSchemaName()); + Connection targetConn = createConnection(param.getTargetDataSourceId(), param.getTargetDatabaseName(), + param.getTargetSchemaName())) { + + log.info("Connections created in {} ms", System.currentTimeMillis() - connStartTime); + + // 步骤3:初始化比较选项和废弃表 + final CompareOption option = validateAndGetOption(param.getCompareOption()); + final Set sourceDeprecated = queryDeprecatedIfNeeded(param, option, true); + final Set targetDeprecated = queryDeprecatedIfNeeded(param, option, false); + + // 步骤4:并行获取表列表 + List sourceTableNames = fetchTableListsParallel( + sourceConn, sourceMeta, param, sourceDeprecated, true); + List targetTableNames = fetchTableListsParallel( + targetConn, targetMeta, param, targetDeprecated, false); + + // 步骤5:批量获取表详情 + Map sourceTableDetails = batchFetchTableDetails( + sourceConn, sourceMeta, param.getSourceDatabaseName(), + param.getSourceSchemaName(), sourceTableNames); + Map targetTableDetails = batchFetchTableDetails( + targetConn, targetMeta, param.getTargetDatabaseName(), + param.getTargetSchemaName(), targetTableNames); + + // 步骤6:构建比较上下文 + CompareContext context = new CompareContext( + sourcePlugin, targetPlugin, sourceMeta, targetMeta, + sourceConn, targetConn, option, + sourceDeprecated, targetDeprecated, + sourceTableNames, targetTableNames, + sourceTableDetails, targetTableDetails); + + // 步骤7:并行比较所有表 + List tableDiffs = compareTablesParallel(context); + CompareStats stats = calculateStats(tableDiffs); + + // 步骤8:构建结果 + return buildCompareResult(param, context, tableDiffs, stats, startTime); + + } catch (Exception e) { + log.error("Schema compare failed", e); + throw new RuntimeException("Schema compare failed: " + e.getMessage(), e); + } + } + + /** + * 获取数据库插件并验证 + */ + private Plugin getPlugin(Long dataSourceId) { + String dbType = getDbType(dataSourceId); + Plugin plugin = Chat2DBContext.PLUGIN_MAP.get(dbType); + if (plugin == null) { + throw new RuntimeException("Plugin not found for database type: " + dbType); + } + return plugin; + } + + /** + * 验证并获取比较选项 + */ + private CompareOption validateAndGetOption(CompareOption option) { + if (option == null) { + throw new RuntimeException("Compare option is required"); + } + return option; + } + + /** + * 根据选项查询废弃表 + */ + private Set queryDeprecatedIfNeeded(SchemaCompareParam param, CompareOption option, boolean isSource) { + if (!option.isExcludeDeprecated()) { + return Collections.emptySet(); + } + + Long dataSourceId = isSource ? param.getSourceDataSourceId() : param.getTargetDataSourceId(); + String databaseName = isSource ? param.getSourceDatabaseName() : param.getTargetDatabaseName(); + String schemaName = isSource ? param.getSourceSchemaName() : param.getTargetSchemaName(); + + return queryDeprecatedTableNames(dataSourceId, databaseName, schemaName); + } + + /** + * 并行获取表列表 + */ + private List fetchTableListsParallel(Connection conn, MetaData meta, SchemaCompareParam param, + Set deprecatedSet, boolean isSource) { + long tableListTime = System.currentTimeMillis(); + + CompletableFuture> tablesFuture = CompletableFuture.supplyAsync(() -> + listTableNames(conn, meta, + isSource ? param.getSourceDatabaseName() : param.getTargetDatabaseName(), + isSource ? param.getSourceSchemaName() : param.getTargetSchemaName(), + param.getTableNames(), deprecatedSet)); + + List tableNames = tablesFuture.join(); + log.info("Table lists fetched in {} ms ({}: {} tables)", + System.currentTimeMillis() - tableListTime, + isSource ? "source" : "target", tableNames.size()); + + return tableNames; + } + + /** + * 并行比较所有表 + */ + private List compareTablesParallel(CompareContext context) { + long compareTime = System.currentTimeMillis(); + + // 构建表名映射 + Function normalizer = context.option.isCaseSensitive() + ? Function.identity() + : String::toLowerCase; + + Map sourceTableMap = context.sourceTableNames.stream() + .collect(Collectors.toMap(normalizer, n -> n, (a, b) -> a)); + Map targetTableMap = context.targetTableNames.stream() + .collect(Collectors.toMap(normalizer, n -> n, (a, b) -> a)); + + Set allNormalizedNames = new HashSet<>(); + allNormalizedNames.addAll(sourceTableMap.keySet()); + allNormalizedNames.addAll(targetTableMap.keySet()); + + log.info("Comparing {} tables in parallel", allNormalizedNames.size()); + + // 创建并行比较任务 + List> diffFutures = allNormalizedNames.stream() + .map(normalizedName -> createCompareTask(normalizedName, sourceTableMap, targetTableMap, context)) + .collect(Collectors.toList()); + + // 收集结果 + List tableDiffs = diffFutures.stream() + .map(CompletableFuture::join) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + log.info("Tables compared in {} ms", System.currentTimeMillis() - compareTime); + return tableDiffs; + } + + /** + * 创建单个表的比较任务 + */ + private CompletableFuture createCompareTask(String normalizedName, + Map sourceTableMap, + Map targetTableMap, + CompareContext context) { + String sourceTableName = sourceTableMap.get(normalizedName); + String targetTableName = targetTableMap.get(normalizedName); + boolean inSource = sourceTableName != null; + boolean inTarget = targetTableName != null; + + final String sourceName = sourceTableName; + final String targetName = targetTableName; + final boolean inSourceFinal = inSource; + final boolean inTargetFinal = inTarget; + + return CompletableFuture.supplyAsync(() -> { + // 仅在源库 -> ADDED + if (inSourceFinal && !inTargetFinal) { + return compareTableOnlyInSource(sourceName, context); + } + // 仅在目标库 -> REMOVED + else if (!inSourceFinal && inTargetFinal) { + return compareTableOnlyInTarget(targetName, context); + } + // 两边都存在 -> 比较详情 + else { + return compareTableInBoth(sourceName, targetName, context); + } + }); + } + + /** + * 比较仅在源库中存在的表 + */ + private TableDiff compareTableOnlyInSource(String sourceName, CompareContext context) { + Table sourceTable = context.sourceTableDetails.get(sourceName); + if (sourceTable == null) { + log.warn("Source table {} not found, skipping", sourceName); + return null; + } + + String ddl = context.sourcePlugin.getMetaData().getSqlBuilder().buildCreateTableSql(sourceTable); + return TableDiff.builder() + .tableName(sourceName) + .diffType(TableDiffType.ADDED) + .sourceTable(sourceTable) + .ddlStatement(ddl) + .ddlStatements(Collections.singletonList(ddl)) + .build(); + } + + /** + * 比较仅在目标库中存在的表 + */ + private TableDiff compareTableOnlyInTarget(String targetName, CompareContext context) { + Table targetTable = context.targetTableDetails.get(targetName); + if (targetTable == null) { + log.warn("Target table {} not found, skipping", targetName); + return null; + } + + return TableDiff.builder() + .tableName(targetName) + .diffType(TableDiffType.REMOVED) + .targetTable(targetTable) + .ddlStatements(Collections.emptyList()) + .build(); + } + + /** + * 比较两边都存在的表 + */ + private TableDiff compareTableInBoth(String sourceName, String targetName, CompareContext context) { + Table sourceTable = context.sourceTableDetails.get(sourceName); + Table targetTable = context.targetTableDetails.get(targetName); + + if (sourceTable == null || targetTable == null) { + log.warn("Table details missing: source={}, target={}", sourceName, targetName); + return null; + } + + // 比较列、索引、外键 + List columnDiffs = context.option.isCompareColumn() + ? compareColumns(sourceTable.getColumnList(), targetTable.getColumnList(), context.option) + : Collections.emptyList(); + List indexDiffs = context.option.isCompareIndex() + ? compareIndexes(sourceTable.getIndexList(), targetTable.getIndexList(), context.option.isCaseSensitive()) + : Collections.emptyList(); + List fkDiffs = context.option.isCompareForeignKey() + ? compareForeignKeys(sourceTable.getForeignKeyList(), targetTable.getForeignKeyList(), context.option.isCaseSensitive()) + : Collections.emptyList(); + + boolean hasChanges = !columnDiffs.isEmpty() || !indexDiffs.isEmpty() || !fkDiffs.isEmpty(); + + if (context.option.isCompareTableOption()) { + hasChanges = hasChanges || !tableOptionsEqual(sourceTable, targetTable, context.option); + } + + if (hasChanges) { + logSchemaDiffDetails(sourceName, sourceTable, targetTable, columnDiffs, indexDiffs, fkDiffs, context); + return buildModifiedTableDiff(sourceName, sourceTable, targetTable, + columnDiffs, indexDiffs, fkDiffs, context); + } else { + return TableDiff.builder() + .tableName(sourceName) + .diffType(TableDiffType.UNCHANGED) + .sourceTable(sourceTable) + .targetTable(targetTable) + .build(); + } + } + + private void logSchemaDiffDetails(String tableName, Table sourceTable, Table targetTable, + List columnDiffs, List indexDiffs, + List fkDiffs, CompareContext context) { + if (context.option.isCompareColumn()) { + for (ColumnDiff diff : columnDiffs) { + String columnName = getColumnDiffName(diff); + log.info("Schema compare column diff, table: {}, column: {}, changeType: {}, changedFields: {}", + tableName, columnName, diff.getChangeType(), formatChangedFields(diff.getChangedFields())); + } + } + if (context.option.isCompareIndex()) { + for (IndexDiff diff : indexDiffs) { + String indexName = getIndexDiffName(diff); + log.info("Schema compare index diff, table: {}, index: {}, changeType: {}, changedFields: {}", + tableName, indexName, diff.getChangeType(), formatChangedFields(diff.getChangedFields())); + } + } + if (context.option.isCompareForeignKey()) { + for (ForeignKeyDiff diff : fkDiffs) { + String fkName = getForeignKeyDiffName(diff); + log.info("Schema compare foreign key diff, table: {}, foreignKey: {}, changeType: {}, changedFields: {}", + tableName, fkName, diff.getChangeType(), formatChangedFields(diff.getChangedFields())); + } + } + if (context.option.isCompareTableOption() && !tableOptionsEqual(sourceTable, targetTable, context.option)) { + log.info("Schema compare table option diff, table: {}, changedFields: {}", + tableName, formatChangedFields(describeTableOptionDiff(sourceTable, targetTable, context.option))); + } + } + + /** + * 构建有变更的表差异 + */ + private TableDiff buildModifiedTableDiff(String sourceName, Table sourceTable, Table targetTable, + List columnDiffs, List indexDiffs, + List fkDiffs, CompareContext context) { + Table tableForDDL = buildTableWithEditStatus(sourceTable, targetTable, + columnDiffs, indexDiffs, fkDiffs, context.option); + SqlBuilder sqlBuilder = context.targetPlugin.getMetaData().getSqlBuilder(); + String alterDdl = sqlBuilder.buildModifyTaleSql(targetTable, tableForDDL); + List tableOptionDiffs = context.option.isCompareTableOption() + ? describeTableOptionDiff(sourceTable, targetTable, context.option) + : Collections.emptyList(); + + return TableDiff.builder() + .tableName(sourceName) + .diffType(TableDiffType.MODIFIED) + .sourceTable(sourceTable) + .targetTable(targetTable) + .columnDiffs(columnDiffs) + .indexDiffs(indexDiffs) + .foreignKeyDiffs(fkDiffs) + .tableOptionDiffs(tableOptionDiffs) + .ddlStatement(alterDdl) + .ddlStatements(StringUtils.isNotBlank(alterDdl) + ? Collections.singletonList(alterDdl) + : Collections.emptyList()) + .build(); + } + + /** + * 计算比较统计 + */ + private CompareStats calculateStats(List tableDiffs) { + CompareStats stats = new CompareStats(); + for (TableDiff diff : tableDiffs) { + switch (diff.getDiffType()) { + case ADDED: + stats.onlyInSource++; + break; + case REMOVED: + stats.onlyInTarget++; + break; + case MODIFIED: + stats.modified++; + break; + case UNCHANGED: + stats.unchanged++; + break; + } + } + return stats; + } + + /** + * 构建比较结果 + */ + private SchemaDiffResult buildCompareResult(SchemaCompareParam param, CompareContext context, + List tableDiffs, CompareStats stats, long startTime) { + // 计算排除的废弃表数量 + int excluded = 0; + if (context.option.isExcludeDeprecated()) { + excluded = (int) (context.sourceDeprecated.size() + context.targetDeprecated.size() + - new HashSet<>(context.sourceDeprecated).stream() + .filter(context.targetDeprecated::contains).count()); + } + + // 计算总表数 + Function normalizer = context.option.isCaseSensitive() + ? Function.identity() + : String::toLowerCase; + Set allNormalizedNames = new HashSet<>(); + allNormalizedNames.addAll(context.sourceTableNames.stream().map(normalizer).collect(Collectors.toSet())); + allNormalizedNames.addAll(context.targetTableNames.stream().map(normalizer).collect(Collectors.toSet())); + + DiffSummary summary = DiffSummary.builder() + .totalTables(allNormalizedNames.size() + excluded) + .tablesOnlyInSource(stats.onlyInSource) + .tablesOnlyInTarget(stats.onlyInTarget) + .modifiedTables(stats.modified) + .unchangedTables(stats.unchanged) + .excludedDeprecatedTables(excluded) + .build(); + List changedTableDiffs = tableDiffs.stream() + .filter(tableDiff -> tableDiff.getDiffType() != TableDiffType.UNCHANGED) + .collect(Collectors.toList()); + + String sourceKey = param.getSourceDataSourceId() + "." + param.getSourceDatabaseName() + + (param.getSourceSchemaName() != null ? "." + param.getSourceSchemaName() : ""); + String targetKey = param.getTargetDataSourceId() + "." + param.getTargetDatabaseName() + + (param.getTargetSchemaName() != null ? "." + param.getTargetSchemaName() : ""); + + long totalTime = System.currentTimeMillis() - startTime; + log.info("Schema compare completed in {} ms. Total tables: {}, Added: {}, Removed: {}, Modified: {}, Unchanged: {}, Returned: {}", + totalTime, summary.getTotalTables(), stats.onlyInSource, stats.onlyInTarget, + stats.modified, stats.unchanged, changedTableDiffs.size()); + + return SchemaDiffResult.builder() + .sourceKey(sourceKey) + .targetKey(targetKey) + .summary(summary) + .tableDiffs(changedTableDiffs) + .build(); + } + + /** + * Execute DDL migration statements on the target database. + * Supports transaction mode and continue-on-error mode. + * + * @param param migration parameters including DDL statements and execution options + * @return migration result with per-statement execution status + */ + @Override + public MigrateResult migrate(SchemaMigrateParam param) { + if (CollectionUtils.isEmpty(param.getDdlStatements())) { + return MigrateResult.builder() + .success(true) + .totalStatements(0) + .successCount(0) + .failCount(0) + .statementResults(Collections.emptyList()) + .build(); + } + + Connection conn = null; + boolean originalAutoCommit = true; + ConnectInfo previousInfo = Chat2DBContext.getConnectInfo(); + try { + ConnectInfo connectInfo = createConnectInfo(param.getTargetDataSourceId(), param.getTargetDatabaseName(), + param.getTargetSchemaName()); + Chat2DBContext.putContext(connectInfo); + + Plugin plugin = Chat2DBContext.PLUGIN_MAP.get(connectInfo.getDbType()); + conn = plugin.getDBManage().getConnection(connectInfo); + connectInfo.setConnection(conn); + + if (param.isExecuteInTransaction()) { + originalAutoCommit = conn.getAutoCommit(); + conn.setAutoCommit(false); + } + + ai.chat2db.spi.CommandExecutor executor = plugin.getMetaData().getCommandExecutor(); + + List results = new ArrayList<>(); + int successCount = 0; + int failCount = 0; + + for (int i = 0; i < param.getDdlStatements().size(); i++) { + String sql = param.getDdlStatements().get(i); + if (StringUtils.isBlank(sql)) { + continue; + } + long start = System.currentTimeMillis(); + try { + ai.chat2db.spi.model.Command command = new ai.chat2db.spi.model.Command(); + command.setScript(sql); + List executeResults = executor.execute(command); + long duration = System.currentTimeMillis() - start; + boolean statementSuccess = executeResults != null + && executeResults.stream().allMatch(r -> r.getSuccess() != null && r.getSuccess()); + if (statementSuccess) { + successCount++; + } else { + failCount++; + if (!param.isContinueOnError()) { + String msg = executeResults != null && !executeResults.isEmpty() + ? executeResults.get(0).getMessage() + : "Unknown error"; + results.add(MigrationStatementResult.builder() + .sequence(i + 1) + .sql(sql) + .success(false) + .errorMessage(msg) + .duration(duration) + .build()); + if (param.isExecuteInTransaction()) { + conn.rollback(); + } + break; + } + } + results.add(MigrationStatementResult.builder() + .sequence(i + 1) + .sql(sql) + .success(statementSuccess) + .errorMessage(statementSuccess ? null : (executeResults != null && !executeResults.isEmpty() + ? executeResults.get(0).getMessage() : "Unknown error")) + .duration(duration) + .build()); + } catch (Exception e) { + long duration = System.currentTimeMillis() - start; + failCount++; + results.add(MigrationStatementResult.builder() + .sequence(i + 1) + .sql(sql) + .success(false) + .errorMessage(e.getMessage()) + .duration(duration) + .build()); + if (param.isExecuteInTransaction()) { + conn.rollback(); + } + if (!param.isContinueOnError()) { + break; + } + } + } + + if (param.isExecuteInTransaction() && failCount == 0) { + conn.commit(); + } + + return MigrateResult.builder() + .success(failCount == 0) + .statementResults(results) + .totalStatements(results.size()) + .successCount(successCount) + .failCount(failCount) + .build(); + } catch (SQLException e) { + log.error("Migration failed with SQL error", e); + throw new RuntimeException("Migration failed: " + e.getMessage(), e); + } finally { + if (conn != null && param.isExecuteInTransaction()) { + try { + conn.setAutoCommit(originalAutoCommit); + } catch (SQLException e) { + log.warn("Failed to restore auto-commit", e); + } + } + closeQuietly(conn); + if (previousInfo != null) { + Chat2DBContext.putContext(previousInfo); + } else { + Chat2DBContext.remove(); + } + } + } + + /** + * Get database type from data source. + */ + private String getDbType(Long dataSourceId) { + DataSource ds = dataSourceService.queryById(dataSourceId); + return ds.getType(); + } + + /** + * Create a database connection with proper context management. + * Sets up Chat2DBContext for the plugin to access connection info. + */ + private Connection createConnection(Long dataSourceId, String databaseName, String schemaName) { + ConnectInfo connectInfo = createConnectInfo(dataSourceId, databaseName, schemaName); + Plugin plugin = Chat2DBContext.PLUGIN_MAP.get(connectInfo.getDbType()); + + ConnectInfo previousInfo = Chat2DBContext.getConnectInfo(); + try { + Chat2DBContext.putContext(connectInfo); + return plugin.getDBManage().getConnection(connectInfo); + } finally { + if (previousInfo != null) { + Chat2DBContext.putContext(previousInfo); + } else { + Chat2DBContext.remove(); + } + } + } + + private ConnectInfo createConnectInfo(Long dataSourceId, String databaseName, String schemaName) { + DataSource dataSource = dataSourceService.queryById(dataSourceId); + if (dataSource == null) { + throw new RuntimeException("DataSource not found: " + dataSourceId); + } + dataSourceAccessBusinessService.checkPermission(dataSource); + + ConnectInfo connectInfo = new ConnectInfo(); + connectInfo.setDataSourceId(dataSourceId); + connectInfo.setUser(dataSource.getUserName()); + connectInfo.setPassword(dataSource.getPassword()); + connectInfo.setDbType(dataSource.getType()); + connectInfo.setUrl(dataSource.getUrl()); + connectInfo.setDatabase(databaseName); + connectInfo.setSchemaName(schemaName); + connectInfo.setDriver(dataSource.getDriver()); + connectInfo.setSsh(dataSource.getSsh()); + connectInfo.setSsl(dataSource.getSsl()); + connectInfo.setJdbc(dataSource.getJdbc()); + connectInfo.setExtendInfo(dataSource.getExtendInfo()); + connectInfo.setHost(dataSource.getHost()); + if (StringUtils.isNotBlank(dataSource.getPort())) { + connectInfo.setPort(Integer.parseInt(dataSource.getPort())); + } + ai.chat2db.spi.config.DriverConfig driverConfig = dataSource.getDriverConfig(); + if (driverConfig == null) { + driverConfig = Chat2DBContext.getDefaultDriverConfig(dataSource.getType()); + } + connectInfo.setDriverConfig(driverConfig); + connectInfo.setConsoleOwn(false); + return connectInfo; + } + + /** + * Query deprecated table names for exclusion during comparison. + */ + private Set queryDeprecatedTableNames(Long dataSourceId, String databaseName, String schemaName) { + DeprecatedTableParam param = new DeprecatedTableParam(); + param.setUserId(ContextUtils.getUserId()); + param.setDataSourceId(dataSourceId); + param.setDatabaseName(databaseName); + param.setSchemaName(schemaName); + List list = deprecatedTableService.queryDeprecatedTables(param); + return new HashSet<>(list); + } + + /** + * List table names from database metadata, filtering by specific tables and deprecated set. + */ + private List listTableNames(Connection conn, MetaData meta, String databaseName, String schemaName, + List specificTables, Set deprecatedSet) { + List
tables = meta.tables(conn, databaseName, schemaName, null); + if (CollectionUtils.isEmpty(tables)) { + return Collections.emptyList(); + } + return tables.stream() + .map(Table::getName) + .filter(name -> CollectionUtils.isEmpty(specificTables) || specificTables.contains(name)) + .filter(name -> !deprecatedSet.contains(name)) + .collect(Collectors.toList()); + } + + /** + * 批量获取所有表的完整详情(列、索引、外键)。 + * 核心优化:通过一次查询获取所有表的元数据,避免 N+1 查询问题。 + * 优化前:每个表 4 次查询(表信息 + 列 + 索引 + 外键),N 个表 = 4N 次查询 + * 优化后:每个类型 1 次查询,总共 4 次查询(表 + 列 + 索引 + 外键) + * + * @param conn 数据库连接 + * @param meta 元数据访问接口 + * @param databaseName 数据库名 + * @param schemaName 模式名 + * @param tableNames 需要获取详情的表名列表 + * @return Map<表名, 表详情> + */ + private Map batchFetchTableDetails(Connection conn, MetaData meta, + String databaseName, String schemaName, + List tableNames) { + if (CollectionUtils.isEmpty(tableNames)) { + return Collections.emptyMap(); + } + + Map tableMap = new HashMap<>(); + + // 第一步:批量获取所有表的基本信息 + try { + List
tables = meta.tables(conn, databaseName, schemaName, null); + if (CollectionUtils.isNotEmpty(tables)) { + for (Table table : tables) { + if (tableNames.contains(table.getName())) { + tableMap.put(table.getName(), table); + } + } + } + } catch (Exception e) { + log.error("Failed to batch fetch tables: {}", e.getMessage()); + } + + if (tableMap.isEmpty()) { + return Collections.emptyMap(); + } + + // 第二步:批量获取所有列并按表名分组 + try { + List allColumns = meta.columns(conn, databaseName, schemaName, null); + if (CollectionUtils.isNotEmpty(allColumns)) { + Map> columnsByTable = allColumns.stream() + .filter(col -> col.getTableName() != null) + .collect(Collectors.groupingBy(TableColumn::getTableName)); + + for (Map.Entry entry : tableMap.entrySet()) { + List columns = columnsByTable.getOrDefault(entry.getKey(), Collections.emptyList()); + entry.getValue().setColumnList(columns); + } + } + } catch (Exception e) { + log.warn("Failed to batch fetch columns: {}", e.getMessage()); + // 如果批量获取失败,逐个表获取作为降级方案 + for (String tableName : tableNames) { + Table table = tableMap.get(tableName); + if (table != null) { + try { + table.setColumnList(meta.columns(conn, databaseName, schemaName, tableName)); + } catch (Exception ex) { + log.warn("Failed to fetch columns for table {}: {}", tableName, ex.getMessage()); + table.setColumnList(Collections.emptyList()); + } + } + } + } + + // 第三步:批量获取所有索引并按表名分组 + try { + List allIndexes = meta.indexes(conn, databaseName, schemaName, null); + if (CollectionUtils.isNotEmpty(allIndexes)) { + Map> indexesByTable = allIndexes.stream() + .filter(idx -> idx.getTableName() != null) + .collect(Collectors.groupingBy(TableIndex::getTableName)); + + for (Map.Entry entry : tableMap.entrySet()) { + List indexes = indexesByTable.getOrDefault(entry.getKey(), Collections.emptyList()); + entry.getValue().setIndexList(indexes); + } + } + } catch (Exception e) { + log.warn("Failed to batch fetch indexes: {}", e.getMessage()); + for (String tableName : tableNames) { + Table table = tableMap.get(tableName); + if (table != null) { + try { + table.setIndexList(meta.indexes(conn, databaseName, schemaName, tableName)); + } catch (Exception ex) { + log.warn("Failed to fetch indexes for table {}: {}", tableName, ex.getMessage()); + table.setIndexList(Collections.emptyList()); + } + } + } + } + + // 第四步:批量获取所有外键并按表名分组 + try { + List allFKs = meta.foreignKeys(conn, databaseName, schemaName, null); + if (CollectionUtils.isNotEmpty(allFKs)) { + Map> fksByTable = allFKs.stream() + .filter(fk -> fk.getTableName() != null) + .collect(Collectors.groupingBy(ForeignKey::getTableName)); + + for (Map.Entry entry : tableMap.entrySet()) { + List fks = fksByTable.getOrDefault(entry.getKey(), Collections.emptyList()); + entry.getValue().setForeignKeyList(fks); + } + } + } catch (Exception e) { + log.warn("Failed to batch fetch foreign keys: {}", e.getMessage()); + for (String tableName : tableNames) { + Table table = tableMap.get(tableName); + if (table != null) { + try { + table.setForeignKeyList(meta.foreignKeys(conn, databaseName, schemaName, tableName)); + } catch (Exception ex) { + log.warn("Failed to fetch foreign keys for table {}: {}", tableName, ex.getMessage()); + table.setForeignKeyList(Collections.emptyList()); + } + } + } + } + + // 确保所有表都有非空的列表 + for (Table table : tableMap.values()) { + if (table.getColumnList() == null) { + table.setColumnList(Collections.emptyList()); + } + if (table.getIndexList() == null) { + table.setIndexList(Collections.emptyList()); + } + if (table.getForeignKeyList() == null) { + table.setForeignKeyList(Collections.emptyList()); + } + } + + return tableMap; + } + + /** + * Fetch complete table details including columns, indexes, and foreign keys. + * Handles fetch errors gracefully by returning empty lists. + * + * @deprecated 使用 {@link #batchFetchTableDetails} 替代,批量查询性能更优 + */ + @Deprecated + private Table fetchTableDetails(Connection conn, MetaData meta, String databaseName, String schemaName, + String tableName) { + List
tables = meta.tables(conn, databaseName, schemaName, tableName); + if (CollectionUtils.isEmpty(tables)) { + return null; + } + Table table = tables.get(0); + try { + table.setColumnList(meta.columns(conn, databaseName, schemaName, tableName)); + } catch (Exception e) { + log.warn("Failed to fetch columns for table {}: {}", tableName, e.getMessage()); + table.setColumnList(Collections.emptyList()); + } + try { + table.setIndexList(meta.indexes(conn, databaseName, schemaName, tableName)); + } catch (Exception e) { + log.warn("Failed to fetch indexes for table {}: {}", tableName, e.getMessage()); + table.setIndexList(Collections.emptyList()); + } + try { + table.setForeignKeyList(meta.foreignKeys(conn, databaseName, schemaName, tableName)); + } catch (Exception e) { + log.warn("Failed to fetch foreign keys for table {}: {}", tableName, e.getMessage()); + table.setForeignKeyList(Collections.emptyList()); + } + return table; + } + + /** + * Compare columns between source and target tables. + * + * @param caseSensitive whether to use case-sensitive name matching + * @return list of column differences with change types + */ + private List compareColumns(List sourceCols, List targetCols, + CompareOption option) { + List diffs = new ArrayList<>(); + if (CollectionUtils.isEmpty(sourceCols) && CollectionUtils.isEmpty(targetCols)) { + return diffs; + } + boolean caseSensitive = option.isCaseSensitive(); + Map sourceMap = CollectionUtils.isEmpty(sourceCols) + ? Collections.emptyMap() + : sourceCols.stream().filter(c -> c.getName() != null) + .collect(Collectors.toMap(c -> caseSensitive ? c.getName() : c.getName().toLowerCase(), c -> c, (a, b) -> a)); + Map targetMap = CollectionUtils.isEmpty(targetCols) + ? Collections.emptyMap() + : targetCols.stream().filter(c -> c.getName() != null) + .collect(Collectors.toMap(c -> caseSensitive ? c.getName() : c.getName().toLowerCase(), c -> c, (a, b) -> a)); + + for (Map.Entry entry : targetMap.entrySet()) { + String colName = entry.getKey(); + TableColumn targetCol = entry.getValue(); + TableColumn sourceCol = sourceMap.get(colName); + if (sourceCol == null) { + diffs.add(ColumnDiff.builder() + .changeType(EditStatus.ADD) + .targetColumn(targetCol) + .changedFields(Collections.singletonList(fieldDiff("targetOnly", null, targetCol.getName()))) + .build()); + } else if (!columnEquals(sourceCol, targetCol, option)) { + diffs.add(ColumnDiff.builder() + .changeType(EditStatus.MODIFY) + .sourceColumn(sourceCol) + .targetColumn(targetCol) + .changedFields(describeColumnDiff(sourceCol, targetCol, option)) + .build()); + } + } + for (Map.Entry entry : sourceMap.entrySet()) { + if (!targetMap.containsKey(entry.getKey())) { + diffs.add(ColumnDiff.builder() + .changeType(EditStatus.DELETE) + .sourceColumn(entry.getValue()) + .changedFields(Collections.singletonList(fieldDiff("sourceOnly", entry.getValue().getName(), null))) + .build()); + } + } + return diffs; + } + + private boolean columnEquals(TableColumn a, TableColumn b, CompareOption option) { + if (a == b) return true; + if (a == null || b == null) return false; + return Objects.equals(a.getDataType(), b.getDataType()) + && columnSizeEquals(a, b, option) + && Objects.equals(a.getDecimalDigits(), b.getDecimalDigits()) + && Objects.equals(a.getNullable(), b.getNullable()) + && Objects.equals(a.getDefaultValue(), b.getDefaultValue()) + && Objects.equals(a.getComment(), b.getComment()) + && Objects.equals(a.getAutoIncrement(), b.getAutoIncrement()) + && charsetEquals(a.getCharSetName(), b.getCharSetName(), option) + && collationEquals(a.getCollationName(), b.getCollationName(), option) + && columnTypeEquals(a.getColumnType(), b.getColumnType(), option); + } + + private List describeColumnDiff(TableColumn source, TableColumn target, CompareOption option) { + List changedFields = new ArrayList<>(); + addChangedField(changedFields, "dataType", source.getDataType(), target.getDataType()); + addChangedField(changedFields, "columnSize", source.getColumnSize(), target.getColumnSize(), + (sourceValue, targetValue) -> columnSizeEquals(source, target, option)); + addChangedField(changedFields, "decimalDigits", source.getDecimalDigits(), target.getDecimalDigits()); + addChangedField(changedFields, "nullable", source.getNullable(), target.getNullable()); + addChangedField(changedFields, "defaultValue", source.getDefaultValue(), target.getDefaultValue()); + addChangedField(changedFields, "comment", source.getComment(), target.getComment()); + addChangedField(changedFields, "autoIncrement", source.getAutoIncrement(), target.getAutoIncrement()); + addChangedField(changedFields, "charSetName", source.getCharSetName(), target.getCharSetName(), + (sourceValue, targetValue) -> charsetEquals(sourceValue, targetValue, option)); + addChangedField(changedFields, "collationName", source.getCollationName(), target.getCollationName(), + (sourceValue, targetValue) -> collationEquals(sourceValue, targetValue, option)); + addChangedField(changedFields, "columnType", source.getColumnType(), target.getColumnType(), + (sourceValue, targetValue) -> columnTypeEquals(sourceValue, targetValue, option)); + return changedFields; + } + + private boolean columnSizeEquals(TableColumn source, TableColumn target, CompareOption option) { + if (option.isIgnoreIntegerDisplayWidth() && isMysqlIntegerType(source.getDataType(), source.getColumnType()) + && isMysqlIntegerType(target.getDataType(), target.getColumnType())) { + return true; + } + return Objects.equals(source.getColumnSize(), target.getColumnSize()); + } + + private boolean columnTypeEquals(String sourceValue, String targetValue, CompareOption option) { + if (!option.isIgnoreIntegerDisplayWidth()) { + return Objects.equals(sourceValue, targetValue); + } + return Objects.equals(normalizeMysqlIntegerDisplayWidth(sourceValue), + normalizeMysqlIntegerDisplayWidth(targetValue)); + } + + private boolean isMysqlIntegerType(String dataType, String columnType) { + String type = StringUtils.defaultIfBlank(dataType, columnType); + if (StringUtils.isBlank(type)) { + return false; + } + String normalized = StringUtils.substringBefore(type, "(").trim(); + normalized = StringUtils.substringBefore(normalized, " ").trim(); + return StringUtils.equalsAnyIgnoreCase(normalized, + "tinyint", "smallint", "mediumint", "int", "integer", "bigint"); + } + + private String normalizeMysqlIntegerDisplayWidth(String value) { + if (StringUtils.isBlank(value)) { + return value; + } + java.util.regex.Matcher matcher = MYSQL_INTEGER_COLUMN_TYPE_WITH_WIDTH.matcher(value.trim()); + if (!matcher.matches()) { + return value; + } + return matcher.group(1).toLowerCase() + StringUtils.defaultString(matcher.group(2)).toLowerCase(); + } + + private boolean charsetEquals(String sourceValue, String targetValue, CompareOption option) { + if (!option.isIgnoreCharsetAlias()) { + return Objects.equals(sourceValue, targetValue); + } + return Objects.equals(normalizeCharsetAlias(sourceValue), normalizeCharsetAlias(targetValue)); + } + + private boolean collationEquals(String sourceValue, String targetValue, CompareOption option) { + if (!option.isIgnoreCharsetAlias()) { + return Objects.equals(sourceValue, targetValue); + } + return Objects.equals(normalizeCollationAlias(sourceValue), normalizeCollationAlias(targetValue)); + } + + private String normalizeCharsetAlias(String value) { + if ("utf8".equalsIgnoreCase(value)) { + return "utf8mb3"; + } + return value; + } + + private String normalizeCollationAlias(String value) { + if (StringUtils.startsWithIgnoreCase(value, "utf8_")) { + return "utf8mb3_" + value.substring("utf8_".length()); + } + return value; + } + + private String getColumnDiffName(ColumnDiff diff) { + if (diff.getTargetColumn() != null) { + return diff.getTargetColumn().getName(); + } + return diff.getSourceColumn() == null ? null : diff.getSourceColumn().getName(); + } + + /** + * Compare indexes between source and target tables. + * + * @param caseSensitive whether to use case-sensitive name matching + * @return list of index differences with change types + */ + private List compareIndexes(List sourceIdxs, List targetIdxs, boolean caseSensitive) { + List diffs = new ArrayList<>(); + if (CollectionUtils.isEmpty(sourceIdxs) && CollectionUtils.isEmpty(targetIdxs)) { + return diffs; + } + Map sourceMap = CollectionUtils.isEmpty(sourceIdxs) + ? Collections.emptyMap() + : sourceIdxs.stream().filter(i -> i.getName() != null) + .collect(Collectors.toMap(i -> caseSensitive ? i.getName() : i.getName().toLowerCase(), i -> i, (a, b) -> a)); + Map targetMap = CollectionUtils.isEmpty(targetIdxs) + ? Collections.emptyMap() + : targetIdxs.stream().filter(i -> i.getName() != null) + .collect(Collectors.toMap(i -> caseSensitive ? i.getName() : i.getName().toLowerCase(), i -> i, (a, b) -> a)); + + for (Map.Entry entry : targetMap.entrySet()) { + String idxName = entry.getKey(); + TableIndex targetIdx = entry.getValue(); + TableIndex sourceIdx = sourceMap.get(idxName); + if (sourceIdx == null) { + diffs.add(IndexDiff.builder() + .changeType(EditStatus.ADD) + .targetIndex(targetIdx) + .changedFields(Collections.singletonList(fieldDiff("targetOnly", null, targetIdx.getName()))) + .build()); + } else if (!indexEquals(sourceIdx, targetIdx)) { + diffs.add(IndexDiff.builder() + .changeType(EditStatus.MODIFY) + .sourceIndex(sourceIdx) + .targetIndex(targetIdx) + .changedFields(describeIndexDiff(sourceIdx, targetIdx)) + .build()); + } + } + for (Map.Entry entry : sourceMap.entrySet()) { + if (!targetMap.containsKey(entry.getKey())) { + diffs.add(IndexDiff.builder() + .changeType(EditStatus.DELETE) + .sourceIndex(entry.getValue()) + .changedFields(Collections.singletonList(fieldDiff("sourceOnly", entry.getValue().getName(), null))) + .build()); + } + } + return diffs; + } + + private boolean indexEquals(TableIndex a, TableIndex b) { + if (a == b) return true; + if (a == null || b == null) return false; + return Objects.equals(a.getType(), b.getType()) + && Objects.equals(a.getUnique(), b.getUnique()) + && Objects.equals(a.getMethod(), b.getMethod()) + && Objects.equals(a.getComment(), b.getComment()); + } + + private List describeIndexDiff(TableIndex source, TableIndex target) { + List changedFields = new ArrayList<>(); + addChangedField(changedFields, "type", source.getType(), target.getType()); + addChangedField(changedFields, "unique", source.getUnique(), target.getUnique()); + addChangedField(changedFields, "method", source.getMethod(), target.getMethod()); + addChangedField(changedFields, "comment", source.getComment(), target.getComment()); + return changedFields; + } + + private String getIndexDiffName(IndexDiff diff) { + if (diff.getTargetIndex() != null) { + return diff.getTargetIndex().getName(); + } + return diff.getSourceIndex() == null ? null : diff.getSourceIndex().getName(); + } + + /** + * Compare foreign keys between source and target tables. + * + * @param caseSensitive whether to use case-sensitive name matching + * @return list of foreign key differences with change types + */ + private List compareForeignKeys(List sourceFKs, List targetFKs, boolean caseSensitive) { + List diffs = new ArrayList<>(); + if (CollectionUtils.isEmpty(sourceFKs) && CollectionUtils.isEmpty(targetFKs)) { + return diffs; + } + Map sourceMap = CollectionUtils.isEmpty(sourceFKs) + ? Collections.emptyMap() + : sourceFKs.stream().filter(fk -> fk.getName() != null) + .collect(Collectors.toMap(fk -> caseSensitive ? fk.getName() : fk.getName().toLowerCase(), fk -> fk, (a, b) -> a)); + Map targetMap = CollectionUtils.isEmpty(targetFKs) + ? Collections.emptyMap() + : targetFKs.stream().filter(fk -> fk.getName() != null) + .collect(Collectors.toMap(fk -> caseSensitive ? fk.getName() : fk.getName().toLowerCase(), fk -> fk, (a, b) -> a)); + + for (Map.Entry entry : targetMap.entrySet()) { + String fkName = entry.getKey(); + ForeignKey targetFK = entry.getValue(); + ForeignKey sourceFK = sourceMap.get(fkName); + if (sourceFK == null) { + diffs.add(ForeignKeyDiff.builder() + .changeType(EditStatus.ADD) + .targetForeignKey(targetFK) + .changedFields(Collections.singletonList(fieldDiff("targetOnly", null, targetFK.getName()))) + .build()); + } else if (!foreignKeyEquals(sourceFK, targetFK)) { + diffs.add(ForeignKeyDiff.builder() + .changeType(EditStatus.MODIFY) + .sourceForeignKey(sourceFK) + .targetForeignKey(targetFK) + .changedFields(describeForeignKeyDiff(sourceFK, targetFK)) + .build()); + } + } + for (Map.Entry entry : sourceMap.entrySet()) { + if (!targetMap.containsKey(entry.getKey())) { + diffs.add(ForeignKeyDiff.builder() + .changeType(EditStatus.DELETE) + .sourceForeignKey(entry.getValue()) + .changedFields(Collections.singletonList(fieldDiff("sourceOnly", entry.getValue().getName(), null))) + .build()); + } + } + return diffs; + } + + private boolean foreignKeyEquals(ForeignKey a, ForeignKey b) { + if (a == b) return true; + if (a == null || b == null) return false; + return Objects.equals(a.getColumn(), b.getColumn()) + && Objects.equals(a.getReferencedTable(), b.getReferencedTable()) + && Objects.equals(a.getReferencedColumn(), b.getReferencedColumn()) + && Objects.equals(a.getUpdateRule(), b.getUpdateRule()) + && Objects.equals(a.getDeleteRule(), b.getDeleteRule()); + } + + private List describeForeignKeyDiff(ForeignKey source, ForeignKey target) { + List changedFields = new ArrayList<>(); + addChangedField(changedFields, "column", source.getColumn(), target.getColumn()); + addChangedField(changedFields, "referencedTable", source.getReferencedTable(), target.getReferencedTable()); + addChangedField(changedFields, "referencedColumn", source.getReferencedColumn(), target.getReferencedColumn()); + addChangedField(changedFields, "updateRule", source.getUpdateRule(), target.getUpdateRule()); + addChangedField(changedFields, "deleteRule", source.getDeleteRule(), target.getDeleteRule()); + return changedFields; + } + + private String getForeignKeyDiffName(ForeignKeyDiff diff) { + if (diff.getTargetForeignKey() != null) { + return diff.getTargetForeignKey().getName(); + } + return diff.getSourceForeignKey() == null ? null : diff.getSourceForeignKey().getName(); + } + + private boolean tableOptionsEqual(Table a, Table b, CompareOption option) { + if (a == b) return true; + if (a == null || b == null) return false; + boolean autoIncrementEquals = option.isIgnoreAutoIncrement() + || Objects.equals(a.getIncrementValue(), b.getIncrementValue()); + return Objects.equals(a.getComment(), b.getComment()) + && Objects.equals(a.getEngine(), b.getEngine()) + && Objects.equals(a.getCharset(), b.getCharset()) + && Objects.equals(a.getCollate(), b.getCollate()) + && autoIncrementEquals; + } + + private List describeTableOptionDiff(Table source, Table target, CompareOption option) { + List changedFields = new ArrayList<>(); + addChangedField(changedFields, "comment", source.getComment(), target.getComment()); + addChangedField(changedFields, "engine", source.getEngine(), target.getEngine()); + addChangedField(changedFields, "charset", source.getCharset(), target.getCharset()); + addChangedField(changedFields, "collate", source.getCollate(), target.getCollate()); + if (!option.isIgnoreAutoIncrement()) { + addChangedField(changedFields, "incrementValue", source.getIncrementValue(), target.getIncrementValue()); + } + return changedFields; + } + + private void addChangedField(List changedFields, String fieldName, Object sourceValue, Object targetValue) { + if (!Objects.equals(sourceValue, targetValue)) { + changedFields.add(fieldDiff(fieldName, sourceValue, targetValue)); + } + } + + private void addChangedField(List changedFields, String fieldName, T sourceValue, T targetValue, + BiPredicate equalsPredicate) { + if (!equalsPredicate.test(sourceValue, targetValue)) { + changedFields.add(fieldDiff(fieldName, sourceValue, targetValue)); + } + } + + private FieldDiff fieldDiff(String fieldName, Object sourceValue, Object targetValue) { + return FieldDiff.builder() + .fieldName(fieldName) + .sourceValue(sourceValue == null ? null : String.valueOf(sourceValue)) + .targetValue(targetValue == null ? null : String.valueOf(targetValue)) + .build(); + } + + private String formatChangedFields(List changedFields) { + if (CollectionUtils.isEmpty(changedFields)) { + return ""; + } + return changedFields.stream() + .map(fieldDiff -> fieldDiff.getFieldName() + ": source=" + fieldDiff.getSourceValue() + + ", target=" + fieldDiff.getTargetValue()) + .collect(Collectors.joining(", ")); + } + + /** + * Build a target table with edit status flags set based on diff results. + * Used for generating ALTER TABLE DDL statements. + * + * @param caseSensitive whether to use case-sensitive name matching + */ + private Table buildTableWithEditStatus(Table sourceTable, Table targetTable, + List columnDiffs, List indexDiffs, + List fkDiffs, CompareOption option) { + boolean caseSensitive = option.isCaseSensitive(); + Table result = new Table(); + result.setName(shouldPreserveTargetName(sourceTable, targetTable, caseSensitive) + ? targetTable.getName() : sourceTable.getName()); + result.setComment(sourceTable.getComment()); + result.setDatabaseName(targetTable.getDatabaseName()); + result.setSchemaName(targetTable.getSchemaName()); + result.setEngine(sourceTable.getEngine()); + result.setCharset(sourceTable.getCharset()); + result.setCollate(sourceTable.getCollate()); + result.setIncrementValue(option.isIgnoreAutoIncrement() + ? targetTable.getIncrementValue() : sourceTable.getIncrementValue()); + result.setPartition(sourceTable.getPartition()); + + if (CollectionUtils.isEmpty(columnDiffs)) { + result.setColumnList(copyColumns(sourceTable.getColumnList())); + } else { + List columns = new ArrayList<>(); + if (sourceTable.getColumnList() != null) { + Map sourceDiffMap = columnDiffs.stream() + .filter(d -> d.getChangeType() == EditStatus.DELETE || d.getChangeType() == EditStatus.MODIFY) + .filter(d -> d.getSourceColumn() != null && d.getSourceColumn().getName() != null) + .collect(Collectors.toMap(d -> caseSensitive ? d.getSourceColumn().getName() + : d.getSourceColumn().getName().toLowerCase(), d -> d, (a, b) -> a)); + + for (TableColumn sourceCol : sourceTable.getColumnList()) { + TableColumn col = copyColumn(sourceCol); + String key = caseSensitive ? col.getName() : col.getName().toLowerCase(); + ColumnDiff diff = sourceDiffMap.get(key); + if (diff != null) { + col.setEditStatus(diff.getChangeType() == EditStatus.DELETE + ? EditStatus.ADD.name() : EditStatus.MODIFY.name()); + if (diff.getChangeType() == EditStatus.MODIFY && diff.getTargetColumn() != null + && !Objects.equals(diff.getTargetColumn().getName(), col.getName())) { + col.setOldName(diff.getTargetColumn().getName()); + } + } + columns.add(col); + } + } + for (ColumnDiff diff : columnDiffs) { + if (diff.getChangeType() == EditStatus.ADD && diff.getTargetColumn() != null) { + TableColumn deletedCol = copyColumn(diff.getTargetColumn()); + deletedCol.setEditStatus(EditStatus.DELETE.name()); + columns.add(deletedCol); + } + } + result.setColumnList(columns); + } + + if (CollectionUtils.isEmpty(indexDiffs)) { + result.setIndexList(copyIndexes(sourceTable.getIndexList())); + } else { + List indexes = new ArrayList<>(); + if (sourceTable.getIndexList() != null) { + Map sourceDiffMap = indexDiffs.stream() + .filter(d -> d.getChangeType() == EditStatus.DELETE || d.getChangeType() == EditStatus.MODIFY) + .filter(d -> d.getSourceIndex() != null && d.getSourceIndex().getName() != null) + .collect(Collectors.toMap(d -> caseSensitive ? d.getSourceIndex().getName() + : d.getSourceIndex().getName().toLowerCase(), d -> d, (a, b) -> a)); + for (TableIndex sourceIdx : sourceTable.getIndexList()) { + TableIndex idx = copyIndex(sourceIdx); + String key = caseSensitive ? idx.getName() : idx.getName().toLowerCase(); + IndexDiff diff = sourceDiffMap.get(key); + if (diff != null) { + idx.setEditStatus(diff.getChangeType() == EditStatus.DELETE + ? EditStatus.ADD.name() : EditStatus.MODIFY.name()); + if (diff.getChangeType() == EditStatus.MODIFY && diff.getTargetIndex() != null) { + idx.setOldName(diff.getTargetIndex().getName()); + } + } + indexes.add(idx); + } + } + for (IndexDiff diff : indexDiffs) { + if (diff.getChangeType() == EditStatus.ADD && diff.getTargetIndex() != null) { + TableIndex deletedIdx = copyIndex(diff.getTargetIndex()); + deletedIdx.setOldName(diff.getTargetIndex().getName()); + deletedIdx.setEditStatus(EditStatus.DELETE.name()); + indexes.add(deletedIdx); + } + } + result.setIndexList(indexes); + } + + result.setForeignKeyList(copyForeignKeys(sourceTable.getForeignKeyList())); + + return result; + } + + private boolean shouldPreserveTargetName(Table sourceTable, Table targetTable, boolean caseSensitive) { + return !caseSensitive && StringUtils.equalsIgnoreCase(sourceTable.getName(), targetTable.getName()); + } + + private List copyColumns(List source) { + if (source == null) { + return new ArrayList<>(); + } + return source.stream().map(this::copyColumn).collect(Collectors.toList()); + } + + private TableColumn copyColumn(TableColumn source) { + TableColumn target = new TableColumn(); + BeanUtils.copyProperties(source, target); + return target; + } + + private List copyIndexes(List source) { + if (source == null) { + return new ArrayList<>(); + } + return source.stream().map(this::copyIndex).collect(Collectors.toList()); + } + + private TableIndex copyIndex(TableIndex source) { + TableIndex target = new TableIndex(); + BeanUtils.copyProperties(source, target); + return target; + } + + private List copyForeignKeys(List source) { + if (source == null) { + return new ArrayList<>(); + } + return source.stream().map(this::copyForeignKey).collect(Collectors.toList()); + } + + private ForeignKey copyForeignKey(ForeignKey source) { + ForeignKey target = new ForeignKey(); + BeanUtils.copyProperties(source, target); + return target; + } + + private void closeQuietly(Connection conn) { + if (conn != null) { + try { + conn.close(); + } catch (SQLException e) { + log.warn("Error closing connection", e); + } + } + } +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java index 1454772d8..46083b1aa 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java @@ -1,47 +1,56 @@ package ai.chat2db.server.domain.core.impl; import java.sql.Connection; -import java.sql.ResultSet; -import java.sql.SQLException; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; +import java.util.stream.IntStream; -import ai.chat2db.server.domain.api.enums.TableVectorEnum; -import ai.chat2db.server.domain.api.param.*; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.google.common.collect.Lists; + +import ai.chat2db.server.domain.api.model.TreeNode; +import ai.chat2db.server.domain.api.param.DeprecatedTableParam; +import ai.chat2db.server.domain.api.param.DropParam; +import ai.chat2db.server.domain.api.param.PinTableParam; +import ai.chat2db.server.domain.api.param.ShowCreateTableParam; +import ai.chat2db.server.domain.api.param.TablePageQueryParam; +import ai.chat2db.server.domain.api.param.TableQueryParam; +import ai.chat2db.server.domain.api.param.TableSelector; +import ai.chat2db.server.domain.api.param.TreeSearchParam; +import ai.chat2db.server.domain.api.param.TypeQueryParam; +import ai.chat2db.server.domain.api.service.DeprecatedTableService; import ai.chat2db.server.domain.api.service.PinService; import ai.chat2db.server.domain.api.service.TableService; -import ai.chat2db.server.domain.core.cache.CacheManage; +import ai.chat2db.server.domain.api.service.ForeignKeySyncService; +import ai.chat2db.server.domain.core.cache.LuceneIndexManager; +import ai.chat2db.server.domain.core.cache.LuceneIndexManagerFactory; import ai.chat2db.server.domain.core.converter.PinTableConverter; -import ai.chat2db.server.domain.core.converter.TableConverter; -import ai.chat2db.server.domain.repository.Dbutils; -import ai.chat2db.server.domain.repository.entity.*; -import ai.chat2db.server.domain.repository.mapper.TableCacheMapper; -import ai.chat2db.server.domain.repository.mapper.TableCacheVersionMapper; -import ai.chat2db.server.domain.repository.mapper.TableVectorMappingMapper; -import ai.chat2db.server.tools.base.wrapper.result.ActionResult; -import ai.chat2db.server.tools.base.wrapper.result.DataResult; -import ai.chat2db.server.tools.base.wrapper.result.ListResult; -import ai.chat2db.server.tools.base.wrapper.result.PageResult; +import ai.chat2db.server.tools.base.enums.DataSourceTypeEnum; import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.SqlBuilder; +import ai.chat2db.spi.CommandExecutor; import ai.chat2db.spi.enums.EditStatus; -import ai.chat2db.spi.model.*; +import ai.chat2db.spi.model.ExecuteResult; +import ai.chat2db.spi.model.ForeignKey; +import ai.chat2db.spi.model.IndexType; +import ai.chat2db.spi.model.SimpleTable; +import ai.chat2db.spi.model.Sql; +import ai.chat2db.spi.model.Table; +import ai.chat2db.spi.model.TableColumn; +import ai.chat2db.spi.model.TableIndex; +import ai.chat2db.spi.model.TableIndexColumn; +import ai.chat2db.spi.model.TableMeta; +import ai.chat2db.spi.model.Type; +import ai.chat2db.spi.model.VirtualForeignKey; import ai.chat2db.spi.sql.Chat2DBContext; -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import com.baomidou.mybatisplus.core.metadata.IPage; -import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import com.google.common.collect.Lists; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import static ai.chat2db.server.domain.core.cache.CacheKey.getColumnKey; -import static ai.chat2db.server.domain.core.cache.CacheKey.getTableKey; /** * @author moji @@ -58,65 +67,64 @@ public class TableServiceImpl implements TableService { @Autowired private PinTableConverter pinTableConverter; - - private TableCacheMapper getTableCacheMapper() { - return Dbutils.getMapper(TableCacheMapper.class); - } - @Autowired - private TableConverter tableConverter; - - - - private TableCacheVersionMapper getVersionMapper() { - return Dbutils.getMapper(TableCacheVersionMapper.class); - } + private DeprecatedTableService deprecatedTableService; + @Autowired + private ForeignKeySyncService foreignKeySyncService; - private TableVectorMappingMapper getTableVectorMapper() { - return Dbutils.getMapper(TableVectorMappingMapper.class); - } + @Autowired + private LuceneIndexManagerFactory managerFactory; @Override - public DataResult showCreateTable(ShowCreateTableParam param) { + public String showCreateTable(ShowCreateTableParam param) { MetaData metaSchema = Chat2DBContext.getMetaData(); - String ddl = metaSchema.tableDDL(Chat2DBContext.getConnection(), param.getDatabaseName(), param.getSchemaName(), param.getTableName()); - return DataResult.of(ddl); + return metaSchema.tableDDL(Chat2DBContext.getConnection(), param.getDatabaseName(), param.getSchemaName(), + param.getTableName()); } @Override - public ActionResult drop(DropParam param) { + public void drop(DropParam param) { DBManage metaSchema = Chat2DBContext.getDBManage(); - metaSchema.dropTable(Chat2DBContext.getConnection(), param.getDatabaseName(), param.getTableSchema(), param.getTableName()); - return ActionResult.isSuccess(); + metaSchema.dropTable(Chat2DBContext.getConnection(), param.getDatabaseName(), param.getSchemaName(), + param.getTableName()); } @Override - public DataResult createTableExample(String dbType) { - String sql = Chat2DBContext.getDBConfig().getSimpleCreateTable(); - return DataResult.of(sql); + public String createTableExample(String dbType) { + return Chat2DBContext.getDBConfig().getSimpleCreateTable(); } @Override - public DataResult alterTableExample(String dbType) { - String sql = Chat2DBContext.getDBConfig().getSimpleAlterTable(); - return DataResult.of(sql); + public String alterTableExample(String dbType) { + return Chat2DBContext.getDBConfig().getSimpleAlterTable(); } @Override - public DataResult
query(TableQueryParam param, TableSelector selector) { + public Table query(TableQueryParam param, TableSelector selector) { MetaData metaSchema = Chat2DBContext.getMetaData(); - List
tables = metaSchema.tables(Chat2DBContext.getConnection(), param.getDatabaseName(), param.getSchemaName(), param.getTableName()); + boolean redis = DataSourceTypeEnum.REDIS.getCode().equals(Chat2DBContext.getConnectInfo().getDbType()); + Connection connection = redis ? null : Chat2DBContext.getConnection(); + List
tables = metaSchema.tables(connection, param.getDatabaseName(), + param.getSchemaName(), param.getTableName()); if (!CollectionUtils.isEmpty(tables)) { Table table = tables.get(0); + if (redis) { + return table; + } table.setIndexList( - metaSchema.indexes(Chat2DBContext.getConnection(), param.getDatabaseName(), param.getSchemaName(), param.getTableName())); + metaSchema.indexes(connection, param.getDatabaseName(), param.getSchemaName(), + param.getTableName())); table.setColumnList( - metaSchema.columns(Chat2DBContext.getConnection(), param.getDatabaseName(), param.getSchemaName(), param.getTableName())); + metaSchema.columns(connection, param.getDatabaseName(), param.getSchemaName(), + param.getTableName())); + table.setForeignKeyList( + metaSchema.foreignKeys(connection, param.getDatabaseName(), + param.getSchemaName(), param.getTableName())); setPrimaryKey(table); - return DataResult.of(table); + return table; } - return DataResult.of(null); + return null; } private void setPrimaryKey(Table table) { @@ -133,7 +141,6 @@ private void setPrimaryKey(Table table) { } Map columnMap = columns.stream() .collect(Collectors.toMap(TableColumn::getName, Function.identity())); - List indexes = new ArrayList<>(); for (TableIndex tableIndex : tableIndices) { if ("Primary".equalsIgnoreCase(tableIndex.getType())) { List indexColumns = tableIndex.getColumnList(); @@ -147,15 +154,12 @@ private void setPrimaryKey(Table table) { } } } - } else { - indexes.add(tableIndex); } } - table.setIndexList(indexes); } @Override - public ListResult buildSql(Table oldTable, Table newTable) { + public List buildSql(Table oldTable, Table newTable) { initOldTable(oldTable, newTable); SqlBuilder sqlBuilder = Chat2DBContext.getSqlBuilder(); List sqls = new ArrayList<>(); @@ -166,7 +170,19 @@ public ListResult buildSql(Table oldTable, Table newTable) { initUpdatePrimaryKey(oldTable, newTable); sqls.add(Sql.builder().sql(sqlBuilder.buildModifyTaleSql(oldTable, newTable)).build()); } - return ListResult.of(sqls); + return sqls; + } + + @Override + public List buildBatchSql(List
oldTables, List
newTables) { + if (oldTables.size() != newTables.size()) { + throw new IllegalArgumentException("Old tables and new tables lists must have the same size."); + } + SqlBuilder sqlBuilder = Chat2DBContext.getSqlBuilder(); + return IntStream.range(0, oldTables.size()) + .mapToObj(i -> sqlBuilder.buildModifyTaleSql(oldTables.get(i), newTables.get(i))) + .filter(StringUtils::isNotEmpty) + .collect(Collectors.toList()); } private void initUpdatePrimaryKey(Table oldTable, Table newTable) { @@ -195,7 +211,8 @@ private void initUpdatePrimaryKey(Table oldTable, Table newTable) { return; } boolean flag = false; - Map oldColumnMap = oldColumns.stream().collect(Collectors.toMap(TableColumn::getName, Function.identity())); + Map oldColumnMap = oldColumns.stream() + .collect(Collectors.toMap(TableColumn::getName, Function.identity())); for (TableColumn column : newColumns) { TableColumn oldColumn = oldColumnMap.get(column.getName()); if (oldColumn == null) { @@ -215,8 +232,8 @@ private List getPrimaryKeyColumn(Table table) { if (table == null || CollectionUtils.isEmpty(table.getColumnList())) { return null; } - return table.getColumnList().stream().filter(tableColumn -> - tableColumn.getPrimaryKey() != null && tableColumn.getPrimaryKey()) + return table.getColumnList().stream() + .filter(tableColumn -> tableColumn.getPrimaryKey() != null && tableColumn.getPrimaryKey()) .collect(Collectors.toList()); } @@ -240,16 +257,18 @@ private void addPrimaryKey(Table newTable, TableColumn column, String status) { if (indexes == null) { indexes = new ArrayList<>(); } - TableIndex keyIndex = indexes.stream().filter(index -> "Primary".equalsIgnoreCase(index.getType())).findFirst().orElse(null); + TableIndex keyIndex = indexes.stream().filter(index -> "Primary".equalsIgnoreCase(index.getType())).findFirst() + .orElse(null); if (keyIndex == null) { keyIndex = new TableIndex(); keyIndex.setType("Primary"); - keyIndex.setName(StringUtils.isBlank(column.getPrimaryKeyName()) ? "PRIMARY_KEY" : column.getPrimaryKeyName()); + keyIndex.setName( + StringUtils.isBlank(column.getPrimaryKeyName()) ? "PRIMARY_KEY" : column.getPrimaryKeyName()); keyIndex.setTableName(newTable.getName()); keyIndex.setSchemaName(newTable.getSchemaName()); keyIndex.setDatabaseName(newTable.getDatabaseName()); keyIndex.setEditStatus(status); - if(!EditStatus.ADD.name().equals(status)){ + if (!EditStatus.ADD.name().equals(status)) { keyIndex.setOldName(keyIndex.getName()); } indexes.add(keyIndex); @@ -258,6 +277,15 @@ private void addPrimaryKey(Table newTable, TableColumn column, String status) { if (tableIndexColumns == null) { tableIndexColumns = new ArrayList<>(); } + boolean exists = tableIndexColumns.stream() + .anyMatch(indexColumn -> StringUtils.equalsIgnoreCase(indexColumn.getColumnName(), column.getName())); + if (exists) { + keyIndex.setColumnList(tableIndexColumns.stream() + .sorted(Comparator.comparing(TableIndexColumn::getOrdinalPosition)) + .collect(Collectors.toList())); + newTable.setIndexList(indexes); + return; + } TableIndexColumn indexColumn = new TableIndexColumn(); indexColumn.setColumnName(column.getName()); indexColumn.setTableName(newTable.getName()); @@ -266,13 +294,15 @@ private void addPrimaryKey(Table newTable, TableColumn column, String status) { indexColumn.setOrdinalPosition(Short.valueOf(column.getPrimaryKeyOrder() + "")); indexColumn.setEditStatus(status); tableIndexColumns.add(indexColumn); - List sortTableIndexColumns = tableIndexColumns.stream().sorted(Comparator.comparing(TableIndexColumn::getOrdinalPosition)).collect(Collectors.toList()); - Set statusList = sortTableIndexColumns.stream().map(TableIndexColumn::getEditStatus).collect(Collectors.toSet()); + List sortTableIndexColumns = tableIndexColumns.stream() + .sorted(Comparator.comparing(TableIndexColumn::getOrdinalPosition)).collect(Collectors.toList()); + Set statusList = sortTableIndexColumns.stream().map(TableIndexColumn::getEditStatus) + .collect(Collectors.toSet()); if (statusList.size() == 1) { - //only one status ,set index status + // only one status ,set index status keyIndex.setEditStatus(statusList.iterator().next()); } else { - //more status ,set index status modify + // more status ,set index status modify keyIndex.setEditStatus(EditStatus.MODIFY.name()); } @@ -281,7 +311,6 @@ private void addPrimaryKey(Table newTable, TableColumn column, String status) { } - private void initOldTable(Table oldTable, Table newTable) { if (oldTable == null || newTable == null) { return; @@ -311,214 +340,126 @@ private void initOldTable(Table oldTable, Table newTable) { } @Override - public PageResult
pageQuery(TablePageQueryParam param, TableSelector selector) { - LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); - String key = getTableKey(param.getDataSourceId(), param.getDatabaseName(), param.getSchemaName()); - queryWrapper.eq(TableCacheVersionDO::getKey, key); - TableCacheVersionDO versionDO = getVersionMapper().selectOne(queryWrapper); - long total = 0; - long version = 0L; - if (param.isRefresh() || versionDO == null) { - total = addCache(param,versionDO); + public List
pageQuery(TablePageQueryParam param, TableSelector selector) { + LuceneIndexManager
luceneMgr = managerFactory.getManager(param.getDataSourceId()); + Long version = luceneMgr.getMaxVersion(param); + if (needRefreshCache(param, version)) { + loadAndCacheMetadata(luceneMgr, param.getDatabaseName(), param.getSchemaName(), version); + } + List
tables; + if (StringUtils.isNotBlank(param.getSortField())) { + boolean reverse = "descend".equals(param.getSortOrder()); + tables = luceneMgr.search(param, param.getLastDocId(), param.getSearchKey(), param.getSortField(), reverse); } else { - if ("2".equals(versionDO.getStatus())) { - version = versionDO.getVersion() - 1; - } else { - version = versionDO.getVersion(); - } - total = versionDO.getTableCount(); + tables = luceneMgr.search(param, param.getLastDocId(), param.getSearchKey()); } - Page page = new Page<>(param.getPageNo(), param.getPageSize()); - // page.setSearchCount(param.getEnableReturnCount()); - IPage iPage = getTableCacheMapper().pageQuery(page, param.getDataSourceId(), param.getDatabaseName(), param.getSchemaName(), param.getSearchKey()); - List
tables = new ArrayList<>(); - if (CollectionUtils.isNotEmpty(iPage.getRecords())) { - for (TableCacheDO tableCacheDO : iPage.getRecords()) { - Table t = new Table(); - t.setName(tableCacheDO.getTableName()); - t.setComment(tableCacheDO.getExtendInfo()); - t.setSchemaName(tableCacheDO.getSchemaName()); - t.setDatabaseName(tableCacheDO.getDatabaseName()); - tables.add(t); - } - } - if (param.getPageNo() <= 1) { + if (param.getLastDocId() == null) { tables = pinTable(tables, param); - } - return PageResult.of(tables, total, param); - } - - private long addCache(TablePageQueryParam param,TableCacheVersionDO versionDO){ - LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); - String key = getTableKey(param.getDataSourceId(), param.getDatabaseName(), param.getSchemaName()); - queryWrapper.eq(TableCacheVersionDO::getKey, key); - long total = 0; - long version = getLock(param.getDataSourceId(), param.getDatabaseName(), param.getSchemaName(), versionDO); - if (version == -1) { - int n = 0; - while (n < 100) { - try { - Thread.sleep(200); - } catch (InterruptedException e) { - } - versionDO = getVersionMapper().selectOne(queryWrapper); - if (versionDO != null && "1".equals(versionDO.getStatus())) { - version = versionDO.getVersion(); - total = versionDO.getTableCount(); - break; - } - n++; + tables = deprecatedTable(tables, param); + } + for (Table table : tables) { + TableQueryParam queryParam = TableQueryParam.builder() + .dataSourceId(param.getDataSourceId()) + .schemaName(table.getSchemaName()) + .databaseName(table.getDatabaseName()) + .tableName(table.getName()) + .refresh(param.isRefresh()) + .build(); + if (Boolean.TRUE.equals(selector.getColumnList())) { + queryParam.setClassType(TableColumn.class); + List columnList = getTableColumns((LuceneIndexManager) luceneMgr, queryParam); + table.setColumnList(columnList); } - } else { - total = addDBCache(param.getDataSourceId(), param.getDatabaseName(), param.getSchemaName(), version); - TableCacheVersionDO versionDO1 = new TableCacheVersionDO(); - versionDO1.setStatus("1"); - versionDO1.setTableCount(total); - getVersionMapper().update(versionDO1, queryWrapper); + if (Boolean.TRUE.equals(selector.getIndexList())) { + MetaData metaSchema = Chat2DBContext.getMetaData(); + List indexList = metaSchema.indexes(Chat2DBContext.getConnection(), + table.getDatabaseName(), table.getSchemaName(), table.getName()); + table.setIndexList(indexList); + } + if (Boolean.TRUE.equals(selector.getForeignKey())) { + List foreignKeys = foreignKeySyncService.queryRealForeignKeys( + param.getDataSourceId(), + table.getDatabaseName(), + table.getSchemaName(), + table.getName() + ); + table.setForeignKeyList(foreignKeys); + List virtualForeignKeys = foreignKeySyncService.queryVirtualForeignKeys( + param.getDataSourceId(), + table.getDatabaseName(), + table.getSchemaName(), + table.getName()); + table.setVirtualForeignKeyList(virtualForeignKeys); + } + if (Boolean.TRUE.equals(selector.getColumnList()) && Boolean.TRUE.equals(selector.getIndexList())) { + setPrimaryKey(table); + } + } - return total; + + param.setLastDocId(luceneMgr.getLastDocId()); + return tables; } + @Override - public ListResult queryTables(TablePageQueryParam param) { - LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); - String key = getTableKey(param.getDataSourceId(), param.getDatabaseName(), param.getSchemaName()); - queryWrapper.eq(TableCacheVersionDO::getKey, key); - TableCacheVersionDO versionDO = getVersionMapper().selectOne(queryWrapper); - if (versionDO == null) { - addCache(param,versionDO); - versionDO = getVersionMapper().selectOne(queryWrapper); - } - long version = "2".equals(versionDO.getStatus()) ? versionDO.getVersion() - 1 : versionDO.getVersion(); - - LambdaQueryWrapper query = new LambdaQueryWrapper<>(); - query.eq(TableCacheDO::getVersion, version); - query.eq(TableCacheDO::getDataSourceId, param.getDataSourceId()); - if (StringUtils.isNotBlank(param.getDatabaseName())) { - query.eq(TableCacheDO::getDatabaseName, param.getDatabaseName()); - } - if (StringUtils.isNotBlank(param.getSchemaName())) { - query.eq(TableCacheDO::getSchemaName, param.getSchemaName()); + public List queryTables(TablePageQueryParam param) { + LuceneIndexManager
luceneMgr = managerFactory.getManager(param.getDataSourceId()); + Long version = luceneMgr.getMaxVersion(param); + if (needRefreshCache(param, version)) { + loadAndCacheMetadata(luceneMgr, param.getDatabaseName(), param.getSchemaName(), version); } + List
search = luceneMgr.search(param, param.getLastDocId(), param.getSearchKey()); List tables = new ArrayList<>(); - - for (int i = 0; i < versionDO.getTableCount() / 500 + 1; i++) { - Page page = new Page<>(i + 1, 500); - IPage iPage = getTableCacheMapper().selectPage(page, query); - if (CollectionUtils.isNotEmpty(iPage.getRecords())) { - for (TableCacheDO tableCacheDO : iPage.getRecords()) { - SimpleTable t = new SimpleTable(); - t.setName(tableCacheDO.getTableName()); - t.setComment(tableCacheDO.getExtendInfo()); - tables.add(t); - } - } + for (Table table : search) { + SimpleTable t = new SimpleTable(); + t.setName(table.getName()); + t.setComment(table.getComment()); + tables.add(t); } - return ListResult.of(tables); + return tables; } - private long addDBCache(Long dataSourceId, String databaseName, String schemaName, long version) { - String key = getTableKey(dataSourceId, databaseName, schemaName); + private boolean needRefreshCache(TablePageQueryParam param, Long version) { + return param.isRefresh() || version == null; + } - Connection connection = Chat2DBContext.getConnection(); - long n = 0; - try (ResultSet resultSet = connection.getMetaData().getTables(databaseName, schemaName, null, - new String[]{"TABLE", "SYSTEM TABLE"})) { - List cacheDOS = new ArrayList<>(); - while (resultSet.next()) { - TableCacheDO tableCacheDO = new TableCacheDO(); - tableCacheDO.setDatabaseName(databaseName); - tableCacheDO.setSchemaName(schemaName); - tableCacheDO.setTableName(resultSet.getString("TABLE_NAME")); - tableCacheDO.setExtendInfo(resultSet.getString("REMARKS")); - tableCacheDO.setDataSourceId(dataSourceId); - tableCacheDO.setVersion(version); - tableCacheDO.setKey(key); - cacheDOS.add(tableCacheDO); - if (cacheDOS.size() >= 500) { - getTableCacheMapper().batchInsert(cacheDOS); - cacheDOS = new ArrayList<>(); - } - n++; - } - if (!CollectionUtils.isEmpty(cacheDOS)) { - getTableCacheMapper().batchInsert(cacheDOS); - } - LambdaQueryWrapper q = new LambdaQueryWrapper(); - q.eq(TableCacheDO::getDataSourceId, dataSourceId); - q.lt(TableCacheDO::getVersion, version); - if (StringUtils.isNotBlank(databaseName)) { - q.eq(TableCacheDO::getDatabaseName, databaseName); - } - if (StringUtils.isNotBlank(schemaName)) { - q.eq(TableCacheDO::getSchemaName, schemaName); - } - getTableCacheMapper().delete(q); - } catch (SQLException e) { - throw new RuntimeException(e); - } - return n; - } - - private Long getLock(Long dataSourceId, String databaseName, String schemaName, TableCacheVersionDO versionDO) { - String key = getTableKey(dataSourceId, databaseName, schemaName); - if (versionDO == null) { - versionDO = new TableCacheVersionDO(); - versionDO.setDatabaseName(databaseName); - versionDO.setSchemaName(schemaName); - versionDO.setDataSourceId(dataSourceId); - versionDO.setStatus("2"); - versionDO.setKey(key); - versionDO.setVersion(0L); - versionDO.setTableCount(0L); - try { - getVersionMapper().insert(versionDO); - return 0L; - } catch (Exception e) { - e.printStackTrace(); - return -1L; - } - } else { - long version = versionDO.getVersion() + 1; - LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper(); - queryWrapper.eq(TableCacheVersionDO::getId, versionDO.getId()); - queryWrapper.eq(TableCacheVersionDO::getVersion, versionDO.getVersion()); - versionDO.setVersion(version); - versionDO.setStatus("2"); - int n = getVersionMapper().update(versionDO, queryWrapper); - if (n == 1) { - return version; - } else { - return -1L; + private void loadAndCacheMetadata(LuceneIndexManager
mgr, String databaseName, String schemaName, Long version) { + mgr.getLock().writeLock().lock(); + try { + boolean redis = DataSourceTypeEnum.REDIS.getCode().equals(Chat2DBContext.getConnectInfo().getDbType()); + Connection conn = redis ? null : Chat2DBContext.getConnection(); + MetaData meta = Chat2DBContext.getMetaData(); + List
tables = meta.tables(conn, databaseName, schemaName, null); + if (CollectionUtils.isEmpty(tables)) { + mgr.deleteByDatabaseAndSchema(Table.builder() + .databaseName(databaseName) + .schemaName(schemaName) + .build()); + return; } + mgr.updateDocuments(tables, version); + } catch (Exception e) { + log.error("loadAndCacheMetadata error,version:{}", version, e); + } finally { + mgr.getLock().writeLock().unlock(); } } - -// private String buildKey(Long dataSourceId, String databaseName, String schemaName) { -// StringBuilder stringBuilder = new StringBuilder(dataSourceId.toString()); -// if (StringUtils.isNotBlank(databaseName)) { -// stringBuilder.append("_").append(databaseName); -// } -// if (StringUtils.isNotBlank(schemaName)) { -// stringBuilder.append("_").append(schemaName); -// } -// return stringBuilder.toString(); -// } - private List
pinTable(List
list, TablePageQueryParam param) { if (CollectionUtils.isEmpty(list)) { return Lists.newArrayList(); } PinTableParam pinTableParam = pinTableConverter.toPinTableParam(param); pinTableParam.setUserId(ContextUtils.getUserId()); - ListResult listResult = pinService.queryPinTables(pinTableParam); - if (!listResult.success() || CollectionUtils.isEmpty(listResult.getData())) { + List pinnedTables = pinService.queryPinTables(pinTableParam); + if (CollectionUtils.isEmpty(pinnedTables)) { return list; } List
tables = new ArrayList<>(); - Map tableMap = list.stream().collect(Collectors.toMap(Table::getName, Function.identity())); - for (String tableName : listResult.getData()) { + Map tableMap = list.stream() + .collect(Collectors.toMap(Table::getName, Function.identity(), (o1, o2) -> o1)); + for (String tableName : pinnedTables) { Table table = tableMap.get(tableName); if (table != null) { table.setPinned(true); @@ -534,19 +475,112 @@ private List
pinTable(List
list, TablePageQueryParam param) { return tables; } + private List
deprecatedTable(List
list, TablePageQueryParam param) { + if (CollectionUtils.isEmpty(list)) { + return Lists.newArrayList(); + } + DeprecatedTableParam deprecatedTableParam = new DeprecatedTableParam(); + deprecatedTableParam.setDataSourceId(param.getDataSourceId()); + deprecatedTableParam.setDatabaseName(param.getDatabaseName()); + deprecatedTableParam.setSchemaName(param.getSchemaName()); + deprecatedTableParam.setUserId(ContextUtils.getUserId()); + List deprecatedTables = deprecatedTableService.queryDeprecatedTables(deprecatedTableParam); + if (CollectionUtils.isEmpty(deprecatedTables)) { + return list; + } + Set deprecatedTableNames = new java.util.HashSet<>(deprecatedTables); + List
filteredTables = new ArrayList<>(); + for (Table table : list) { + if (table != null && !deprecatedTableNames.contains(table.getName())) { + filteredTables.add(table); + } + } + return filteredTables; + } + + @Override + public List
pageQueryDeprecated(TablePageQueryParam param, TableSelector selector) { + DeprecatedTableParam deprecatedTableParam = new DeprecatedTableParam(); + deprecatedTableParam.setDataSourceId(param.getDataSourceId()); + deprecatedTableParam.setDatabaseName(param.getDatabaseName()); + deprecatedTableParam.setSchemaName(param.getSchemaName()); + deprecatedTableParam.setUserId(ContextUtils.getUserId()); + List tableNames = deprecatedTableService.queryDeprecatedTables(deprecatedTableParam); + if (CollectionUtils.isEmpty(tableNames)) { + return Collections.emptyList(); + } + Set deprecatedTableNames = new java.util.HashSet<>(tableNames); + List
allTables = queryAllTables(param); + List
deprecatedTables = new ArrayList<>(); + for (Table table : allTables) { + if (table != null && deprecatedTableNames.contains(table.getName())) { + table.setDeprecated(true); + deprecatedTables.add(table); + } + } + return deprecatedTables; + } + + private List
queryAllTables(TablePageQueryParam param) { + LuceneIndexManager
luceneMgr = managerFactory.getManager(param.getDataSourceId()); + Long version = luceneMgr.getMaxVersion(param); + if (needRefreshCache(param, version)) { + loadAndCacheMetadata(luceneMgr, param.getDatabaseName(), param.getSchemaName(), version); + } + return luceneMgr.search(param, param.getLastDocId(), param.getSearchKey()); + } + + @Override + public void deprecatedTable(DeprecatedTableParam param) { + param.setUserId(ContextUtils.getUserId()); + deprecatedTableService.deprecatedTable(param); + } + + @Override + public void deleteDeprecatedTable(DeprecatedTableParam param) { + param.setUserId(ContextUtils.getUserId()); + deprecatedTableService.deleteDeprecatedTable(param); + } + + @Override + public List queryDeprecatedTables(DeprecatedTableParam param) { + param.setUserId(ContextUtils.getUserId()); + return deprecatedTableService.queryDeprecatedTables(param); + } + @Override public List queryColumns(TableQueryParam param) { - String tableColumnKey = getColumnKey(param.getDataSourceId(), param.getDatabaseName(), param.getSchemaName(), param.getTableName()); + LuceneIndexManager luceneIndexManager = managerFactory.getManager(param.getDataSourceId()); + param.setClassType(TableColumn.class); + return getTableColumns(luceneIndexManager, param); + } + + private List getTableColumns(LuceneIndexManager mgr, + TableQueryParam param) { MetaData metaSchema = Chat2DBContext.getMetaData(); - return CacheManage.getList(tableColumnKey, TableColumn.class, - (key) -> param.isRefresh(), (key) -> - metaSchema.columns(Chat2DBContext.getConnection(), param.getDatabaseName(), param.getSchemaName(), param.getTableName())); + Long version = mgr.getMaxVersion(param); + if (param.isRefresh() || version == null) { + mgr.getLock().writeLock().lock(); + try { + List columns = metaSchema.columns(Chat2DBContext.getConnection(), param.getDatabaseName(), + param.getSchemaName(), + param.getTableName()); + mgr.updateDocuments(columns, version); + return columns; + } catch (Exception e) { + log.error("getTableColumns error", e); + } finally { + mgr.getLock().writeLock().unlock(); + } + } + return mgr.search(param, null, null); } @Override public List queryIndexes(TableQueryParam param) { MetaData metaSchema = Chat2DBContext.getMetaData(); - return metaSchema.indexes(Chat2DBContext.getConnection(), param.getDatabaseName(), param.getSchemaName(), param.getTableName()); + return metaSchema.indexes(Chat2DBContext.getConnection(), param.getDatabaseName(), param.getSchemaName(), + param.getTableName()); } @@ -559,12 +593,14 @@ public List queryTypes(TypeQueryParam param) { @Override public TableMeta queryTableMeta(TypeQueryParam param) { MetaData metaSchema = Chat2DBContext.getMetaData(); - TableMeta tableMeta = metaSchema.getTableMeta(null, null, null); + Connection connection = Chat2DBContext.getConnection(); + TableMeta tableMeta = metaSchema.getTableMeta(connection, null, null); if (tableMeta != null) { - //filter primary key + // filter primary key List indexTypes = tableMeta.getIndexTypes(); if (CollectionUtils.isNotEmpty(indexTypes)) { - List types = indexTypes.stream().filter(indexType -> !"Primary".equals(indexType.getTypeName())).collect(Collectors.toList()); + List types = indexTypes.stream() + .filter(indexType -> !"Primary".equals(indexType.getTypeName())).collect(Collectors.toList()); tableMeta.setIndexTypes(types); } } @@ -572,28 +608,130 @@ public TableMeta queryTableMeta(TypeQueryParam param) { } + @Override - public ActionResult saveTableVector(TableVectorParam param) { - if (checkTableVector(param).getData()) { - return ActionResult.isSuccess(); + public void truncate(DropParam param) { + DBManage metaSchema = Chat2DBContext.getDBManage(); + metaSchema.truncate(Chat2DBContext.getConnection(), param.getDatabaseName(), param.getSchemaName(), + param.getTableName()); + } + + @Override + public List searchTreeNodes(TreeSearchParam param) { + LuceneIndexManager
mgr = managerFactory.getManager(param.getDataSourceId()); + Table queryModel = Table.builder() + .databaseName(param.getDatabaseName()) + .schemaName(param.getSchemaName()) + .build(); + Long version = mgr.getMaxVersion(queryModel); + + if (param.isRefresh() || version == null) { + loadAndCacheMetadata(mgr, param.getDatabaseName(), param.getSchemaName(), version); } - TableVectorMappingDO mappingDO = tableConverter.toTableVectorMappingDO(param); - mappingDO.setStatus(TableVectorEnum.SAVED.getCode()); - getTableVectorMapper().insert(mappingDO); - return ActionResult.isSuccess(); + + List
tables = mgr.search(queryModel, null, param.getSearchKey()); + List result = new ArrayList<>(); + for (Table table : tables) { + TreeNode node = buildTreeNode(table); + result.add(node); + } + return result; + } + + + private TreeNode buildTreeNode(Table table) { + List parentPath = new ArrayList<>(); + if (StringUtils.isNotBlank(table.getDatabaseName())) { + parentPath.add(table.getDatabaseName()); + } + if (StringUtils.isNotBlank(table.getSchemaName())) { + parentPath.add(table.getSchemaName()); + } + + Map extraParams = new HashMap<>(); + extraParams.put("databaseName", table.getDatabaseName()); + extraParams.put("schemaName", table.getSchemaName()); + extraParams.put("tableName", table.getName()); + + return TreeNode.builder() + .uuid("table-" + table.getName()) + .key(table.getName()) + .name(table.getName()) + .treeNodeType("table") + .comment(table.getComment()) + .isLeaf(true) + .pinned(table.isPinned()) + .parentPath(parentPath) + .extraParams(extraParams) + .build(); } @Override - public DataResult checkTableVector(TableVectorParam param) { - LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper(); - queryWrapper.eq(TableVectorMappingDO::getApiKey, param.getApiKey()); - queryWrapper.eq(TableVectorMappingDO::getDataSourceId, param.getDataSourceId()); - queryWrapper.eq(TableVectorMappingDO::getDatabase, param.getDatabase()); - queryWrapper.eq(TableVectorMappingDO::getSchema, param.getSchema()); - TableVectorMappingDO mappingDO = getTableVectorMapper().selectOne(queryWrapper); - if (Objects.nonNull(mappingDO) && TableVectorEnum.SAVED.getCode().equals(mappingDO.getStatus())) { - return DataResult.of(true); - } - return DataResult.of(false); + public List batchOptimizeTables(List tableNames, String databaseName, String schemaName) { + List results = new ArrayList<>(); + SqlBuilder sqlBuilder = Chat2DBContext.getSqlBuilder(); + MetaData metaData = Chat2DBContext.getMetaData(); + Connection connection = Chat2DBContext.getConnection(); + + for (String tableName : tableNames) { + String sql = sqlBuilder.buildOptimizeTableSql(databaseName, schemaName, tableName); + if (sql == null) { + results.add(ExecuteResult.builder() + .success(false) + .message("OPTIMIZE TABLE is not supported for this database type") + .sql(sql) + .build()); + continue; + } + try { + CommandExecutor commandExecutor = metaData.getCommandExecutor(); + ExecuteResult result = commandExecutor.execute(sql, connection, false, null, null, metaData.getValueHandler()); + result.setSql(sql); + results.add(result); + } catch (Exception e) { + log.error("Failed to optimize table: {}", tableName, e); + results.add(ExecuteResult.builder() + .success(false) + .message(e.getMessage()) + .sql(sql) + .build()); + } + } + return results; + } + + @Override + public List batchAnalyzeTables(List tableNames, String databaseName, String schemaName) { + List results = new ArrayList<>(); + SqlBuilder sqlBuilder = Chat2DBContext.getSqlBuilder(); + MetaData metaData = Chat2DBContext.getMetaData(); + Connection connection = Chat2DBContext.getConnection(); + + for (String tableName : tableNames) { + String sql = sqlBuilder.buildAnalyzeTableSql(databaseName, schemaName, tableName); + if (sql == null) { + results.add(ExecuteResult.builder() + .success(false) + .message("ANALYZE TABLE is not supported for this database type") + .sql(sql) + .build()); + continue; + } + try { + CommandExecutor commandExecutor = metaData.getCommandExecutor(); + ExecuteResult result = commandExecutor.execute(sql, connection, false, null, null, metaData.getValueHandler()); + result.setSql(sql); + results.add(result); + } catch (Exception e) { + log.error("Failed to analyze table: {}", tableName, e); + results.add(ExecuteResult.builder() + .success(false) + .message(e.getMessage()) + .sql(sql) + .build()); + } + } + return results; } + } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TaskServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TaskServiceImpl.java index 25ba9a9fe..d15b65014 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TaskServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TaskServiceImpl.java @@ -10,17 +10,24 @@ import ai.chat2db.server.domain.core.converter.TaskConverter; import ai.chat2db.server.domain.repository.MapperUtils; import ai.chat2db.server.domain.repository.entity.TaskDO; -import ai.chat2db.server.tools.base.wrapper.result.ActionResult; -import ai.chat2db.server.tools.base.wrapper.result.DataResult; -import ai.chat2db.server.tools.base.wrapper.result.PageResult; +import ai.chat2db.server.tools.base.wrapper.ServicePage; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.OrderItem; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.google.common.collect.Lists; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.io.File; +import java.io.IOException; +import java.util.List; + @Service +@Slf4j public class TaskServiceImpl implements TaskService { /** @@ -30,40 +37,97 @@ public class TaskServiceImpl implements TaskService { private TaskConverter taskConverter; @Override - public DataResult create(TaskCreateParam param) { + public Long create(TaskCreateParam param) { TaskDO taskDO = taskConverter.todo(param); taskDO.setDeleted(DeletedTypeEnum.N.name()); taskDO.setTaskStatus(TaskStatusEnum.INIT.name()); MapperUtils.getTaskMapper().insert(taskDO); - return DataResult.of(taskDO.getId()); + return taskDO.getId(); } @Override - public ActionResult updateStatus(TaskUpdateParam param) { + public void updateStatus(TaskUpdateParam param) { TaskDO taskDO = new TaskDO(); taskDO.setId(param.getId()); taskDO.setTaskStatus(param.getTaskStatus()); + taskDO.setTaskProgress(param.getTaskProgress()); + taskDO.setDownloadUrl(param.getDownloadUrl()); taskDO.setContent(param.getContent()); MapperUtils.getTaskMapper().updateById(taskDO); - return ActionResult.isSuccess(); + } @Override - public PageResult page(TaskPageParam param) { + public ServicePage page(TaskPageParam param) { + if (param.getDeleted() == null) { + param.setDeleted(DeletedTypeEnum.N.name()); + } Page page = new Page<>(); page.setCurrent(param.getPageNo()); page.setSize(param.getPageSize()); page.setOrders(Lists.newArrayList(OrderItem.desc("gmt_create"))); - IPage iPage = MapperUtils.getTaskMapper().pageQuery(page, param.getUserId(), DeletedTypeEnum.N.name()); + IPage iPage = MapperUtils.getTaskMapper().pageQuery(page, param); if (iPage != null) { - return PageResult.of(taskConverter.toModel(iPage.getRecords()), param); + return ServicePage.of(taskConverter.toModel(iPage.getRecords()), iPage.getTotal(), param.getPageNo(), param.getPageSize()); } - return PageResult.empty(param.getPageNo(), param.getPageSize()); + return ServicePage.empty(param.getPageNo(), param.getPageSize()); } @Override - public DataResult get(Long id) { + public Task get(Long id) { TaskDO task = MapperUtils.getTaskMapper().selectById(id); - return DataResult.of(taskConverter.toModel(task)); + return taskConverter.toModel(task); + } + + @Override + public int cleanupFinishedTasks(Long userId) { + if (userId == null) { + return 0; + } + List cleanableStatuses = Lists.newArrayList(TaskStatusEnum.FINISH.name(), TaskStatusEnum.ERROR.name()); + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper() + .eq(TaskDO::getUserId, userId) + .and(wrapper -> wrapper.eq(TaskDO::getDeleted, DeletedTypeEnum.N.name()).or().isNull(TaskDO::getDeleted)) + .in(TaskDO::getTaskStatus, cleanableStatuses); + List tasks = MapperUtils.getTaskMapper().selectList(queryWrapper); + if (tasks == null || tasks.isEmpty()) { + return 0; + } + + tasks.forEach(this::deleteTaskTempFile); + + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper() + .eq(TaskDO::getUserId, userId) + .and(wrapper -> wrapper.eq(TaskDO::getDeleted, DeletedTypeEnum.N.name()).or().isNull(TaskDO::getDeleted)) + .in(TaskDO::getTaskStatus, cleanableStatuses) + .set(TaskDO::getDeleted, DeletedTypeEnum.Y.name()); + return MapperUtils.getTaskMapper().update(null, updateWrapper); + } + + private void deleteTaskTempFile(TaskDO task) { + if (task == null || StringUtils.isBlank(task.getDownloadUrl())) { + return; + } + File file = new File(task.getDownloadUrl()); + if (!isSafeTempFile(file)) { + log.warn("Skip deleting non-temp task file, taskId={}, path={}", task.getId(), task.getDownloadUrl()); + return; + } + if (file.exists() && file.isFile() && !file.delete()) { + log.warn("Failed to delete task temp file, taskId={}, path={}", task.getId(), task.getDownloadUrl()); + } + } + + private boolean isSafeTempFile(File file) { + try { + File tempDir = new File(System.getProperty("java.io.tmpdir")); + String tempPath = tempDir.getCanonicalPath() + File.separator; + String filePath = file.getCanonicalPath(); + return filePath.startsWith(tempPath); + } catch (IOException e) { + log.warn("Failed to validate task temp file path: {}", file.getPath(), e); + return false; + } } } + diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamServiceImpl.java index 33ac9dcf3..9c06e31e5 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamServiceImpl.java @@ -23,6 +23,7 @@ import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; +import ai.chat2db.server.tools.base.wrapper.ServicePage; import ai.chat2db.server.tools.common.exception.DataAlreadyExistsBusinessException; import ai.chat2db.server.tools.common.exception.ParamBusinessException; import ai.chat2db.server.tools.common.model.EasyLambdaQueryWrapper; @@ -64,19 +65,18 @@ private DataSourceAccessMapper getDataSourceAccessMapper() { private UserConverter userConverter; @Override - public ListResult listQuery(List idList) { + public List listQuery(List idList) { if (CollectionUtils.isEmpty(idList)) { - return ListResult.empty(); + return java.util.Collections.emptyList(); } LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.in(TeamDO::getId, idList); List dataList = getTeamMapper().selectList(queryWrapper); - List list = teamConverter.do2dto(dataList); - return ListResult.of(list); + return teamConverter.do2dto(dataList); } @Override - public PageResult pageQuery(TeamPageQueryParam param, TeamSelector selector) { + public ServicePage pageQuery(TeamPageQueryParam param, TeamSelector selector) { EasyLambdaQueryWrapper queryWrapper = new EasyLambdaQueryWrapper<>(); if (StringUtils.isNotBlank(param.getSearchKey())) { queryWrapper.and(wrapper -> wrapper.like(TeamDO::getCode, "%" + param.getSearchKey() + "%") @@ -91,11 +91,11 @@ public PageResult pageQuery(TeamPageQueryParam param, TeamSelector selecto fillData(list, selector); - return PageResult.of(list, iPage.getTotal(), param); + return ServicePage.of(list, iPage.getTotal(), param.getPageNo(), param.getPageSize()); } @Override - public DataResult create(TeamCreateParam param) { + public Long create(TeamCreateParam param) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(TeamDO::getCode, param.getCode()); Page page = new Page<>(1, 1); @@ -110,18 +110,18 @@ public DataResult create(TeamCreateParam param) { TeamDO data = teamConverter.param2do(param, ContextUtils.getUserId()); getTeamMapper().insert(data); - return DataResult.of(data.getId()); + return data.getId(); } @Override - public DataResult update(TeamUpdateParam param) { + public Long update(TeamUpdateParam param) { TeamDO data = teamConverter.param2do(param, ContextUtils.getUserId()); getTeamMapper().updateById(data); - return DataResult.of(data.getId()); + return data.getId(); } @Override - public ActionResult delete(Long id) { + public void delete(Long id) { getTeamMapper().deleteById(id); LambdaQueryWrapper teamUserQueryWrapper = new LambdaQueryWrapper<>(); @@ -133,7 +133,7 @@ public ActionResult delete(Long id) { .eq(DataSourceAccessDO::getAccessObjectType, AccessObjectTypeEnum.TEAM.getCode()) ; getDataSourceAccessMapper().delete(dataSourceAccessQueryWrapper); - return ActionResult.isSuccess(); + } private void fillData(List list, TeamSelector selector) { diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamUserServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamUserServiceImpl.java index 732971708..948dfa6c7 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamUserServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamUserServiceImpl.java @@ -18,6 +18,7 @@ import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; +import ai.chat2db.server.tools.base.wrapper.ServicePage; import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.server.tools.common.util.EasyCollectionUtils; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; @@ -55,7 +56,7 @@ private TeamUserMapper getTeamUserMapper() { private TeamConverter teamConverter; @Override - public PageResult pageQuery(TeamUserPageQueryParam param, TeamUserSelector selector) { + public ServicePage pageQuery(TeamUserPageQueryParam param, TeamUserSelector selector) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(TeamUserDO::getTeamId, param.getTeamId()) .eq(TeamUserDO::getUserId, param.getUserId()) @@ -69,11 +70,11 @@ public PageResult pageQuery(TeamUserPageQueryParam param, TeamUserSele fillData(list, selector); - return PageResult.of(list, iPage.getTotal(), param); + return ServicePage.of(list, iPage.getTotal(), param.getPageNo(), param.getPageSize()); } @Override - public PageResult comprehensivePageQuery(TeamUserComprehensivePageQueryParam param, + public ServicePage comprehensivePageQuery(TeamUserComprehensivePageQueryParam param, TeamUserSelector selector) { Page page = new Page<>(param.getPageNo(), param.getPageSize()); page.setSearchCount(param.getEnableReturnCount()); @@ -84,21 +85,21 @@ public PageResult comprehensivePageQuery(TeamUserComprehensivePageQuer fillData(list, selector); - return PageResult.of(list, iPage.getTotal(), param); + return ServicePage.of(list, iPage.getTotal(), param.getPageNo(), param.getPageSize()); } @Override - public DataResult create(TeamUserCreatParam param) { + public Long create(TeamUserCreatParam param) { TeamUserDO data = teamUserConverter.param2do(param, ContextUtils.getUserId()); getTeamUserMapper().insert(data); - return DataResult.of(data.getId()); + return data.getId(); } @Override - public ActionResult delete(Long id) { + public void delete(Long id) { getTeamUserMapper().deleteById(id); - return ActionResult.isSuccess(); + } private void fillData(List list, TeamUserSelector selector) { @@ -125,3 +126,4 @@ private void fillTeam(List list, TeamUserSelector selector) { teamConverter.fillDetail(EasyCollectionUtils.toList(list, TeamUser::getTeam)); } } + diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TriggerServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TriggerServiceImpl.java index d6c3fae9d..710521cc8 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TriggerServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TriggerServiceImpl.java @@ -1,21 +1,125 @@ package ai.chat2db.server.domain.core.impl; +import java.sql.Connection; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import ai.chat2db.server.domain.api.model.TreeNode; +import ai.chat2db.server.domain.api.param.TreeSearchParam; import ai.chat2db.server.domain.api.service.TriggerService; +import ai.chat2db.server.domain.core.cache.LuceneIndexManager; +import ai.chat2db.server.domain.core.cache.LuceneIndexManagerFactory; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; +import ai.chat2db.spi.MetaData; import ai.chat2db.spi.model.Trigger; import ai.chat2db.spi.sql.Chat2DBContext; -import org.springframework.stereotype.Service; +import lombok.extern.slf4j.Slf4j; @Service +@Slf4j public class TriggerServiceImpl implements TriggerService { + + @Autowired + private LuceneIndexManagerFactory managerFactory; + + @Override + public List triggers(String databaseName, String schemaName) { + return Chat2DBContext.getMetaData().triggers(Chat2DBContext.getConnection(),databaseName, schemaName); + } + + @Override + public List triggersWithCache(Long dataSourceId, String databaseName, String schemaName, String searchKey, boolean refresh) { + LuceneIndexManager mgr = managerFactory.getManager(dataSourceId); + Trigger queryModel = Trigger.builder() + .databaseName(databaseName) + .schemaName(schemaName) + .build(); + Long version = mgr.getMaxVersion(queryModel); + + if (refresh || version == null) { + loadAndCacheMetadata(mgr, databaseName, schemaName, version); + } + + return mgr.search(queryModel, null, searchKey); + } + @Override - public ListResult triggers(String databaseName, String schemaName) { - return ListResult.of(Chat2DBContext.getMetaData().triggers(Chat2DBContext.getConnection(),databaseName, schemaName)); + public Trigger detail(String databaseName, String schemaName, String triggerName) { + return Chat2DBContext.getMetaData().trigger(Chat2DBContext.getConnection(), databaseName, schemaName, triggerName); } @Override - public DataResult detail(String databaseName, String schemaName, String triggerName) { - return DataResult.of(Chat2DBContext.getMetaData().trigger(Chat2DBContext.getConnection(), databaseName, schemaName, triggerName)); + public List searchTreeNodes(TreeSearchParam param) { + LuceneIndexManager mgr = managerFactory.getManager(param.getDataSourceId()); + Trigger queryModel = Trigger.builder() + .databaseName(param.getDatabaseName()) + .schemaName(param.getSchemaName()) + .build(); + Long version = mgr.getMaxVersion(queryModel); + + if (param.isRefresh() || version == null) { + loadAndCacheMetadata(mgr, param.getDatabaseName(), param.getSchemaName(), version); + } + + List triggers = mgr.search(queryModel, null, param.getSearchKey()); + List result = new ArrayList<>(); + for (Trigger trigger : triggers) { + TreeNode node = buildTreeNode(trigger); + result.add(node); + } + return result; + } + + private void loadAndCacheMetadata(LuceneIndexManager mgr, String databaseName, String schemaName, Long currentVersion) { + mgr.getLock().writeLock().lock(); + try { + Connection conn = Chat2DBContext.getConnection(); + MetaData meta = Chat2DBContext.getMetaData(); + List triggers = meta.triggers(conn, databaseName, schemaName); + if (CollectionUtils.isEmpty(triggers)) { + mgr.deleteByDatabaseAndSchema(databaseName, schemaName); + return; + } + mgr.updateDocuments(triggers, currentVersion); + log.info("[Trigger] Cached {} triggers for database: {}", triggers.size(), databaseName); + } catch (Exception e) { + log.error("[Trigger] loadAndCacheMetadata error", e); + } finally { + mgr.getLock().writeLock().unlock(); + } + } + + private TreeNode buildTreeNode(Trigger trigger) { + List parentPath = new ArrayList<>(); + if (StringUtils.isNotBlank(trigger.getDatabaseName())) { + parentPath.add(trigger.getDatabaseName()); + } + if (StringUtils.isNotBlank(trigger.getSchemaName())) { + parentPath.add(trigger.getSchemaName()); + } + + Map extraParams = new HashMap<>(); + extraParams.put("databaseName", trigger.getDatabaseName()); + extraParams.put("schemaName", trigger.getSchemaName()); + extraParams.put("triggerName", trigger.getName()); + + return TreeNode.builder() + .uuid("trigger-" + trigger.getName()) + .key(trigger.getName()) + .name(trigger.getName()) + .treeNodeType("trigger") + .comment(trigger.getComment()) + .isLeaf(true) + .parentPath(parentPath) + .extraParams(extraParams) + .build(); } -} +} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/UserServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/UserServiceImpl.java index 7a3947bba..7c01de456 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/UserServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/UserServiceImpl.java @@ -25,6 +25,7 @@ import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; +import ai.chat2db.server.tools.base.wrapper.ServicePage; import ai.chat2db.server.tools.common.exception.DataAlreadyExistsBusinessException; import ai.chat2db.server.tools.common.exception.ParamBusinessException; import ai.chat2db.server.tools.common.model.EasyLambdaQueryWrapper; @@ -63,34 +64,33 @@ private DataSourceAccessMapper getDataSourceAccessMapper() { } @Override - public DataResult query(Long id) { - return DataResult.of(userConverter.do2dto(getDbhubUserMapper().selectById(id))); + public User query(Long id) { + return userConverter.do2dto(getDbhubUserMapper().selectById(id)); } @Override - public DataResult query(String userName) { + public User query(String userName) { LambdaQueryWrapper query = new LambdaQueryWrapper<>(); if (Objects.nonNull(userName)) { query.eq(DbhubUserDO::getUserName, userName); } DbhubUserDO dbhubUserDO = getDbhubUserMapper().selectOne(query); - return DataResult.of(userConverter.do2dto(dbhubUserDO)); + return userConverter.do2dto(dbhubUserDO); } @Override - public ListResult listQuery(List idList) { + public List listQuery(List idList) { if (CollectionUtils.isEmpty(idList)) { - return ListResult.empty(); + return java.util.Collections.emptyList(); } LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.in(DbhubUserDO::getId, idList); List dataList = getDbhubUserMapper().selectList(queryWrapper); - List list = userConverter.do2dto(dataList); - return ListResult.of(list); + return userConverter.do2dto(dataList); } @Override - public PageResult pageQuery(UserPageQueryParam param, UserSelector selector) { + public ServicePage pageQuery(UserPageQueryParam param, UserSelector selector) { EasyLambdaQueryWrapper queryWrapper = new EasyLambdaQueryWrapper<>(); if (StringUtils.isNotBlank(param.getSearchKey())) { queryWrapper.and(wrapper -> wrapper.like(DbhubUserDO::getUserName, "%" + param.getSearchKey() + "%") @@ -108,11 +108,11 @@ public PageResult pageQuery(UserPageQueryParam param, UserSelector selecto List list = userConverter.do2dto(iPage.getRecords()); fillData(list, selector); - return PageResult.of(list, iPage.getTotal(), param); + return ServicePage.of(list, iPage.getTotal(), param.getPageNo(), param.getPageSize()); } @Override - public DataResult update(UserUpdateParam param) { + public Long update(UserUpdateParam param) { if (RoleCodeEnum.DESKTOP.getDefaultUserId().equals(param.getId())) { throw new BusinessException("user.canNotOperateSystemAccount"); } @@ -133,11 +133,11 @@ public DataResult update(UserUpdateParam param) { data.setRoleCode(null); } getDbhubUserMapper().updateById(data); - return DataResult.of(data.getId()); + return data.getId(); } @Override - public ActionResult delete(Long id) { + public void delete(Long id) { if (RoleCodeEnum.DESKTOP.getDefaultUserId().equals(id) || RoleCodeEnum.ADMIN.getDefaultUserId().equals(id)) { throw new BusinessException("user.canNotOperateSystemAccount"); } @@ -152,11 +152,11 @@ public ActionResult delete(Long id) { .eq(DataSourceAccessDO::getAccessObjectType, AccessObjectTypeEnum.USER.getCode()) ; getDataSourceAccessMapper().delete(dataSourceAccessQueryWrapper); - return ActionResult.isSuccess(); + } @Override - public DataResult create(UserCreateParam param) { + public Long create(UserCreateParam param) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.and(wrapper -> wrapper.eq(DbhubUserDO::getUserName, param.getUserName()) .or() @@ -176,7 +176,7 @@ public DataResult create(UserCreateParam param) { String bcryptPassword = DigestUtil.bcrypt(data.getPassword()); data.setPassword(bcryptPassword); getDbhubUserMapper().insert(data); - return DataResult.of(data.getId()); + return data.getId(); } private void fillData(List list, UserSelector selector) { diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ViewServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ViewServiceImpl.java index 57442e8b4..7763e7f4d 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ViewServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ViewServiceImpl.java @@ -1,26 +1,133 @@ package ai.chat2db.server.domain.core.impl; +import java.sql.Connection; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import ai.chat2db.server.domain.api.model.TreeNode; +import ai.chat2db.server.domain.api.param.TreeSearchParam; import ai.chat2db.server.domain.api.service.ViewService; +import ai.chat2db.server.domain.core.cache.LuceneIndexManager; +import ai.chat2db.server.domain.core.cache.LuceneIndexManagerFactory; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.model.Table; +import ai.chat2db.spi.model.View; import ai.chat2db.spi.sql.Chat2DBContext; -import org.springframework.stereotype.Service; +import lombok.extern.slf4j.Slf4j; @Service +@Slf4j public class ViewServiceImpl implements ViewService { + @Autowired + private LuceneIndexManagerFactory managerFactory; + @Override - public ListResult
views(String databaseName, String schemaName) { - return ListResult.of(Chat2DBContext.getMetaData().views(Chat2DBContext.getConnection(),databaseName, schemaName)); + public List
views(String databaseName, String schemaName) { + return Chat2DBContext.getMetaData().views(Chat2DBContext.getConnection(),databaseName, schemaName); + } + + @Override + public List
viewsWithCache(Long dataSourceId, String databaseName, String schemaName, String searchKey, boolean refresh) { + LuceneIndexManager mgr = managerFactory.getManager(dataSourceId); + View queryModel = View.builder() + .databaseName(databaseName) + .schemaName(schemaName) + .build(); + Long version = mgr.getMaxVersion(queryModel); + + if (refresh || version == null) { + loadAndCacheMetadata(mgr, databaseName, schemaName, version); + } + + return new ArrayList<>(mgr.search(queryModel, null, searchKey)); } @Override - public DataResult
detail(String databaseName, String schemaName, String tableName) { + public Table detail(String databaseName, String schemaName, String tableName) { MetaData metaSchema = Chat2DBContext.getMetaData(); Table table = metaSchema.view(Chat2DBContext.getConnection(), databaseName, schemaName, tableName); - return DataResult.of(table); + return table; } + @Override + public List searchTreeNodes(TreeSearchParam param) { + LuceneIndexManager mgr = managerFactory.getManager(param.getDataSourceId()); + View queryModel = View.builder() + .databaseName(param.getDatabaseName()) + .schemaName(param.getSchemaName()) + .build(); + Long version = mgr.getMaxVersion(queryModel); + + if (param.isRefresh() || version == null) { + loadAndCacheMetadata(mgr, param.getDatabaseName(), param.getSchemaName(), version); + } + + List views = mgr.search(queryModel, null, param.getSearchKey()); + List result = new ArrayList<>(); + for (Table view : views) { + TreeNode node = buildTreeNode(view); + result.add(node); + } + return result; + } + + private void loadAndCacheMetadata(LuceneIndexManager mgr, String databaseName, String schemaName, Long currentVersion) { + mgr.getLock().writeLock().lock(); + try { + Connection conn = Chat2DBContext.getConnection(); + MetaData meta = Chat2DBContext.getMetaData(); + List views = meta.views(conn, databaseName, schemaName).stream() + .map(View::from) + .toList(); + if (CollectionUtils.isEmpty(views)) { + mgr.deleteByDatabaseAndSchema(View.builder() + .databaseName(databaseName) + .schemaName(schemaName) + .build()); + return; + } + mgr.updateDocuments(views, currentVersion); + log.info("[View] Cached {} views for database: {}", views.size(), databaseName); + } catch (Exception e) { + log.error("[View] loadAndCacheMetadata error", e); + } finally { + mgr.getLock().writeLock().unlock(); + } + } + + private TreeNode buildTreeNode(Table view) { + List parentPath = new ArrayList<>(); + if (StringUtils.isNotBlank(view.getDatabaseName())) { + parentPath.add(view.getDatabaseName()); + } + if (StringUtils.isNotBlank(view.getSchemaName())) { + parentPath.add(view.getSchemaName()); + } + + Map extraParams = new HashMap<>(); + extraParams.put("databaseName", view.getDatabaseName()); + extraParams.put("schemaName", view.getSchemaName()); + extraParams.put("tableName", view.getName()); + + return TreeNode.builder() + .uuid("view-" + view.getName()) + .key(view.getName()) + .name(view.getName()) + .treeNodeType("view") + .comment(view.getComment()) + .isLeaf(true) + .parentPath(parentPath) + .extraParams(extraParams) + .build(); + } } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/service/VirtualFkSuggestionService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/service/VirtualFkSuggestionService.java new file mode 100644 index 000000000..ee2727b0a --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/service/VirtualFkSuggestionService.java @@ -0,0 +1,148 @@ +package ai.chat2db.server.domain.core.service; + +import ai.chat2db.server.domain.core.util.MetaNameUtils; +import ai.chat2db.spi.model.VirtualForeignKey; +import ai.chat2db.spi.model.VirtualForeignKeySuggestion; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.select.*; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.operators.relational.EqualsTo; +import net.sf.jsqlparser.expression.operators.conditional.AndExpression; +import net.sf.jsqlparser.expression.operators.conditional.OrExpression; +import org.springframework.stereotype.Service; + +import java.util.*; + +@Service +public class VirtualFkSuggestionService { + + public List suggest(Statement stmt, List existingFKs) { + Set existingFKKeys = buildExistingFKSet(existingFKs); + List suggestions = new ArrayList<>(); + if (stmt instanceof Select selectStmt) { + SelectBody body = selectStmt.getSelectBody(); + if (body instanceof PlainSelect plainSelect) { + Map aliasMap = buildAliasMap(plainSelect); + extractSuggestions(plainSelect, aliasMap, suggestions, existingFKKeys); + } + } + return suggestions; + } + + private Set buildExistingFKSet(List existingFKs) { + Set keys = new HashSet<>(); + if (existingFKs == null) return keys; + for (VirtualForeignKey vk : existingFKs) { + keys.add(buildFKKey(vk.getTableName(), vk.getColumn(), vk.getReferencedTable(), vk.getReferencedColumn())); + } + return keys; + } + + private String buildFKKey(String table, String column, String refTable, String refColumn) { + return cleanIdentifier(table).toLowerCase() + "." + cleanIdentifier(column).toLowerCase() + "->" + + cleanIdentifier(refTable).toLowerCase() + "." + cleanIdentifier(refColumn).toLowerCase(); + } + + private Map buildAliasMap(PlainSelect plainSelect) { + Map map = new HashMap<>(); + addFromItem(plainSelect.getFromItem(), map); + if (plainSelect.getJoins() != null) { + for (Join join : plainSelect.getJoins()) { + addFromItem(join.getRightItem(), map); + } + } + return map; + } + + private void addFromItem(FromItem item, Map map) { + if (item instanceof Table table) { + String name = cleanIdentifier(table.getName()); + map.put(name, name); + if (table.getAlias() != null) { + map.put(cleanIdentifier(table.getAlias().getName()), name); + } + } + } + + private void extractSuggestions(PlainSelect plainSelect, Map aliasMap, List suggestions, Set existingFKKeys) { + if (plainSelect.getJoins() != null) { + for (Join join : plainSelect.getJoins()) { + if (join.getOnExpressions() != null) { + for (Expression onExpression : join.getOnExpressions()) { + processExpression(onExpression, aliasMap, suggestions, existingFKKeys); + } + } + } + } + } + + private void processExpression(Expression expr, Map aliasMap, List suggestions, Set existingFKKeys) { + if (expr instanceof EqualsTo equalsTo) { + processEquality(equalsTo.getLeftExpression(), equalsTo.getRightExpression(), aliasMap, suggestions, existingFKKeys); + } else if (expr instanceof AndExpression andExpr) { + processExpression(andExpr.getLeftExpression(), aliasMap, suggestions, existingFKKeys); + processExpression(andExpr.getRightExpression(), aliasMap, suggestions, existingFKKeys); + } else if (expr instanceof OrExpression orExpr) { + processExpression(orExpr.getLeftExpression(), aliasMap, suggestions, existingFKKeys); + processExpression(orExpr.getRightExpression(), aliasMap, suggestions, existingFKKeys); + } + } + + private void processEquality(Expression left, Expression right, Map aliasMap, List suggestions, Set existingFKKeys) { + if (left instanceof Column cLeft && right instanceof Column cRight) { + if (cLeft.getTable() == null || cRight.getTable() == null) return; + + String lTableAlias = cleanIdentifier(cLeft.getTable().getName()); + String lCol = cleanIdentifier(cLeft.getColumnName()); + String rTableAlias = cleanIdentifier(cRight.getTable().getName()); + String rCol = cleanIdentifier(cRight.getColumnName()); + + String lRealTable = aliasMap.get(lTableAlias); + String rRealTable = aliasMap.get(rTableAlias); + + if (lRealTable != null && rRealTable != null && !lRealTable.equalsIgnoreCase(rRealTable)) { + boolean leftIsFK = lCol.toLowerCase().endsWith("_id"); + boolean rightIsFK = rCol.toLowerCase().endsWith("_id"); + + if (leftIsFK && !rightIsFK) { + addSuggestion(lRealTable, lCol, rRealTable, rCol, suggestions, existingFKKeys); + } else if (!leftIsFK && rightIsFK) { + addSuggestion(rRealTable, rCol, lRealTable, lCol, suggestions, existingFKKeys); + } + } + } + } + + private void addSuggestion(String srcTable, String srcCol, String tgtTable, String tgtCol, List suggestions, Set existingFKKeys) { + srcTable = cleanIdentifier(srcTable); + srcCol = cleanIdentifier(srcCol); + tgtTable = cleanIdentifier(tgtTable); + tgtCol = cleanIdentifier(tgtCol); + String fkKey = buildFKKey(srcTable, srcCol, tgtTable, tgtCol); + if (existingFKKeys.contains(fkKey)) { + return; + } + for (VirtualForeignKeySuggestion s : suggestions) { + if (s.getSourceTable().equalsIgnoreCase(srcTable) && + s.getSourceColumn().equalsIgnoreCase(srcCol) && + s.getTargetTable().equalsIgnoreCase(tgtTable) && + s.getTargetColumn().equalsIgnoreCase(tgtCol)) { + return; + } + } + suggestions.add(VirtualForeignKeySuggestion.builder() + .sourceTable(srcTable) + .sourceColumn(srcCol) + .targetTable(tgtTable) + .targetColumn(tgtCol) + .reason("JOIN condition") + .build()); + } + + private String cleanIdentifier(String name) { + String cleanedName = MetaNameUtils.getMetaName(name); + return cleanedName == null ? "" : cleanedName; + } +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/util/DesUtil.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/util/DesUtil.java index a5eaf3ab5..6f5c29ead 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/util/DesUtil.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/util/DesUtil.java @@ -5,6 +5,7 @@ import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; +import java.nio.charset.StandardCharsets; import java.security.Key; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; @@ -119,7 +120,7 @@ public void setKey(Key key) { public void getKey(String secretKey) { try { SecureRandom secureRandom = SecureRandom.getInstance(SHA1PRNG); - secureRandom.setSeed(secretKey.getBytes()); + secureRandom.setSeed(secretKey.getBytes(StandardCharsets.UTF_8)); KeyGenerator generator = null; try { generator = KeyGenerator.getInstance(DES); diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/Dbutils.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/Dbutils.java index ce829f212..fd42b8266 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/Dbutils.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/Dbutils.java @@ -112,7 +112,20 @@ private static void initFlyway(DataSource dataSource) { .dataSource(dataSource) .locations("classpath:db/migration") .load(); - flyway.migrate(); + try { + flyway.migrate(); + } catch (Exception e) { + log.warn("Migration failed, attempting repair: {}", e.getMessage()); + try { + // 尝试修复并重新迁移 + flyway.repair(); + flyway.migrate(); + log.info("Repair and re-migrate successfully"); + } catch (Exception repairException) { + log.error("Repair and migrate failed", repairException); + throw repairException; + } + } configJson.setLatestStartupSuccessVersion(currentVersion); @@ -187,7 +200,7 @@ private static void registryMapperXml(MybatisConfiguration configuration, String Enumeration mapper = contextClassLoader.getResources(classPath); while (mapper.hasMoreElements()) { URL url = mapper.nextElement(); - if (url.getProtocol().equals("file")) { + if ("file".equals(url.getProtocol())) { String path = url.getPath(); File file = new File(path); File[] files = file.listFiles(); diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TableVectorMappingDO.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/AiConversationDO.java similarity index 50% rename from chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TableVectorMappingDO.java rename to chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/AiConversationDO.java index fb020c14f..73c63a8bd 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TableVectorMappingDO.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/AiConversationDO.java @@ -3,53 +3,35 @@ import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; -import java.io.Serializable; import lombok.Getter; import lombok.Setter; +import java.io.Serializable; +import java.time.LocalDateTime; + /** - *

- * milvus映射表保存记录 - *

- * - * @author chat2db - * @since 2023-10-14 + * AI 会话表 */ @Getter @Setter -@TableName("TABLE_VECTOR_MAPPING") -public class TableVectorMappingDO implements Serializable { +@TableName("AI_CONVERSATION") +public class AiConversationDO implements Serializable { private static final long serialVersionUID = 1L; - /** - * 主键 - */ @TableId(value = "ID", type = IdType.AUTO) private Long id; - /** - * api key - */ - private String apiKey; + private LocalDateTime gmtCreate; + private LocalDateTime gmtModified; - /** - * 数据源连接ID - */ + private String conversationId; + private Long userId; + private String title; private Long dataSourceId; - - /** - * 数据库名称 - */ - private String database; - - /** - * schema名称 - */ - private String schema; - - /** - * 向量保存状态 - */ + private String databaseName; + private String schemaName; + private Integer messageCount; + private String lastMessagePreview; private String status; } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/AiMessageDO.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/AiMessageDO.java new file mode 100644 index 000000000..df9785429 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/AiMessageDO.java @@ -0,0 +1,36 @@ +package ai.chat2db.server.domain.repository.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Getter; +import lombok.Setter; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * AI 消息表 + */ +@Getter +@Setter +@TableName("AI_MESSAGE") +public class AiMessageDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @TableId(value = "ID", type = IdType.AUTO) + private Long id; + + private LocalDateTime gmtCreate; + + private String conversationId; + private Long userId; + private String messageId; + private String role; + private String content; + private String thinking; + private String promptType; + private String sqlExtracted; + private Integer sequenceNo; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DataGenerationRuleDO.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DataGenerationRuleDO.java new file mode 100644 index 000000000..bd4d4e461 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DataGenerationRuleDO.java @@ -0,0 +1,47 @@ +package ai.chat2db.server.domain.repository.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Getter; +import lombok.Setter; + +import java.io.Serializable; +import java.time.LocalDateTime; + +@Getter +@Setter +@TableName("data_generation_rule") +public class DataGenerationRuleDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + private LocalDateTime gmtCreate; + + private LocalDateTime gmtModified; + + @TableField("data_source_id") + private Long dataSourceId; + + @TableField("database_name") + private String databaseName; + + @TableField("schema_name") + private String schemaName; + + @TableField("table_name") + private String tableName; + + @TableField("row_count") + private Integer rowCount; + + @TableField("column_configs") + private String columnConfigs; + + @TableField("user_id") + private Long userId; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DataSourceSortDO.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DataSourceSortDO.java new file mode 100644 index 000000000..7357bcf03 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DataSourceSortDO.java @@ -0,0 +1,35 @@ +package ai.chat2db.server.domain.repository.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import java.io.Serializable; +import java.util.Date; +import lombok.Getter; +import lombok.Setter; + +/** + * 数据源连接用户排序表 + * + * @author chat2db + */ +@Getter +@Setter +@TableName("DATA_SOURCE_SORT") +public class DataSourceSortDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @TableId(value = "ID", type = IdType.AUTO) + private Long id; + + private Date gmtCreate; + + private Date gmtModified; + + private Long userId; + + private Long dataSourceId; + + private Integer sort; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TableCacheVersionDO.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DeprecatedTableDO.java similarity index 56% rename from chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TableCacheVersionDO.java rename to chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DeprecatedTableDO.java index 9c3d4e0f7..37f0eca47 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TableCacheVersionDO.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DeprecatedTableDO.java @@ -1,26 +1,18 @@ package ai.chat2db.server.domain.repository.entity; import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; -import java.io.Serializable; -import java.util.Date; import lombok.Getter; import lombok.Setter; -/** - *

- * table cache version - *

- * - * @author chat2db - * @since 2023-10-11 - */ +import java.io.Serializable; +import java.time.LocalDateTime; + @Getter @Setter -@TableName("TABLE_CACHE_VERSION") -public class TableCacheVersionDO implements Serializable { +@TableName("DEPRECATED_TABLE") +public class DeprecatedTableDO implements Serializable { private static final long serialVersionUID = 1L; @@ -33,12 +25,12 @@ public class TableCacheVersionDO implements Serializable { /** * 创建时间 */ - private Date gmtCreate; + private LocalDateTime gmtCreate; /** * 修改时间 */ - private Date gmtModified; + private LocalDateTime gmtModified; /** * 数据源连接ID @@ -50,29 +42,20 @@ public class TableCacheVersionDO implements Serializable { */ private String databaseName; - /** - * schema名称 - */ - private String schemaName; /** - * 唯一索引 + * 保存名称 */ - @TableField(value = "`key`") - private String key; + private String schemaName; /** - * 版本 + * userId */ - private Long version; + private Long userId; /** - * 表数量 + * tableName */ - private Long tableCount; + private String tableName; - /** - * 状态 - */ - private String status; } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/ForeignKeyDO.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/ForeignKeyDO.java new file mode 100644 index 000000000..21ea2300a --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/ForeignKeyDO.java @@ -0,0 +1,62 @@ +package ai.chat2db.server.domain.repository.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Getter; +import lombok.Setter; + +import java.io.Serializable; +import java.time.LocalDateTime; + +@Getter +@Setter +@TableName("foreign_key") +public class ForeignKeyDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + private LocalDateTime gmtCreate; + + private LocalDateTime gmtModified; + + private Long dataSourceId; + + private String databaseName; + + private String schemaName; + + private String tableName; + + @TableField("column_name") + private String columnName; + + @TableField("fk_name") + private String fkName; + + @TableField("referenced_table") + private String referencedTable; + + @TableField("referenced_column") + private String referencedColumnName; + + @TableField("referenced_schema") + private String referencedSchema; + + @TableField("referenced_database") + private String referencedDatabase; + + private Integer updateRule; + + private Integer deleteRule; + + private String comment; + + private LocalDateTime syncTime; + + private String syncVersion; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/LoginAttempt.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/LoginAttempt.java new file mode 100644 index 000000000..d78e5538c --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/LoginAttempt.java @@ -0,0 +1,20 @@ +package ai.chat2db.server.domain.repository.entity; + +import java.util.Date; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; + +import lombok.Data; + +@Data +@TableName("login_attempt") +public class LoginAttempt { + @TableId(type = IdType.AUTO) + private Long id; + private String clientFingerprint; + private Integer attempts; + private Date lastAttemptTime; + private Date lockedUntil; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TableCacheDO.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TableCacheDO.java deleted file mode 100644 index 5eace2195..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TableCacheDO.java +++ /dev/null @@ -1,83 +0,0 @@ -package ai.chat2db.server.domain.repository.entity; - -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import java.io.Serializable; -import java.util.Date; -import lombok.Getter; -import lombok.Setter; - -/** - *

- * table cache - *

- * - * @author chat2db - * @since 2023-10-11 - */ -@Getter -@Setter -@TableName("TABLE_CACHE") -public class TableCacheDO implements Serializable { - - private static final long serialVersionUID = 1L; - - /** - * 主键 - */ - @TableId(value = "ID", type = IdType.AUTO) - private Long id; - - /** - * 创建时间 - */ - private Date gmtCreate; - - /** - * 修改时间 - */ - private Date gmtModified; - - /** - * 数据源连接ID - */ - private Long dataSourceId; - - /** - * db名称 - */ - private String databaseName; - - /** - * schema名称 - */ - private String schemaName; - - /** - * table名称 - */ - private String tableName; - - /** - * 唯一索引 - */ - @TableField(value = "`key`") - private String key; - - /** - * 版本 - */ - private Long version; - - /** - * 表字段 - */ - private String columns; - - /** - * 自定义扩展字段json - */ - private String extendInfo; -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TaskDO.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TaskDO.java index 9e4661a60..e875e9a30 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TaskDO.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TaskDO.java @@ -97,5 +97,6 @@ public class TaskDO implements Serializable { /** * task content */ - private byte[] content; + private String content; + } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/VirtualForeignKeyDO.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/VirtualForeignKeyDO.java new file mode 100644 index 000000000..e2e8478b2 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/VirtualForeignKeyDO.java @@ -0,0 +1,54 @@ +package ai.chat2db.server.domain.repository.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Getter; +import lombok.Setter; + +import java.io.Serializable; +import java.time.LocalDateTime; + +@Getter +@Setter +@TableName("virtual_foreign_key") +public class VirtualForeignKeyDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + private LocalDateTime gmtCreate; + + private LocalDateTime gmtModified; + + private Long dataSourceId; + + private String databaseName; + + private String schemaName; + + private String tableName; + + @TableField("column_name") + private String columnName; + + @TableField("vk_name") + private String vkName; + + @TableField("referenced_table") + private String referencedTable; + + @TableField("referenced_column") + private String referencedColumnName; + + private String comment; + + @TableField("source_type") + private String sourceType; + + @TableField("user_id") + private Long userId; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/AiConversationMapper.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/AiConversationMapper.java new file mode 100644 index 000000000..8eae8b953 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/AiConversationMapper.java @@ -0,0 +1,11 @@ +package ai.chat2db.server.domain.repository.mapper; + +import ai.chat2db.server.domain.repository.entity.AiConversationDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + * AI 会话表 Mapper 接口 + */ +public interface AiConversationMapper extends BaseMapper { + +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/AiMessageMapper.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/AiMessageMapper.java new file mode 100644 index 000000000..7656919f5 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/AiMessageMapper.java @@ -0,0 +1,11 @@ +package ai.chat2db.server.domain.repository.mapper; + +import ai.chat2db.server.domain.repository.entity.AiMessageDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + * AI 消息表 Mapper 接口 + */ +public interface AiMessageMapper extends BaseMapper { + +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataGenerationRuleMapper.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataGenerationRuleMapper.java new file mode 100644 index 000000000..b65fc1fba --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataGenerationRuleMapper.java @@ -0,0 +1,11 @@ +package ai.chat2db.server.domain.repository.mapper; + +import ai.chat2db.server.domain.repository.entity.DataGenerationRuleDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + * 数据生成规则Mapper + */ +public interface DataGenerationRuleMapper extends BaseMapper { + +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataSourceSortMapper.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataSourceSortMapper.java new file mode 100644 index 000000000..0064c5fac --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataSourceSortMapper.java @@ -0,0 +1,12 @@ +package ai.chat2db.server.domain.repository.mapper; + +import ai.chat2db.server.domain.repository.entity.DataSourceSortDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + * 数据源连接用户排序 Mapper + * + * @author chat2db + */ +public interface DataSourceSortMapper extends BaseMapper { +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DeprecatedTableMapper.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DeprecatedTableMapper.java new file mode 100644 index 000000000..8f3e3b3e7 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DeprecatedTableMapper.java @@ -0,0 +1,7 @@ +package ai.chat2db.server.domain.repository.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import ai.chat2db.server.domain.repository.entity.DeprecatedTableDO; + +public interface DeprecatedTableMapper extends BaseMapper { +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/ForeignKeyMapper.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/ForeignKeyMapper.java new file mode 100644 index 000000000..9ab3490d7 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/ForeignKeyMapper.java @@ -0,0 +1,8 @@ +package ai.chat2db.server.domain.repository.mapper; + +import ai.chat2db.server.domain.repository.entity.ForeignKeyDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +public interface ForeignKeyMapper extends BaseMapper { + +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/LoginAttemptMapper.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/LoginAttemptMapper.java new file mode 100644 index 000000000..fc691a0be --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/LoginAttemptMapper.java @@ -0,0 +1,18 @@ +package ai.chat2db.server.domain.repository.mapper; + +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; +import org.apache.ibatis.annotations.Update; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +import ai.chat2db.server.domain.repository.entity.LoginAttempt; + +public interface LoginAttemptMapper extends BaseMapper { + @Update("UPDATE login_attempt SET attempts = attempts + 1, last_attempt_time = NOW()" + + " WHERE client_fingerprint = #{clientFingerprint}") + int incrementAttempts(@Param("clientFingerprint") String clientFingerprint); + + @Select("SELECT * FROM login_attempt WHERE client_fingerprint = #{clientFingerprint}") + LoginAttempt findByFingerprint(String clientFingerprint); +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TableCacheMapper.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TableCacheMapper.java deleted file mode 100644 index 7c26d0cd1..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TableCacheMapper.java +++ /dev/null @@ -1,24 +0,0 @@ -package ai.chat2db.server.domain.repository.mapper; - -import ai.chat2db.server.domain.repository.entity.TableCacheDO; -import ai.chat2db.server.domain.repository.entity.TeamUserDO; -import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import com.baomidou.mybatisplus.core.metadata.IPage; -import org.apache.ibatis.annotations.Param; - -import java.util.List; - -/** - *

- * table cache Mapper 接口 - *

- * - * @author chat2db - * @since 2023-10-11 - */ -public interface TableCacheMapper extends BaseMapper { - - void batchInsert(List list); - - IPage pageQuery(IPage page, @Param("dataSourceId") Long dataSourceId, @Param("databaseName") String databaseName, @Param("schemaName") String schemaName, @Param("searchKey") String searchKey); -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TableCacheVersionMapper.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TableCacheVersionMapper.java deleted file mode 100644 index 5ac07037f..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TableCacheVersionMapper.java +++ /dev/null @@ -1,16 +0,0 @@ -package ai.chat2db.server.domain.repository.mapper; - -import ai.chat2db.server.domain.repository.entity.TableCacheVersionDO; -import com.baomidou.mybatisplus.core.mapper.BaseMapper; - -/** - *

- * table cache version Mapper 接口 - *

- * - * @author chat2db - * @since 2023-10-11 - */ -public interface TableCacheVersionMapper extends BaseMapper { - -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TableVectorMappingMapper.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TableVectorMappingMapper.java deleted file mode 100644 index 40749c54e..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TableVectorMappingMapper.java +++ /dev/null @@ -1,16 +0,0 @@ -package ai.chat2db.server.domain.repository.mapper; - -import ai.chat2db.server.domain.repository.entity.TableVectorMappingDO; -import com.baomidou.mybatisplus.core.mapper.BaseMapper; - -/** - *

- * milvus映射表保存记录 Mapper 接口 - *

- * - * @author chat2db - * @since 2023-10-14 - */ -public interface TableVectorMappingMapper extends BaseMapper { - -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TaskMapper.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TaskMapper.java index 1a641ab83..795a38d57 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TaskMapper.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TaskMapper.java @@ -15,5 +15,5 @@ */ public interface TaskMapper extends BaseMapper { - IPage pageQuery(IPage page, @Param("userId") Long userId,@Param("deleted") String deleted); + IPage pageQuery(IPage page, @Param("param") Object param); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/VirtualForeignKeyMapper.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/VirtualForeignKeyMapper.java new file mode 100644 index 000000000..72af958c9 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/VirtualForeignKeyMapper.java @@ -0,0 +1,8 @@ +package ai.chat2db.server.domain.repository.mapper; + +import ai.chat2db.server.domain.repository.entity.VirtualForeignKeyDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +public interface VirtualForeignKeyMapper extends BaseMapper { + +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V2_1_10__user.sql b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V2_1_10__user.sql new file mode 100644 index 000000000..47245b8b7 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V2_1_10__user.sql @@ -0,0 +1 @@ +update DBHUB_USER set USER_NAME='hejianjun' WHERE USER_NAME='$2a$10$gMvgU3XegmyrBKSHluSLu.uKDOfjGQN1g267bnLSPrzHeRmSUOany' \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V2_1_11__login_attempt.sql b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V2_1_11__login_attempt.sql new file mode 100644 index 000000000..304166afb --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V2_1_11__login_attempt.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS login_attempt ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + client_fingerprint VARCHAR(64) UNIQUE NOT NULL, + attempts INT DEFAULT 0, + last_attempt_time TIMESTAMP, + locked_until TIMESTAMP +); \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V2_1_12__task_total_count.sql b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V2_1_12__task_total_count.sql new file mode 100644 index 000000000..145c858ba --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V2_1_12__task_total_count.sql @@ -0,0 +1 @@ +ALTER TABLE TASK ADD COLUMN TOTAL_COUNT INT DEFAULT 0; \ No newline at end of file diff --git "a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V2_1_13__\345\272\237\345\274\203\350\241\250.sql" "b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V2_1_13__\345\272\237\345\274\203\350\241\250.sql" new file mode 100644 index 000000000..2fd5cf6b5 --- /dev/null +++ "b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V2_1_13__\345\272\237\345\274\203\350\241\250.sql" @@ -0,0 +1,13 @@ +CREATE TABLE IF NOT EXISTS `deprecated_table` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + `data_source_id` bigint(20) unsigned NOT NULL COMMENT '数据源连接ID', + `database_name` varchar(128) DEFAULT NULL COMMENT 'db名称', + `schema_name` varchar(128) DEFAULT NULL COMMENT 'schema名称', + `table_name` varchar(128) DEFAULT NULL COMMENT 'table_name', + `user_id` bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT '用户id', + PRIMARY KEY (`id`) + ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='DEPRECATED TABLES' +; +create INDEX idx_user_id_data_source_id_deprecated on deprecated_table(user_id,data_source_id) ; diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V2_1_14__foreign_key_storage.sql b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V2_1_14__foreign_key_storage.sql new file mode 100644 index 000000000..4f5d8d659 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V2_1_14__foreign_key_storage.sql @@ -0,0 +1,45 @@ +CREATE TABLE IF NOT EXISTS `foreign_key` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + `data_source_id` bigint(20) unsigned NOT NULL COMMENT '数据源ID', + `database_name` varchar(128) DEFAULT NULL COMMENT '数据库名', + `schema_name` varchar(128) DEFAULT NULL COMMENT '模式名', + `table_name` varchar(128) NOT NULL COMMENT '当前表名', + `column_name` varchar(128) NOT NULL COMMENT '当前列名', + `fk_name` varchar(256) DEFAULT NULL COMMENT '外键名称', + `referenced_table` varchar(128) NOT NULL COMMENT '引用表名', + `referenced_column` varchar(128) NOT NULL COMMENT '引用列名', + `referenced_schema` varchar(128) DEFAULT NULL COMMENT '引用模式名', + `referenced_database` varchar(128) DEFAULT NULL COMMENT '引用数据库名', + `update_rule` int DEFAULT NULL COMMENT '更新规则', + `delete_rule` int DEFAULT NULL COMMENT '删除规则', + `comment` varchar(512) DEFAULT NULL COMMENT '备注', + `sync_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '同步时间', + `sync_version` varchar(64) DEFAULT NULL COMMENT '同步版本号', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_fk` (`data_source_id`,`database_name`,`schema_name`,`table_name`,`column_name`,`referenced_table`,`referenced_column`), + INDEX `idx_fk_data_source` (`data_source_id`), + INDEX `idx_fk_referenced` (`data_source_id`,`referenced_table`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='真实外键存储表'; + +CREATE TABLE IF NOT EXISTS `virtual_foreign_key` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + `data_source_id` bigint(20) unsigned NOT NULL COMMENT '数据源ID', + `database_name` varchar(128) DEFAULT NULL COMMENT '数据库名', + `schema_name` varchar(128) DEFAULT NULL COMMENT '模式名', + `table_name` varchar(128) NOT NULL COMMENT '当前表名', + `column_name` varchar(128) NOT NULL COMMENT '当前列名', + `vk_name` varchar(256) DEFAULT NULL COMMENT '虚拟外键名称', + `referenced_table` varchar(128) NOT NULL COMMENT '引用表名', + `referenced_column` varchar(128) NOT NULL COMMENT '引用列名', + `comment` varchar(512) DEFAULT NULL COMMENT '用户备注', + `source_type` varchar(32) NOT NULL DEFAULT 'MANUAL' COMMENT '来源: MANUAL/INFERRED', + `user_id` bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT '创建用户ID', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_vk` (`data_source_id`,`database_name`,`schema_name`,`table_name`,`column_name`,`referenced_table`), + INDEX `idx_vk_data_source` (`data_source_id`), + INDEX `idx_vk_user_source` (`user_id`,`data_source_id`,`source_type`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='虚拟外键存储表'; diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V2_1_15__system_config_content_clob.sql b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V2_1_15__system_config_content_clob.sql new file mode 100644 index 000000000..3edbae22f --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V2_1_15__system_config_content_clob.sql @@ -0,0 +1 @@ +ALTER TABLE system_config ALTER COLUMN content CLOB; diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V2_1_16__data_generation_rule_storage.sql b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V2_1_16__data_generation_rule_storage.sql new file mode 100644 index 000000000..7e14870fb --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V2_1_16__data_generation_rule_storage.sql @@ -0,0 +1,18 @@ +DROP TABLE IF EXISTS `data_generation_rule`; + +CREATE TABLE IF NOT EXISTS `data_generation_rule` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + `data_source_id` bigint(20) unsigned NOT NULL COMMENT '数据源ID', + `database_name` varchar(128) DEFAULT NULL COMMENT '数据库名', + `schema_name` varchar(128) DEFAULT NULL COMMENT '模式名', + `table_name` varchar(128) NOT NULL COMMENT '表名', + `row_count` int unsigned NOT NULL DEFAULT 100 COMMENT '生成行数', + `column_configs` text COMMENT '列配置JSON: [{"columnName":"id","expression":"..."},{"columnName":"name","expression":"..."}]', + `user_id` bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT '创建用户ID', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_generation_rule` (`data_source_id`,`database_name`,`schema_name`,`table_name`), + INDEX `idx_generation_rule_data_source` (`data_source_id`), + INDEX `idx_generation_rule_user_source` (`user_id`,`data_source_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数据生成规则表'; diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V2_1_17__task_content_string.sql b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V2_1_17__task_content_string.sql new file mode 100644 index 000000000..6feebb5dd --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V2_1_17__task_content_string.sql @@ -0,0 +1,24 @@ +-- H2 不支持 BLOB 直接转 CLOB,删除表重建 +DROP TABLE IF EXISTS TASK; + +CREATE TABLE IF NOT EXISTS TASK ( + ID bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + GMT_CREATE datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + GMT_MODIFIED datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', + DATA_SOURCE_ID bigint unsigned NULL COMMENT '数据源连接ID', + DATABASE_NAME varchar(128) DEFAULT NULL COMMENT 'db名称', + SCHEMA_NAME varchar(128) DEFAULT NULL COMMENT 'schema名称', + TABLE_NAME varchar(128) DEFAULT NULL COMMENT 'table_name', + DELETED varchar(10) DEFAULT NULL COMMENT '是否被删除,y表示删除,n表示未删除', + USER_ID bigint unsigned NOT NULL DEFAULT 0 COMMENT '用户id', + TASK_TYPE varchar(128) DEFAULT NULL COMMENT 'task type', + TASK_STATUS varchar(128) DEFAULT NULL COMMENT 'task status', + TASK_PROGRESS varchar(128) DEFAULT NULL COMMENT 'task progress', + TASK_NAME varchar(128) DEFAULT NULL COMMENT 'task name', + CONTENT CLOB DEFAULT NULL COMMENT 'task content', + DOWNLOAD_URL varchar(512) DEFAULT NULL COMMENT 'down load url', + TOTAL_COUNT int DEFAULT 0 COMMENT 'total count', + PRIMARY KEY (ID) +); + +CREATE INDEX IF NOT EXISTS idx_task_user_id ON TASK(USER_ID); diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V2_1_18__data_source_sort.sql b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V2_1_18__data_source_sort.sql new file mode 100644 index 000000000..838370d96 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V2_1_18__data_source_sort.sql @@ -0,0 +1,12 @@ +CREATE TABLE IF NOT EXISTS `data_source_sort` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `gmt_create` datetime NOT NULL COMMENT '创建时间', + `gmt_modified` datetime NOT NULL COMMENT '修改时间', + `user_id` bigint(20) unsigned NOT NULL COMMENT '用户ID', + `data_source_id` bigint(20) unsigned NOT NULL COMMENT '数据源ID', + `sort` int NOT NULL COMMENT '排序', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_data_source_sort` (`user_id`, `data_source_id`), + KEY `idx_data_source_sort_user` (`user_id`, `sort`) +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '数据源连接用户排序表'; diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V2_1_19__ai_chat.sql b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V2_1_19__ai_chat.sql new file mode 100644 index 000000000..0d16df570 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V2_1_19__ai_chat.sql @@ -0,0 +1,36 @@ +CREATE TABLE IF NOT EXISTS `ai_conversation` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + `conversation_id` varchar(64) NOT NULL COMMENT '客户端会话UUID', + `user_id` bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT '用户id', + `title` varchar(256) DEFAULT NULL COMMENT '会话标题(异步AI生成)', + `data_source_id` bigint(20) unsigned DEFAULT NULL COMMENT '关联数据源', + `database_name` varchar(128) DEFAULT NULL COMMENT '数据库名', + `schema_name` varchar(128) DEFAULT NULL COMMENT 'schema名', + `message_count` int(11) NOT NULL DEFAULT 0 COMMENT '消息数量', + `last_message_preview` varchar(512) DEFAULT NULL COMMENT '最后一条消息预览', + `status` varchar(32) NOT NULL DEFAULT 'ACTIVE' COMMENT 'ACTIVE/ARCHIVED/DELETED', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_conv_id` (`conversation_id`), + KEY `idx_user_modified` (`user_id`, `gmt_modified`), + KEY `idx_user_ds` (`user_id`, `data_source_id`), + KEY `idx_user_status` (`user_id`, `status`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AI 会话表'; + +CREATE TABLE IF NOT EXISTS `ai_message` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `conversation_id` varchar(64) NOT NULL COMMENT '会话ID', + `user_id` bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT '用户id', + `message_id` varchar(64) NOT NULL COMMENT '客户端消息UUID', + `role` varchar(16) NOT NULL COMMENT 'user/assistant', + `content` longtext NOT NULL COMMENT '消息内容', + `thinking` longtext DEFAULT NULL COMMENT '思考过程', + `prompt_type` varchar(32) DEFAULT NULL COMMENT 'PromptType', + `sql_extracted` longtext DEFAULT NULL COMMENT '提取的SQL(用于revision续接)', + `sequence_no` int(11) NOT NULL COMMENT '消息序号(0,1,2...)', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_conv_msg` (`conversation_id`, `message_id`), + KEY `idx_conv_seq` (`conversation_id`, `sequence_no`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AI 消息表'; diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/TableVectorMappingMapper.xml b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/AiConversationMapper.xml similarity index 63% rename from chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/TableVectorMappingMapper.xml rename to chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/AiConversationMapper.xml index 96907d2dd..1eef56809 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/TableVectorMappingMapper.xml +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/AiConversationMapper.xml @@ -1,5 +1,5 @@ - + diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/AiMessageMapper.xml b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/AiMessageMapper.xml new file mode 100644 index 000000000..fd4b6b022 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/AiMessageMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceCustomMapper.xml b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceCustomMapper.xml index 3c01bac27..b636bf536 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceCustomMapper.xml +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceCustomMapper.xml @@ -5,6 +5,7 @@ diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceSortMapper.xml b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceSortMapper.xml new file mode 100644 index 000000000..06ea49800 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceSortMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/LoginAttemptMapper.xml b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/LoginAttemptMapper.xml new file mode 100644 index 000000000..e91057466 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/LoginAttemptMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/TableCacheMapper.xml b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/TableCacheMapper.xml deleted file mode 100644 index c367e2605..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/TableCacheMapper.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - insert into TABLE_CACHE - (data_source_id,database_name,schema_name,table_name,`key`,version,columns,extend_info) - values - - (#{item.dataSourceId},#{item.databaseName},#{item.schemaName},#{item.tableName},#{item.key},#{item.version},#{item.columns},#{item.extendInfo}) - - - - - diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/TaskMapper.xml b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/TaskMapper.xml index 498d1b6d3..652b2d783 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/TaskMapper.xml +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/TaskMapper.xml @@ -3,33 +3,39 @@ diff --git a/chat2db-server/chat2db-server-start/pom.xml b/chat2db-server/chat2db-server-start/pom.xml index 1c1cbef8e..98d007e63 100644 --- a/chat2db-server/chat2db-server-start/pom.xml +++ b/chat2db-server/chat2db-server-start/pom.xml @@ -100,6 +100,12 @@ org.zalando logbook-spring-boot-starter + + + + io.micrometer + context-propagation + chat2db-server-start @@ -107,6 +113,9 @@ org.springframework.boot spring-boot-maven-plugin + + ai.chat2db.server.start.Chat2dbLiteApplication + diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Application.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Application.java deleted file mode 100644 index 43ab662c6..000000000 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Application.java +++ /dev/null @@ -1,36 +0,0 @@ -package ai.chat2db.server.start; - -import ai.chat2db.server.domain.repository.Dbutils; -import ai.chat2db.server.tools.common.util.ConfigUtils; -import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cache.annotation.EnableCaching; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.scheduling.annotation.EnableAsync; -import org.springframework.scheduling.annotation.EnableScheduling; -import org.springframework.stereotype.Indexed; - - -/** - * 启动类 - * - * @author Jiaju Zhuang - */ -@SpringBootApplication -@ComponentScan(value = {"ai.chat2db.server"}) -@Indexed -@EnableCaching -@EnableScheduling -@EnableAsync -@Slf4j -public class Application { - - public static void main(String[] args) { - ConfigUtils.initProcess(); - new Thread(() -> { - Dbutils.init(); - }).start(); - SpringApplication.run(Application.class, args); - } -} diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Chat2dbLiteApplication.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Chat2dbLiteApplication.java new file mode 100644 index 000000000..2f78bfaf2 --- /dev/null +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Chat2dbLiteApplication.java @@ -0,0 +1,64 @@ +package ai.chat2db.server.start; + +import ai.chat2db.server.domain.repository.Dbutils; +import ai.chat2db.server.tools.common.util.ConfigUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration; +import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.stereotype.Indexed; + + +/** + * Chat2DB 简化启动类(无登录鉴权) + * + *

适用于: + *

    + *
  • 本地开发测试场景
  • + *
  • 不需要用户登录鉴权的简化部署
  • + *
  • 单元测试启动
  • + *
+ * + *

JAR 文件:chat2db-server-start.jar + * + *

注意:生产环境请使用 Chat2dbWebApplication(chat2db-server-web-start.jar) + * + * @author Jiaju Zhuang + */ +@SpringBootApplication(exclude = { + MailSenderAutoConfiguration.class, + QuartzAutoConfiguration.class +}) +@ComponentScan(value = {"ai.chat2db.server"}, lazyInit = true) +@Indexed +@EnableCaching +@EnableScheduling +@EnableAsync +@Slf4j +public class Chat2dbLiteApplication { + + private static long startTime; + + public static void main(String[] args) { + startTime = System.currentTimeMillis(); + log.info("[Startup] Starting Chat2dbLiteApplication..."); + ConfigUtils.initProcess(); + new Thread(() -> { + Dbutils.init(); + }).start(); + SpringApplication.run(Chat2dbLiteApplication.class, args); + } + + @EventListener(ApplicationReadyEvent.class) + public void onApplicationReady() { + long elapsed = System.currentTimeMillis() - startTime; + log.info("[Startup] Chat2dbLiteApplication started successfully in {}ms ({}s)", elapsed, String.format("%.2f", elapsed / 1000.0)); + } +} diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/AsyncContextRefreshedListener.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/AsyncContextRefreshedListener.java deleted file mode 100644 index 5c4bef0c2..000000000 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/AsyncContextRefreshedListener.java +++ /dev/null @@ -1,32 +0,0 @@ -//package ai.chat2db.server.start.config.config; -// -//import ai.chat2db.server.tools.common.model.ConfigJson; -//import ai.chat2db.server.tools.common.util.ConfigUtils; -//import lombok.extern.slf4j.Slf4j; -//import org.apache.commons.lang3.StringUtils; -//import org.springframework.context.ApplicationListener; -//import org.springframework.context.event.ContextRefreshedEvent; -//import org.springframework.scheduling.annotation.Async; -//import org.springframework.stereotype.Component; -// -///** -// * Execute tasks after startup is completed -// * -// * @author Jiaju Zhuang -// */ -//@Component -//@Slf4j -//public class AsyncContextRefreshedListener implements ApplicationListener { -// @Override -// @Async -// public void onApplicationEvent(ContextRefreshedEvent event) { -// // Successfully set up startup -// String currentVersion = ConfigUtils.getLocalVersion(); -// ConfigJson configJson = ConfigUtils.getConfig(); -// if (StringUtils.isNotBlank(currentVersion) && !StringUtils.equals(currentVersion, -// configJson.getLatestStartupSuccessVersion())) { -// configJson.setLatestStartupSuccessVersion(currentVersion); -// ConfigUtils.setConfig(configJson); -// } -// } -//} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/Chat2dbWebMvcConfigurer.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/Chat2dbWebMvcConfigurer.java index 2c178c95d..e1adda157 100644 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/Chat2dbWebMvcConfigurer.java +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/Chat2dbWebMvcConfigurer.java @@ -71,7 +71,7 @@ public boolean preHandle(@NotNull HttpServletRequest request, @NotNull HttpServl Long userId = RoleCodeEnum.DESKTOP.getDefaultUserId(); Long finalUserId = userId; LoginUser loginUser = MemoryCacheManage.computeIfAbsent(CacheKey.getLoginUserKey(userId), () -> { - User user = userService.query(finalUserId).getData(); + User user = userService.query(finalUserId); if (user == null) { return null; } diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/controller/oauth/OauthController.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/controller/oauth/OauthController.java index 8d5514db9..fb8614dbd 100644 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/controller/oauth/OauthController.java +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/controller/oauth/OauthController.java @@ -68,19 +68,9 @@ private void validateUser(final User user) { } /** - * user + * 获取当前登录用户信息 * - * @return - */ - @GetMapping("user") - public DataResult user() { - return DataResult.of(getLoginUser()); - } - - /** - * user - * - * @return + * @return 当前登录用户信息 */ @GetMapping("user_a") public DataResult usera() { diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/controller/thymeleaf/ThymeleafController.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/controller/thymeleaf/ThymeleafController.java index fa01f4ac8..6941ce889 100644 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/controller/thymeleaf/ThymeleafController.java +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/controller/thymeleaf/ThymeleafController.java @@ -26,10 +26,4 @@ public class ThymeleafController { public String index() { return "index"; } - - @RequestMapping(value = "/chat.html", method={RequestMethod.GET}, produces="text/html;charset=utf-8") - public String chat(){ - - return "chat"; - } } diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/exception/EasyControllerExceptionHandler.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/exception/EasyControllerExceptionHandler.java index ab42935ee..998342104 100644 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/exception/EasyControllerExceptionHandler.java +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/exception/EasyControllerExceptionHandler.java @@ -72,7 +72,7 @@ public class EasyControllerExceptionHandler { /** * 默认转换器 */ - public static ExceptionConvertor DEFAULT_EXCEPTION_CONVERTOR = new DefaultExceptionConvertor(); + public static final ExceptionConvertor DEFAULT_EXCEPTION_CONVERTOR = new DefaultExceptionConvertor(); /** * 业务异常 diff --git a/chat2db-server/chat2db-server-start/src/main/resources/application.yml b/chat2db-server/chat2db-server-start/src/main/resources/application.yml index dac984258..10194a227 100644 --- a/chat2db-server/chat2db-server-start/src/main/resources/application.yml +++ b/chat2db-server/chat2db-server-start/src/main/resources/application.yml @@ -4,17 +4,25 @@ spring: active: dev main: allow-bean-definition-overriding: true + # 延迟初始化 Bean,显著提升启动速度 lazy-initialization: true + flyway: + enabled: false messages: basename: i18n/messages encoding: UTF-8 fallbackToSystemLocale: true jmx: enabled: false + # 排除不需要的自动配置 + autoconfigure: + exclude: + - org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration + - org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration # thymeleaf thymeleaf: prefix: classpath:/thymeleaf/ - check-template-location: true + check-template-location: false suffix: .html servlet: content-type: text/html @@ -66,3 +74,8 @@ logbook: # direct-buffers: true # max-http-post-size: 0 +thread-pool: + core-size: 2 + max-size: 5 + keep-alive: 60 + queue-capacity: 1000 \ No newline at end of file diff --git a/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages.properties b/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages.properties index c7b3939a9..1e0538093 100644 --- a/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages.properties +++ b/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages.properties @@ -30,4 +30,26 @@ sqlResult.success=Execution successful user.canNotOperateSystemAccount=System accounts cannot be operated execute.exportCsv=For more data, please click on Export CSV -settings.permissionDeniedForAiConfig=Please contact the administrator to set ApiKey in "Settings ->Custom Ai" \ No newline at end of file +settings.permissionDeniedForAiConfig=Please contact the administrator to set ApiKey in "Settings ->Custom Ai" + +# import data +dataSource.importColumnNotFound=The following columns in the file were not found in the table: {0}. Please ensure the file column headers match the table column names. +dataSource.importRowError=Error importing row {0}: {1} +dataSource.importError=Import failed: {0} +dataSource.importSqlNotSupported=SQL import is no longer supported, please use "Execute SQL Statement". +dataSource.unsupportedFileType=Unsupported file type: {0} +dataSource.executeSqlErrorDetail=Failed at SQL statement #{0} (start line: {1}): {2}; SQL: {3} +dataSource.exportTypeNotSupported=Unsupported export type: {0} +dataSource.exportError=Export failed: {0} + +# data generation +dataGeneration.batchInsertFailed=Batch insert failed: {0} + +# table relation +workspace.tableRelation.title=Table Relations +workspace.tableRelation.masterTable=Master Table +workspace.tableRelation.uniqueColumn=Unique Column +workspace.tableRelation.childTable=Child Table +workspace.tableRelation.relationColumn=Relation Column +editTable.label.sourceType=Source Type +editTable.label.comment=Comment diff --git a/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_en_US.properties b/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_en_US.properties index c377c2958..985ce485f 100644 --- a/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_en_US.properties +++ b/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_en_US.properties @@ -29,6 +29,17 @@ user.canNotOperateSystemAccount=System accounts cannot be operated execute.exportCsv=For more data, please click on Export CSV settings.permissionDeniedForAiConfig=Please contact the administrator to set ApiKey in "Settings ->Custom Ai" + +# import data +dataSource.importColumnNotFound=The following columns in the file were not found in the table: {0}. Please ensure the file column headers match the table column names. +dataSource.importRowError=Error importing row {0}: {1} +dataSource.importError=Import failed: {0} +dataSource.importSqlNotSupported=SQL import is no longer supported, please use "Execute SQL Statement". +dataSource.unsupportedFileType=Unsupported file type: {0} +dataSource.executeSqlErrorDetail=Failed at SQL statement #{0} (start line: {1}): {2}; SQL: {3} +dataSource.exportTypeNotSupported=Unsupported export type: {0} +dataSource.exportError=Export failed: {0} +dataGeneration.batchInsertFailed=Batch insert failed: {0} main.indexName=Name main.indexFieldName=Column Name main.indexType=Index Type @@ -44,3 +55,12 @@ main.fieldDecimalPlaces=Decimal Places main.fieldNote=Column Comment main.databaseText=Database: main.sheetName=Table Structure + +# table relation +workspace.tableRelation.title=Table Relations +workspace.tableRelation.masterTable=Master Table +workspace.tableRelation.uniqueColumn=Unique Column +workspace.tableRelation.childTable=Child Table +workspace.tableRelation.relationColumn=Relation Column +editTable.label.sourceType=Source Type +editTable.label.comment=Comment diff --git a/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_zh_CN.properties b/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_zh_CN.properties index 7a8befe79..867ae2252 100644 --- a/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_zh_CN.properties +++ b/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_zh_CN.properties @@ -43,3 +43,25 @@ main.fieldDecimalPlaces=小数位 main.fieldNote=备注 main.databaseText=数据库: main.sheetName=表结构 + +# import data +dataSource.importColumnNotFound=文件中以下列在表中未找到:{0}。请确保文件列名与表列名一致。 +dataSource.importRowError=导入第 {0} 行数据时出错:{1} +dataSource.importError=导入失败:{0} +dataSource.importSqlNotSupported=SQL 文件导入已不支持,请使用“执行sql语句” +dataSource.unsupportedFileType=不支持的文件类型:{0} +dataSource.executeSqlErrorDetail=执行第 {0} 条 SQL 失败(起始行:{1}):{2};SQL:{3} +dataSource.exportTypeNotSupported=不支持的导出类型:{0} +dataSource.exportError=导出失败:{0} + +# data generation +dataGeneration.batchInsertFailed=批量插入数据失败:{0} + +# table relation +workspace.tableRelation.title=表间关系 +workspace.tableRelation.masterTable=主表 +workspace.tableRelation.uniqueColumn=主键列 +workspace.tableRelation.childTable=子表 +workspace.tableRelation.relationColumn=外键列 +editTable.label.sourceType=来源类型 +editTable.label.comment=注释 diff --git a/chat2db-server/chat2db-server-start/src/main/resources/logback-spring.xml b/chat2db-server/chat2db-server-start/src/main/resources/logback-spring.xml index 34502b607..74a1e53fc 100644 --- a/chat2db-server/chat2db-server-start/src/main/resources/logback-spring.xml +++ b/chat2db-server/chat2db-server-start/src/main/resources/logback-spring.xml @@ -16,6 +16,7 @@ ${LOG_FILE} ${EASY_FILE_LOG_PATTERN} + UTF-8 ${LOG_FILE}.%d{yyyy-MM-dd}.%i.log @@ -28,7 +29,7 @@ ${EASY_CONSOLE_LOG_PATTERN} - utf8 + UTF-8 diff --git a/chat2db-server/chat2db-server-start/src/main/resources/thymeleaf/index.html b/chat2db-server/chat2db-server-start/src/main/resources/thymeleaf/index.html new file mode 100644 index 000000000..9916759a3 --- /dev/null +++ b/chat2db-server/chat2db-server-start/src/main/resources/thymeleaf/index.html @@ -0,0 +1,52 @@ + + + + + + Chat2DB + + + + + + + + + + +

+ + + + + diff --git a/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/TestApplication.java b/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/TestApplication.java index 8a74a8b12..2eaa3ca74 100644 --- a/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/TestApplication.java +++ b/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/TestApplication.java @@ -1,6 +1,6 @@ package ai.chat2db.server.start.test; -import ai.chat2db.server.start.Application; +import ai.chat2db.server.start.Chat2dbLiteApplication; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; @@ -13,12 +13,12 @@ * * @author 是仪 */ -@SpringBootTest(classes = {Application.class}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@SpringBootTest(classes = {Chat2dbLiteApplication.class}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @Slf4j @Indexed public class TestApplication { public static void main(String[] args) { - SpringApplication.run(Application.class, args); + SpringApplication.run(Chat2dbLiteApplication.class, args); } } diff --git a/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/common/BaseTest.java b/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/common/BaseTest.java index 9317d8823..e175436a3 100644 --- a/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/common/BaseTest.java +++ b/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/common/BaseTest.java @@ -1,6 +1,6 @@ package ai.chat2db.server.start.test.common; -import ai.chat2db.server.start.Application; +import ai.chat2db.server.start.Chat2dbLiteApplication; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.test.context.SpringBootTest; @@ -10,7 +10,7 @@ * * @author Jiaju Zhuang **/ -@SpringBootTest(classes = {Application.class}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@SpringBootTest(classes = {Chat2dbLiteApplication.class}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @Slf4j public abstract class BaseTest { diff --git a/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/common/BaseTest.java b/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/common/BaseTest.java index be4900657..cdcca3d3b 100644 --- a/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/common/BaseTest.java +++ b/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/common/BaseTest.java @@ -2,7 +2,7 @@ import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.sql.ConnectInfo; -import ai.chat2db.server.start.Application; +import ai.chat2db.server.start.Chat2dbLiteApplication; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.MethodOrderer; @@ -14,7 +14,7 @@ * * @author Jiaju Zhuang **/ -@SpringBootTest(classes = {Application.class}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@SpringBootTest(classes = {Chat2dbLiteApplication.class}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @Slf4j @TestMethodOrder(value = MethodOrderer.OrderAnnotation.class) public abstract class BaseTest { diff --git a/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/DatabaseOperationsTest.java b/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/DatabaseOperationsTest.java index 5ba0a44e7..0dbd338e7 100644 --- a/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/DatabaseOperationsTest.java +++ b/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/DatabaseOperationsTest.java @@ -56,10 +56,10 @@ public void queryAll() { DatabaseQueryAllParam databaseQueryAllParam = new DatabaseQueryAllParam(); databaseQueryAllParam.setDataSourceId(dataSourceId); - ListResult databaseList = databaseService.queryAll(databaseQueryAllParam); + List databaseList = databaseService.queryAll(databaseQueryAllParam); log.info("查询数据库返回:{}", JSON.toJSONString(databaseList)); - Database Database = databaseList.getData().stream() + Database Database = databaseList.stream() .filter(database -> dialectProperties.getDatabaseName().equals(database.getName())) .findFirst() .orElse(null); diff --git a/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/ExampleOperationsTest.java b/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/ExampleOperationsTest.java index c128f1ec7..40e1302e0 100644 --- a/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/ExampleOperationsTest.java +++ b/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/ExampleOperationsTest.java @@ -32,10 +32,10 @@ public class ExampleOperationsTest extends BaseTest { @Order(1) public void example() { for (DialectProperties dialectProperties : dialectPropertiesList) { - DataResult createTable = tableService.createTableExample(dialectProperties.getDbType()); + String createTable = tableService.createTableExample(dialectProperties.getDbType()); log.info("返回建表语句:{}", createTable); Assertions.assertNotNull(createTable, "查询样例失败"); - DataResult alterTable = tableService.alterTableExample(dialectProperties.getDbType()); + String alterTable = tableService.alterTableExample(dialectProperties.getDbType()); log.info("返回建修改表语句:{}", alterTable); Assertions.assertNotNull(alterTable, "查询样例失败"); } diff --git a/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/JdbcOperationsTest.java b/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/JdbcOperationsTest.java index e4a8d3a3e..0e8fb2557 100644 --- a/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/JdbcOperationsTest.java +++ b/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/JdbcOperationsTest.java @@ -90,8 +90,8 @@ public void execute() { templateQueryParam.setConsoleId(consoleId); templateQueryParam.setDataSourceId(dataSourceId); templateQueryParam.setSql(dialectProperties.getInsertSql(TABLE_NAME, DATE, NUMBER, STRING)); - ListResult executeResult = dlTemplateService.execute(templateQueryParam); - Assertions.assertTrue(executeResult.getSuccess(), "查询数据失败"); + List executeResult = dlTemplateService.execute(templateQueryParam); + Assertions.assertFalse(executeResult.isEmpty(), "查询数据失败"); // Assertions.assertEquals(1, listResult.getUpdateCount(), "查询数据失败"); // 查询 @@ -101,12 +101,12 @@ public void execute() { templateQueryParam.setSql(dialectProperties.getSelectSqlById(TABLE_NAME, 1L)); executeResult = dlTemplateService.execute(templateQueryParam); log.info("返回数据:{}", JSON.toJSONString(executeResult)); - Assertions.assertTrue(executeResult.getSuccess(), "查询数据失败"); - List
headerList = executeResult.getData().get(0).getHeaderList(); + Assertions.assertFalse(executeResult.isEmpty(), "查询数据失败"); + List
headerList = executeResult.get(0).getHeaderList(); Assertions.assertEquals(4L, headerList.size(), "查询数据失败"); Assertions.assertEquals(dialectProperties.toCase("ID"), headerList.get(0).getName(), "查询数据失败"); - List> dataList = executeResult.getData().get(0).getDataList(); + List> dataList = executeResult.get(0).getDataList(); Assertions.assertEquals(1L, dataList.size(), "查询数据失败"); List data1 = dataList.get(0); Assertions.assertEquals(Long.toString(NUMBER), data1.get(0), "查询数据失败"); @@ -123,8 +123,8 @@ public void execute() { templateQueryParam.setSql(dialectProperties.getTableNotFoundSqlById(TABLE_NAME)); executeResult = dlTemplateService.execute(templateQueryParam); log.info("异常sql执行结果:{}", JSON.toJSONString(executeResult)); - Assertions.assertFalse(executeResult.getSuccess(), "异常sql错误"); - Assertions.assertNotNull(executeResult.getErrorMessage(), "异常sql错误"); + Assertions.assertFalse(executeResult.get(0).getSuccess(), "异常sql错误"); + Assertions.assertNotNull(executeResult.get(0).getMessage(), "异常sql错误"); removeConnect(); } diff --git a/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/SQLExecutorOperationsTest.java b/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/SQLExecutorOperationsTest.java index 7fbe3ebae..071ae5b42 100644 --- a/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/SQLExecutorOperationsTest.java +++ b/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/SQLExecutorOperationsTest.java @@ -44,8 +44,7 @@ public void createAndClose() { dataSourceCreateParam.setUrl(dialectProperties.getUrl()); dataSourceCreateParam.setUser(dialectProperties.getUsername()); dataSourceCreateParam.setPassword(dialectProperties.getPassword()); - ActionResult dataSourceConnect = dataSourceService.preConnect(dataSourceCreateParam); - Assertions.assertTrue(dataSourceConnect.getSuccess(), "创建数据库连接池失败"); + dataSourceService.preConnect(dataSourceCreateParam); // Assertions.assertTrue(DataCenterUtils.JDBC_ACCESSOR_MAP.containsKey(dataSourceId), "创建数据库连接池失败"); // 关闭 @@ -67,9 +66,7 @@ public void test() { dataSourceCreateParam.setUrl(dialectProperties.getErrorUrl()); dataSourceCreateParam.setUser(dialectProperties.getUsername()); dataSourceCreateParam.setPassword(dialectProperties.getPassword()); - ActionResult dataSourceConnect = dataSourceService.preConnect(dataSourceCreateParam); - log.info("创建数据库返回:{}", JSON.toJSONString(dataSourceConnect)); - Assertions.assertFalse(dataSourceConnect.getSuccess(), "创建数据库失败错误"); + dataSourceService.preConnect(dataSourceCreateParam); } } @Test @@ -89,9 +86,8 @@ public void createDataSource(){ dataSourceCreateParam.setUrl(dialectProperties.getUrl()); dataSourceCreateParam.setUserName(dialectProperties.getUsername()); dataSourceCreateParam.setPassword(dialectProperties.getPassword()); - DataResult dataSourceConnect = dataSourceService.createWithPermission(dataSourceCreateParam); - Assertions.assertTrue(dataSourceConnect.getSuccess(), "创建数据库连接池失败"); - // Assertions.assertTrue(DataCenterUtils.JDBC_ACCESSOR_MAP.containsKey(dataSourceId), "创建数据库连接池失败"); + dataSourceService.createWithPermission(dataSourceCreateParam); + } } diff --git a/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/TableOperationsTest.java b/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/TableOperationsTest.java index 84dfc5ae5..c51ccc8ee 100644 --- a/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/TableOperationsTest.java +++ b/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/TableOperationsTest.java @@ -108,10 +108,10 @@ public void table() { showCreateTableParam.setSchemaName("public"); } - DataResult createTable = tableService.showCreateTable(showCreateTableParam); - log.info("建表语句:{}", createTable.getData()); + String createTable = tableService.showCreateTable(showCreateTableParam); + log.info("建表语句:{}", createTable); if (dialectProperties.getDbType() != "H2") { - Assertions.assertTrue(createTable.getData().contains(dialectProperties.toCase(TABLE_NAME)), + Assertions.assertTrue(createTable.contains(dialectProperties.toCase(TABLE_NAME)), "查询表结构失败"); } @@ -126,7 +126,7 @@ public void table() { List
tableList = tableService.pageQuery(tablePageQueryParam, TableSelector.builder() .columnList(Boolean.TRUE) .indexList(Boolean.TRUE) - .build()).getData(); + .build()); log.info("分析数据返回{}", JSON.toJSONString(tableList)); Assertions.assertNotEquals(0L, tableList.size(), "查询表结构失败"); Table table = tableList.get(0); @@ -193,7 +193,7 @@ public void table() { tableList = tableService.pageQuery(tablePageQueryParam, TableSelector.builder() .columnList(Boolean.TRUE) .indexList(Boolean.TRUE) - .build()).getData(); + .build()); log.info("删除表后数据返回{}", JSON.toJSONString(tableList)); Assertions.assertEquals(0L, tableList.size(), "查询表结构失败"); @@ -309,7 +309,7 @@ private void testBuildSql(DialectProperties dialectProperties, Long dataSourceId .build())) .build()); // 构建sql - List buildTableSqlList = tableService.buildSql(null, newTable).getData(); + List buildTableSqlList = tableService.buildSql(null, newTable); log.info("创建表的结构语句是:{}", JSON.toJSONString(buildTableSqlList)); for (Sql sql : buildTableSqlList) { DlExecuteParam templateQueryParam = new DlExecuteParam(); @@ -330,7 +330,7 @@ private void testBuildSql(DialectProperties dialectProperties, Long dataSourceId Table table = tableService.query(tablePageQueryParam, TableSelector.builder() .columnList(Boolean.TRUE) .indexList(Boolean.TRUE) - .build()).getData(); + .build()); log.info("分析数据返回{}", JSON.toJSONString(table)); Assertions.assertNotNull(table, "查询表结构失败"); Table oldTable = table; @@ -341,7 +341,7 @@ private void testBuildSql(DialectProperties dialectProperties, Long dataSourceId // 构建sql log.info("oldTable:{}", JSON.toJSONString(oldTable)); log.info("newTable:{}", JSON.toJSONString(newTable)); - buildTableSqlList = tableService.buildSql(oldTable, newTable).getData(); + buildTableSqlList = tableService.buildSql(oldTable, newTable); log.info("修改表结构是:{}", JSON.toJSONString(buildTableSqlList)); Assertions.assertTrue(!buildTableSqlList.isEmpty(), "构建sql失败"); // 重新去查询下 这样有2个对象 @@ -352,7 +352,7 @@ private void testBuildSql(DialectProperties dialectProperties, Long dataSourceId newTable = tableService.query(tablePageQueryParam, TableSelector.builder() .columnList(Boolean.TRUE) .indexList(Boolean.TRUE) - .build()).getData(); + .build()); // 修改字段 @@ -377,7 +377,7 @@ private void testBuildSql(DialectProperties dialectProperties, Long dataSourceId // 查询表结构变更 log.info("oldTable:{}", JSON.toJSONString(oldTable)); log.info("newTable:{}", JSON.toJSONString(newTable)); - buildTableSqlList = tableService.buildSql(oldTable, newTable).getData(); + buildTableSqlList = tableService.buildSql(oldTable, newTable); log.info("修改表结构是:{}", JSON.toJSONString(buildTableSqlList)); // 删除表结构 @@ -400,7 +400,7 @@ private void dropTable(String tableName, DialectProperties dialectProperties, Lo List
tableList = tableService.pageQuery(tablePageQueryParam, TableSelector.builder() .columnList(Boolean.TRUE) .indexList(Boolean.TRUE) - .build()).getData(); + .build()); log.info("删除表后数据返回{}", JSON.toJSONString(tableList)); Assertions.assertEquals(0L, tableList.size(), "查询表结构失败"); } @@ -414,7 +414,7 @@ private void checkTable(String tableName, DialectProperties dialectProperties, L List
tableList = tableService.pageQuery(tablePageQueryParam, TableSelector.builder() .columnList(Boolean.TRUE) .indexList(Boolean.TRUE) - .build()).getData(); + .build()); log.info("分析数据返回{}", JSON.toJSONString(tableList)); Assertions.assertEquals(1L, tableList.size(), "查询表结构失败"); diff --git a/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/redis/RedisNativeClientTest.java b/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/redis/RedisNativeClientTest.java new file mode 100644 index 000000000..b8a86f144 --- /dev/null +++ b/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/redis/RedisNativeClientTest.java @@ -0,0 +1,60 @@ +package ai.chat2db.server.test.redis; + +import ai.chat2db.plugin.redis.RedisCommandParser; +import ai.chat2db.plugin.redis.RedisConnectionProvider; +import ai.chat2db.spi.sql.ConnectInfo; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; + +class RedisNativeClientTest { + + @Test + void parseRedisUrlWithDatabase() { + ConnectInfo connectInfo = new ConnectInfo(); + connectInfo.setUrl("redis://localhost:6380/2"); + + RedisConnectionProvider.RedisConnectionInfo connectionInfo = RedisConnectionProvider.parse(connectInfo); + + Assertions.assertEquals("localhost", connectionInfo.host()); + Assertions.assertEquals(6380, connectionInfo.port()); + Assertions.assertEquals(2, connectionInfo.database()); + } + + @Test + void parseRedisUrlRejectsJdbcScheme() { + ConnectInfo connectInfo = new ConnectInfo(); + connectInfo.setUrl("jdbc:redis://localhost:6379/0"); + + Assertions.assertThrows(IllegalArgumentException.class, () -> RedisConnectionProvider.parse(connectInfo)); + } + + @Test + void parseCommandKeepsQuotedValues() { + List statements = RedisCommandParser.splitStatements("set key \"hello world\"; get key"); + + Assertions.assertEquals(2, statements.size()); + Assertions.assertEquals(List.of("set", "key", "hello world"), + RedisCommandParser.tokenize(statements.get(0))); + Assertions.assertEquals(List.of("get", "key"), RedisCommandParser.tokenize(statements.get(1))); + } + + @Test + void parseCommandPositionsKeepOriginalLineNumbers() { + List positions = RedisCommandParser.splitStatementPositions(""" + + set key "hello + world"; + get key + """); + + Assertions.assertEquals(2, positions.size()); + Assertions.assertEquals("set key \"hello\nworld\"", positions.get(0).statement()); + Assertions.assertEquals(2, positions.get(0).startLine()); + Assertions.assertEquals(3, positions.get(0).endLine()); + Assertions.assertEquals("get key", positions.get(1).statement()); + Assertions.assertEquals(4, positions.get(1).startLine()); + Assertions.assertEquals(4, positions.get(1).endLine()); + } +} diff --git a/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/temp/SqlTeset.java b/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/temp/SqlTeset.java index 86c5292eb..7768d1d19 100644 --- a/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/temp/SqlTeset.java +++ b/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/temp/SqlTeset.java @@ -48,15 +48,15 @@ public void test() { dataSourceCreateParam.setUrl(mysqlDialectProperties.getUrl()); dataSourceCreateParam.setUser(mysqlDialectProperties.getUsername()); dataSourceCreateParam.setPassword(mysqlDialectProperties.getPassword()); - ActionResult actionResult = dataSourceService.preConnect(dataSourceCreateParam); + dataSourceService.preConnect(dataSourceCreateParam); - DataResult createTable = tableService.createTableExample("MYSQL"); - log.info("sql1:{}", createTable.getData()); + String createTable = tableService.createTableExample("MYSQL"); + log.info("sql1:{}", createTable); SqlAnalyseParam sqlAnalyseParam = new SqlAnalyseParam(); sqlAnalyseParam.setDataSourceId(1L); - sqlAnalyseParam.setSql(createTable.getData()); + sqlAnalyseParam.setSql(createTable); List sqlList = new ArrayList<>(); - sqlList.add(Sql.builder().sql(createTable.getData()).build()); + sqlList.add(Sql.builder().sql(createTable).build()); // 创建控制台 ConsoleConnectParam consoleCreateParam = new ConsoleConnectParam(); @@ -70,7 +70,7 @@ public void test() { templateQueryParam.setConsoleId(1L); templateQueryParam.setDataSourceId(1L); templateQueryParam.setSql("drop table test;"); - ListResult executeResult = dlTemplateService.execute(templateQueryParam); + List executeResult = dlTemplateService.execute(templateQueryParam); log.info("result:{}", JSON.toJSONString(executeResult)); // 创建表结构 diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/enums/DataSourceTypeEnum.java b/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/enums/DataSourceTypeEnum.java index 7a9cbcc64..b2787dcd2 100644 --- a/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/enums/DataSourceTypeEnum.java +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/enums/DataSourceTypeEnum.java @@ -26,7 +26,10 @@ public enum DataSourceTypeEnum implements BaseEnum { */ MONGODB("mongo数据库连接"), - ; + /** + * PHOENIX数据库连接 + */ + PHOENIX("PHOENIX数据库连接"); final String description; diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/ServicePage.java b/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/ServicePage.java new file mode 100644 index 000000000..09820ed4a --- /dev/null +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/ServicePage.java @@ -0,0 +1,91 @@ +package ai.chat2db.server.tools.base.wrapper; + +import java.io.Serializable; +import java.util.Collections; +import java.util.List; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * Service layer pagination wrapper + * Contains only core pagination fields without Result semantics + * + * @param data type + */ +@Data +@SuperBuilder +@NoArgsConstructor +public class ServicePage implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * Data list + */ + private List data; + + /** + * Total count + */ + private Long total; + + /** + * Page number + */ + private Integer pageNo; + + /** + * Page size + */ + private Integer pageSize; + + /** + * Last document ID for cursor-based pagination + */ + private Integer lastDocId; + + public ServicePage(List data, Long total, Integer pageNo, Integer pageSize) { + this.data = data; + this.total = total; + this.pageNo = pageNo; + this.pageSize = pageSize; + } + + public ServicePage(List data, Long total, Integer pageNo, Integer pageSize, Integer lastDocId) { + this.data = data; + this.total = total; + this.pageNo = pageNo; + this.pageSize = pageSize; + this.lastDocId = lastDocId; + } + + /** + * Create a ServicePage from data, total and query params + */ + public static ServicePage of(List data, Long total, Integer pageNo, Integer pageSize) { + return new ServicePage<>(data, total, pageNo, pageSize); + } + + /** + * Create a ServicePage with lastDocId support + */ + public static ServicePage of(List data, Long total, Integer pageNo, Integer pageSize, Integer lastDocId) { + return new ServicePage<>(data, total, pageNo, pageSize, lastDocId); + } + + /** + * Create an empty ServicePage + */ + public static ServicePage empty(Integer pageNo, Integer pageSize) { + return of(Collections.emptyList(), 0L, pageNo, pageSize); + } + + /** + * Check if data is not empty + */ + public boolean isNotEmpty() { + return data != null && !data.isEmpty(); + } +} diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/param/PageQueryParam.java b/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/param/PageQueryParam.java index 762af5e97..e6bf07cf3 100644 --- a/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/param/PageQueryParam.java +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/param/PageQueryParam.java @@ -53,6 +53,12 @@ public class PageQueryParam implements Serializable { */ private List orderByList; + + /** + * 最后id + */ + private Integer lastDocId; + public PageQueryParam() { this.pageNo = 1; this.pageSize = 100; diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/request/PageQueryRequest.java b/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/request/PageQueryRequest.java index 7bf1ccbfb..b89191cc8 100644 --- a/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/request/PageQueryRequest.java +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/request/PageQueryRequest.java @@ -40,6 +40,12 @@ public class PageQueryRequest implements Serializable { message = "分页大小必须在1-" + EasyToolsConstant.MAX_PAGE_SIZE + "之间") private Integer pageSize; + + /** + * 最后id + */ + private Integer lastDocId; + public PageQueryRequest() { this.pageNo = 1; this.pageSize = 10; diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/result/PageResult.java b/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/result/PageResult.java index 1e377cb33..d7663a42b 100644 --- a/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/result/PageResult.java +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/result/PageResult.java @@ -75,6 +75,11 @@ public class PageResult implements Serializable, Result> { */ private String solutionLink; + /** + * 最后id + */ + private Integer lastDocId; + public PageResult() { this.pageNo = 1; this.pageSize = 10; @@ -106,6 +111,21 @@ private PageResult(List data, Long total, Integer pageNo, Integer pageSize) { } } + private PageResult(List data, Long total, Integer pageNo, Integer pageSize, Integer lastDocId) { + this(); + this.data = data; + this.total = total; + if (pageNo != null) { + this.pageNo = pageNo; + } + if (pageSize != null) { + this.pageSize = pageSize; + } + if (lastDocId != null) { + this.lastDocId = lastDocId; + } + } + /** * 构建分页返回对象 * @@ -144,7 +164,7 @@ public static PageResult of(List data, Long total, Integer pageNo, Int * @return 分页返回对象 */ public static PageResult of(List data, Long total, PageQueryParam param) { - return new PageResult<>(data, total, param.getPageNo(), param.getPageSize()); + return new PageResult<>(data, total, param.getPageNo(), param.getPageSize(), param.getLastDocId()); } /** @@ -157,7 +177,7 @@ public static PageResult of(List data, Long total, PageQueryParam para * @return 分页返回对象 */ public static PageResult of(List data, PageQueryParam param) { - return new PageResult<>(data, 0L, param.getPageNo(), param.getPageSize()); + return new PageResult<>(data, 0L, param.getPageNo(), param.getPageSize(), param.getLastDocId()); } /** @@ -193,7 +213,7 @@ public static PageResult empty(Integer pageNo, Integer pageSize) { public Boolean calculateHasNextPage() { // 存在分页大小 根据分页来计算 if (total > 0) { - return (long)pageSize * pageNo <= total; + return (long) pageSize * pageNo <= total; } // 没有数据 肯定没有下一页 if (data == null || data.isEmpty()) { @@ -205,16 +225,10 @@ public Boolean calculateHasNextPage() { /** * 判断是否还有下一页 - * 根据分页大小来计算 防止total为空 + * 根据分页大小来计算 防止 total 为空 * * @return 是否还有下一页 - * @deprecated 使用 {@link #getHasNextPage()} ()} */ - @Deprecated - public boolean hasNextPage() { - return getHasNextPage(); - } - public Boolean getHasNextPage() { if (hasNextPage == null) { hasNextPage = calculateHasNextPage(); @@ -255,7 +269,7 @@ public static PageResult error(String errorCode, String errorMessage) { */ public static boolean hasData(PageResult pageResult) { return pageResult != null && pageResult.getSuccess() && pageResult.getData() != null && !pageResult.getData() - .isEmpty(); + .isEmpty(); } /** @@ -267,7 +281,7 @@ public static boolean hasData(PageResult pageResult) { */ public PageResult map(Function mapper) { List returnData = hasData(this) ? getData().stream().map(mapper).collect(Collectors.toList()) - : Collections.emptyList(); + : Collections.emptyList(); PageResult pageResult = new PageResult<>(); pageResult.setSuccess(getSuccess()); pageResult.setErrorCode(getErrorCode()); @@ -289,7 +303,7 @@ public PageResult map(Function mapper) { */ public ListResult mapToList(Function mapper) { List returnData = hasData(this) ? getData().stream().map(mapper).collect(Collectors.toList()) - : Collections.emptyList(); + : Collections.emptyList(); ListResult result = new ListResult<>(); result.setSuccess(getSuccess()); result.setErrorCode(getErrorCode()); @@ -310,7 +324,7 @@ public ListResult mapToList(Function mapper) { */ public WebPageResult mapToWeb(Function mapper) { List returnData = hasData(this) ? getData().stream().map(mapper).collect(Collectors.toList()) - : Collections.emptyList(); + : Collections.emptyList(); WebPageResult pageResult = new WebPageResult<>(); pageResult.setSuccess(getSuccess()); pageResult.setErrorCode(getErrorCode()); diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/result/web/WebPageResult.java b/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/result/web/WebPageResult.java index eef879b9f..c44297b3a 100644 --- a/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/result/web/WebPageResult.java +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/result/web/WebPageResult.java @@ -60,6 +60,7 @@ public class WebPageResult implements Serializable, Result> { */ private String solutionLink; + public WebPageResult() { this.success = Boolean.TRUE; this.data = new Page<>(); @@ -75,6 +76,11 @@ private WebPageResult(List data, Long total, Integer pageNo, Integer pageSize this.data = new Page<>(data, total, pageNo, pageSize); } + private WebPageResult(List data, Integer lastDocId) { + this.success = Boolean.TRUE; + this.data = new Page<>(data, lastDocId); + } + /** * 构建分页返回对象 * @@ -103,6 +109,10 @@ public static WebPageResult of(List data, Long total, Integer pageNo, return new WebPageResult<>(data, total, pageNo, pageSize); } + public static WebPageResult of(List data, Integer lastDocId) { + return new WebPageResult<>(data, lastDocId); + } + /** * 构建分页返回对象 * @@ -140,18 +150,13 @@ public static WebPageResult empty(Integer pageNo, Integer pageSize) { return of(Collections.emptyList(), 0L, pageNo, pageSize); } + /** * 判断是否还有下一页 - * 根据分页大小来计算 防止total为空 + * 根据分页大小来计算 防止 total 为空 * * @return 是否还有下一页 - * @deprecated 使用 {@link #getHasNextPage()} ()} */ - @Deprecated - public boolean hasNextPage() { - return getHasNextPage(); - } - public Boolean getHasNextPage() { if (data == null) { return Boolean.FALSE; @@ -183,7 +188,7 @@ public static WebPageResult error(String errorCode, String errorMessage) */ public static boolean hasData(WebPageResult pageResult) { return pageResult != null && pageResult.getSuccess() && pageResult.getData() != null - && pageResult.getData().getData() != null && !pageResult.getData().getData().isEmpty(); + && pageResult.getData().getData() != null && !pageResult.getData().getData().isEmpty(); } /** @@ -195,7 +200,7 @@ public static boolean hasData(WebPageResult pageResult) { */ public WebPageResult map(Function mapper) { List returnData = hasData(this) ? getData().getData().stream().map(mapper).collect(Collectors.toList()) - : Collections.emptyList(); + : Collections.emptyList(); WebPageResult pageResult = new WebPageResult<>(); pageResult.setSuccess(getSuccess()); pageResult.setErrorCode(getErrorCode()); @@ -267,7 +272,8 @@ public String solutionLink() { * @param */ @Data - public static class Page { + public static class Page implements Serializable { + private static final long serialVersionUID = 1L; /** * 数据信息 */ @@ -289,6 +295,12 @@ public static class Page { */ private Boolean hasNextPage; + + /** + * 最后id + */ + private Integer lastDocId; + public Page() { this.pageNo = 1; this.pageSize = 10; @@ -319,6 +331,16 @@ private Page(List data, Long total, Integer pageNo, Integer pageSize) { } } + private Page(List data, Integer lastDocId) { + this(); + this.data = data; + if (lastDocId == null) { + this.hasNextPage = false; + } else { + this.lastDocId = lastDocId; + } + } + public Boolean getHasNextPage() { if (hasNextPage == null) { hasNextPage = calculateHasNextPage(); @@ -335,7 +357,7 @@ public Boolean getHasNextPage() { public Boolean calculateHasNextPage() { // 存在分页大小 根据分页来计算 if (total > 0) { - return (long)pageSize * pageNo <= total; + return (long) pageSize * pageNo <= total; } // 没有数据 肯定没有下一页 if (data == null || data.isEmpty()) { diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/ConfigUtils.java b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/ConfigUtils.java index 8aa2d62bf..2ca334661 100644 --- a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/ConfigUtils.java +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/ConfigUtils.java @@ -17,7 +17,7 @@ */ @Slf4j public class ConfigUtils { - public static String CONFIG_BASE_PATH = System.getProperty("user.home") + File.separator + ".chat2db"; + public static final String CONFIG_BASE_PATH = System.getProperty("user.home") + File.separator + ".chat2db"; public static final String APP_PATH = getAppPath(); diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/EasyBooleanUtils.java b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/EasyBooleanUtils.java index 18d997d2b..fc3e074ad 100644 --- a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/EasyBooleanUtils.java +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/EasyBooleanUtils.java @@ -16,7 +16,7 @@ public class EasyBooleanUtils { * @return */ public static boolean equals(Boolean b1, Boolean b2, Boolean defaultValue) { - if (b1 == b2) { + if (b1 != null && b1.equals(b2)) { return true; } if (b1 == null) { @@ -25,7 +25,7 @@ public static boolean equals(Boolean b1, Boolean b2, Boolean defaultValue) { if (b2 == null) { b2 = defaultValue; } - return b1 == b2; + return b1 != null && b1.equals(b2); } } diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/EasyIntegerUtils.java b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/EasyIntegerUtils.java index 1e64099df..b60d65a84 100644 --- a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/EasyIntegerUtils.java +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/EasyIntegerUtils.java @@ -11,7 +11,7 @@ public class EasyIntegerUtils { * @return */ public static boolean equals(Integer b1, Integer b2, Integer defaultValue) { - if (b1 == b2) { + if (b1 != null && b1.equals(b2)) { return true; } if (b1 == null) { @@ -20,6 +20,6 @@ public static boolean equals(Integer b1, Integer b2, Integer defaultValue) { if (b2 == null) { b2 = defaultValue; } - return b1 == b2; + return b1 != null && b1.equals(b2); } } diff --git a/chat2db-server/chat2db-server-web-start/pom.xml b/chat2db-server/chat2db-server-web-start/pom.xml index f3bd4c7d8..f3616f76f 100644 --- a/chat2db-server/chat2db-server-web-start/pom.xml +++ b/chat2db-server/chat2db-server-web-start/pom.xml @@ -114,6 +114,10 @@ org.zalando logbook-spring-boot-starter + + junit + junit + chat2db-server-web-start diff --git a/chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/Application.java b/chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/Chat2dbWebApplication.java similarity index 74% rename from chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/Application.java rename to chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/Chat2dbWebApplication.java index 3d165e0c0..6ec961cfa 100644 --- a/chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/Application.java +++ b/chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/Chat2dbWebApplication.java @@ -11,6 +11,8 @@ import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration; +import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.ComponentScan; import org.springframework.scheduling.annotation.EnableAsync; @@ -18,18 +20,42 @@ import org.springframework.stereotype.Indexed; /** - * 启动类 + * Chat2DB 主启动类(完整功能版) + * + *

包含完整功能: + *

    + *
  • 用户登录鉴权(sa-token + JWT)
  • + *
  • 管理后台 API(admin-api)
  • + *
  • 桌面模式支持(DESKTOP mode)
  • + *
  • 系统唯一 UUID 配置
  • + *
  • Flyway 数据库版本管理
  • + *
+ * + *

JAR 文件:chat2db-server-web-start.jar + * + *

启动命令: + *

+ * java -jar chat2db-server-web-start.jar
+ * 
+ * + *

桌面模式启动: + *

+ * java -Dchat2db.mode=DESKTOP -jar chat2db-server-web-start.jar
+ * 
* * @author Jiaju Zhuang */ -@SpringBootApplication -@ComponentScan(value = {"ai.chat2db.server"}) +@SpringBootApplication(exclude = { + MailSenderAutoConfiguration.class, + QuartzAutoConfiguration.class +}) +@ComponentScan(value = {"ai.chat2db.server"}, lazyInit = true) @Indexed @EnableCaching @EnableScheduling @EnableAsync @Slf4j -public class Application { +public class Chat2dbWebApplication { public static void main(String[] args) { long s1 = System.currentTimeMillis(); @@ -67,6 +93,6 @@ public static void main(String[] args) { args = ArrayUtils.add(args, "--sa-token.jwt-secret-key=" + configJson.getJwtSecretKey()); } System.out.println("启动耗时:" + (System.currentTimeMillis() - s1) + "ms"); - SpringApplication.run(Application.class, args); + SpringApplication.run(Chat2dbWebApplication.class, args); } } diff --git a/chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/config/config/Chat2dbWebMvcConfigurer.java b/chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/config/config/Chat2dbWebMvcConfigurer.java index 519fa42fd..19a8f8ce7 100644 --- a/chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/config/config/Chat2dbWebMvcConfigurer.java +++ b/chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/config/config/Chat2dbWebMvcConfigurer.java @@ -88,7 +88,7 @@ public boolean preHandle(@NotNull HttpServletRequest request, @NotNull HttpServl } Long finalUserId = userId; LoginUser loginUser = MemoryCacheManage.computeIfAbsent(CacheKey.getLoginUserKey(userId), () -> { - User user = userService.query(finalUserId).getData(); + User user = userService.query(finalUserId); if (user == null) { return null; } diff --git a/chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/controller/oauth/OauthController.java b/chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/controller/oauth/OauthController.java index 6e0af757a..029b51df2 100644 --- a/chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/controller/oauth/OauthController.java +++ b/chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/controller/oauth/OauthController.java @@ -3,6 +3,7 @@ import ai.chat2db.server.domain.api.enums.RoleCodeEnum; import ai.chat2db.server.domain.api.enums.ValidStatusEnum; import ai.chat2db.server.domain.api.model.User; +import ai.chat2db.server.domain.api.service.LoginAttemptService; import ai.chat2db.server.domain.api.service.UserService; import ai.chat2db.server.web.start.controller.oauth.request.LoginRequest; import ai.chat2db.server.tools.base.excption.BusinessException; @@ -11,12 +12,12 @@ import ai.chat2db.server.tools.common.model.LoginUser; import ai.chat2db.server.tools.common.util.ContextUtils; import cn.dev33.satoken.context.SaHolder; +import cn.dev33.satoken.context.model.SaRequest; import cn.dev33.satoken.stp.StpUtil; import cn.dev33.satoken.util.SaTokenConsts; import cn.hutool.crypto.digest.DigestUtil; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.NotNull; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -35,6 +36,9 @@ public class OauthController { @Resource private UserService userService; + @Resource + private LoginAttemptService loginAttemptService; + /** * 用户名密码登录 * @@ -43,25 +47,42 @@ public class OauthController { */ @PostMapping("login_a") public DataResult login(@Validated @RequestBody LoginRequest request) { - // 查询用户 - User user = userService.query(request.getUserName()).getData(); - this.validateUser(user); - // Successfully logged in without modifying the administrator password - if (this.validateAdmin(user)) { - return DataResult.of(doLogin(user)); - } + // 客户端指纹校验 + String clientFingerprint = getClientFingerprint(); + loginAttemptService.validateAttempt(clientFingerprint); + // 查询用户 + User user = userService.query(request.getUserName()); + this.validateUser(user); if (!DigestUtil.bcryptCheck(request.getPassword(), user.getPassword())) { + loginAttemptService.recordFailedAttempt(clientFingerprint); throw new BusinessException("oauth.passwordIncorrect"); } - + loginAttemptService.clearAttempts(clientFingerprint); return DataResult.of(doLogin(user)); } - private boolean validateAdmin(final @NotNull User user) { - return RoleCodeEnum.ADMIN.getDefaultUserId().equals(user.getId()) && RoleCodeEnum.ADMIN.getPassword().equals( - user.getPassword()); + private String getClientFingerprint() { + // 从Sa-Token上下文中获取请求对象 + SaRequest request = SaHolder.getRequest(); + + // 获取客户端IP(考虑代理情况) + String ip = Objects.requireNonNullElse(request.getHeader("X-Forwarded-For"), + request.getHeader("X-Real-IP")) + .split(",")[0].trim(); + // 获取设备指纹特征 + String userAgent = Objects.requireNonNullElse(request.getHeader("User-Agent"), ""); + String acceptLanguage = Objects.requireNonNullElse(request.getHeader("Accept-Language"), ""); + String secChUa = Objects.requireNonNullElse(request.getHeader("Sec-CH-UA"), ""); + // 组合指纹要素(可根据安全需求调整) + String fingerprintRaw = String.join("|", + ip, + DigestUtil.md5Hex(userAgent), + DigestUtil.sha256Hex(acceptLanguage), + DigestUtil.sha1Hex(secChUa)); + // 最终指纹生成(双层哈希增加逆向难度) + return DigestUtil.sha256Hex(DigestUtil.md5Hex(fingerprintRaw)); } private void validateUser(final User user) { @@ -93,19 +114,9 @@ public ActionResult logout() { } /** - * user - * - * @return - */ - @GetMapping("user") - public DataResult user() { - return DataResult.of(getLoginUser()); - } - - /** - * user + * 获取当前登录用户信息 * - * @return + * @return 当前登录用户信息 */ @GetMapping("user_a") public DataResult usera() { diff --git a/chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/controller/thymeleaf/ThymeleafController.java b/chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/controller/thymeleaf/ThymeleafController.java index 2da27b52d..fa4d5cb1d 100644 --- a/chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/controller/thymeleaf/ThymeleafController.java +++ b/chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/controller/thymeleaf/ThymeleafController.java @@ -27,9 +27,4 @@ public String index() { return "index"; } - @RequestMapping(value = "/chat.html", method={RequestMethod.GET}, produces="text/html;charset=utf-8") - public String chat(){ - - return "chat"; - } } diff --git a/chat2db-server/chat2db-server-web-start/src/main/resources/application.yml b/chat2db-server/chat2db-server-web-start/src/main/resources/application.yml index e90d6af38..986934df4 100644 --- a/chat2db-server/chat2db-server-web-start/src/main/resources/application.yml +++ b/chat2db-server/chat2db-server-web-start/src/main/resources/application.yml @@ -4,16 +4,23 @@ spring: active: dev main: allow-bean-definition-overriding: true + # 延迟初始化 Bean,显著提升启动速度 + lazy-initialization: true messages: basename: i18n/messages encoding: UTF-8 fallbackToSystemLocale: true jmx: enabled: false + # 排除不需要的自动配置 + autoconfigure: + exclude: + - org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration + - org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration # thymeleaf thymeleaf: prefix: classpath:/thymeleaf/ - check-template-location: true + check-template-location: false suffix: .html servlet: content-type: text/html @@ -82,4 +89,9 @@ forest: connect-timeout: 30000 log-response-content: true read-timeout: 30000 - timeout: 30000 \ No newline at end of file + timeout: 30000 +thread-pool: + core-size: 2 + max-size: 5 + keep-alive: 60 + queue-capacity: 1000 \ No newline at end of file diff --git a/chat2db-server/chat2db-server-web-start/src/main/resources/i18n/messages.properties b/chat2db-server/chat2db-server-web-start/src/main/resources/i18n/messages.properties index c7b3939a9..a78b30149 100644 --- a/chat2db-server/chat2db-server-web-start/src/main/resources/i18n/messages.properties +++ b/chat2db-server/chat2db-server-web-start/src/main/resources/i18n/messages.properties @@ -30,4 +30,13 @@ sqlResult.success=Execution successful user.canNotOperateSystemAccount=System accounts cannot be operated execute.exportCsv=For more data, please click on Export CSV -settings.permissionDeniedForAiConfig=Please contact the administrator to set ApiKey in "Settings ->Custom Ai" \ No newline at end of file +settings.permissionDeniedForAiConfig=Please contact the administrator to set ApiKey in "Settings ->Custom Ai" + +# table relation +workspace.tableRelation.title=Table Relations +workspace.tableRelation.masterTable=Master Table +workspace.tableRelation.uniqueColumn=Unique Column +workspace.tableRelation.childTable=Child Table +workspace.tableRelation.relationColumn=Relation Column +editTable.label.sourceType=Source Type +editTable.label.comment=Comment \ No newline at end of file diff --git a/chat2db-server/chat2db-server-web-start/src/main/resources/i18n/messages_en_US.properties b/chat2db-server/chat2db-server-web-start/src/main/resources/i18n/messages_en_US.properties index c377c2958..68b99ed56 100644 --- a/chat2db-server/chat2db-server-web-start/src/main/resources/i18n/messages_en_US.properties +++ b/chat2db-server/chat2db-server-web-start/src/main/resources/i18n/messages_en_US.properties @@ -44,3 +44,12 @@ main.fieldDecimalPlaces=Decimal Places main.fieldNote=Column Comment main.databaseText=Database: main.sheetName=Table Structure + +# table relation +workspace.tableRelation.title=Table Relations +workspace.tableRelation.masterTable=Master Table +workspace.tableRelation.uniqueColumn=Unique Column +workspace.tableRelation.childTable=Child Table +workspace.tableRelation.relationColumn=Relation Column +editTable.label.sourceType=Source Type +editTable.label.comment=Comment diff --git a/chat2db-server/chat2db-server-web-start/src/main/resources/i18n/messages_zh_CN.properties b/chat2db-server/chat2db-server-web-start/src/main/resources/i18n/messages_zh_CN.properties index 7a8befe79..332a98603 100644 --- a/chat2db-server/chat2db-server-web-start/src/main/resources/i18n/messages_zh_CN.properties +++ b/chat2db-server/chat2db-server-web-start/src/main/resources/i18n/messages_zh_CN.properties @@ -43,3 +43,12 @@ main.fieldDecimalPlaces=小数位 main.fieldNote=备注 main.databaseText=数据库: main.sheetName=表结构 + +# table relation +workspace.tableRelation.title=表间关系 +workspace.tableRelation.masterTable=主表 +workspace.tableRelation.uniqueColumn=主键列 +workspace.tableRelation.childTable=子表 +workspace.tableRelation.relationColumn=外键列 +editTable.label.sourceType=来源类型 +editTable.label.comment=注释 diff --git a/chat2db-server/chat2db-server-web-start/src/main/resources/static/front/1065.fe92f3dd.async.js b/chat2db-server/chat2db-server-web-start/src/main/resources/static/front/1065.fe92f3dd.async.js new file mode 100644 index 000000000..50090e973 --- /dev/null +++ b/chat2db-server/chat2db-server-web-start/src/main/resources/static/front/1065.fe92f3dd.async.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkchat2db=self.webpackChunkchat2db||[]).push([[1065],{71065:function(t,e,n){n.r(e),n.d(e,{conf:function(){return s},language:function(){return i}});var s={brackets:[["{","}"],["[","]"],["(",")"]],autoClosingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:"<",close:">",notIn:["string"]}],surroundingPairs:[{open:"(",close:")"},{open:"[",close:"]"},{open:"`",close:"`"}],folding:{markers:{start:new RegExp("^\\s*"),end:new RegExp("^\\s*")}}},i={defaultToken:"",tokenPostfix:".rst",control:/[\\`*_\[\]{}()#+\-\.!]/,escapes:/\\(?:@control)/,empty:["area","base","basefont","br","col","frame","hr","img","input","isindex","link","meta","param"],alphanumerics:/[A-Za-z0-9]/,simpleRefNameWithoutBq:/(?:@alphanumerics[-_+:.]*@alphanumerics)+|(?:@alphanumerics+)/,simpleRefName:/(?:`@phrase`|@simpleRefNameWithoutBq)/,phrase:/@simpleRefNameWithoutBq(?:\s@simpleRefNameWithoutBq)*/,citationName:/[A-Za-z][A-Za-z0-9-_.]*/,blockLiteralStart:/(?:[!"#$%&'()*+,-./:;<=>?@\[\]^_`{|}~]|[\s])/,precedingChars:/(?:[ -:/'"<([{])/,followingChars:/(?:[ -.,:;!?/'")\]}>]|$)/,punctuation:/(=|-|~|`|#|"|\^|\+|\*|:|\.|'|_|\+)/,tokenizer:{root:[[/^(@punctuation{3,}$){1,1}?/,"keyword"],[/^\s*([\*\-+‣•]|[a-zA-Z0-9]+\.|\([a-zA-Z0-9]+\)|[a-zA-Z0-9]+\))\s/,"keyword"],[/([ ]::)\s*$/,"keyword","@blankLineOfLiteralBlocks"],[/(::)\s*$/,"keyword","@blankLineOfLiteralBlocks"],{include:"@tables"},{include:"@explicitMarkupBlocks"},{include:"@inlineMarkup"}],explicitMarkupBlocks:[{include:"@citations"},{include:"@footnotes"},[/^(\.\.\s)(@simpleRefName)(::\s)(.*)$/,[{token:"",next:"subsequentLines"},"keyword","",""]],[/^(\.\.)(\s+)(_)(@simpleRefName)(:)(\s+)(.*)/,[{token:"",next:"hyperlinks"},"","","string.link","","","string.link"]],[/^((?:(?:\.\.)(?:\s+))?)(__)(:)(\s+)(.*)/,[{token:"",next:"subsequentLines"},"","","","string.link"]],[/^(__\s+)(.+)/,["","string.link"]],[/^(\.\.)( \|)([^| ]+[^|]*[^| ]*)(\| )(@simpleRefName)(:: .*)/,[{token:"",next:"subsequentLines"},"","string.link","","keyword",""],"@rawBlocks"],[/(\|)([^| ]+[^|]*[^| ]*)(\|_{0,2})/,["","string.link",""]],[/^(\.\.)([ ].*)$/,[{token:"",next:"@comments"},"comment"]]],inlineMarkup:[{include:"@citationsReference"},{include:"@footnotesReference"},[/(@simpleRefName)(_{1,2})/,["string.link",""]],[/(`)([^<`]+\s+)(<)(.*)(>)(`)(_)/,["","string.link","","string.link","","",""]],[/\*\*([^\\*]|\*(?!\*))+\*\*/,"strong"],[/\*[^*]+\*/,"emphasis"],[/(``)((?:[^`]|\`(?!`))+)(``)/,["","keyword",""]],[/(__\s+)(.+)/,["","keyword"]],[/(:)((?:@simpleRefNameWithoutBq)?)(:`)([^`]+)(`)/,["","keyword","","",""]],[/(`)([^`]+)(`:)((?:@simpleRefNameWithoutBq)?)(:)/,["","","","keyword",""]],[/(`)([^`]+)(`)/,""],[/(_`)(@phrase)(`)/,["","string.link",""]]],citations:[[/^(\.\.\s+\[)((?:@citationName))(\]\s+)(.*)/,[{token:"",next:"@subsequentLines"},"string.link","",""]]],citationsReference:[[/(\[)(@citationName)(\]_)/,["","string.link",""]]],footnotes:[[/^(\.\.\s+\[)((?:[0-9]+))(\]\s+.*)/,[{token:"",next:"@subsequentLines"},"string.link",""]],[/^(\.\.\s+\[)((?:#@simpleRefName?))(\]\s+)(.*)/,[{token:"",next:"@subsequentLines"},"string.link","",""]],[/^(\.\.\s+\[)((?:\*))(\]\s+)(.*)/,[{token:"",next:"@subsequentLines"},"string.link","",""]]],footnotesReference:[[/(\[)([0-9]+)(\])(_)/,["","string.link","",""]],[/(\[)(#@simpleRefName?)(\])(_)/,["","string.link","",""]],[/(\[)(\*)(\])(_)/,["","string.link","",""]]],blankLineOfLiteralBlocks:[[/^$/,"","@subsequentLinesOfLiteralBlocks"],[/^.*$/,"","@pop"]],subsequentLinesOfLiteralBlocks:[[/(@blockLiteralStart+)(.*)/,["keyword",""]],[/^(?!blockLiteralStart)/,"","@popall"]],subsequentLines:[[/^[\s]+.*/,""],[/^(?!\s)/,"","@pop"]],hyperlinks:[[/^[\s]+.*/,"string.link"],[/^(?!\s)/,"","@pop"]],comments:[[/^[\s]+.*/,"comment"],[/^(?!\s)/,"","@pop"]],tables:[[/\+-[+-]+/,"keyword"],[/\+=[+=]+/,"keyword"]]}}}}]); diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/common/CommonAdminController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/common/CommonAdminController.java index c0d5d521c..b28d50bcc 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/common/CommonAdminController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/common/CommonAdminController.java @@ -54,14 +54,14 @@ public class CommonAdminController { public ListResult teamUserList(@Valid CommonQueryRequest request) { UserPageQueryParam userPageQueryParam = commonAdminConverter.request2paramUser(request); List result = Lists.newArrayList(); - result.addAll(userService.pageQuery(userPageQueryParam, null) - .mapToList(commonAdminConverter::dto2voTeamUser) - .getData()); + result.addAll(userService.pageQuery(userPageQueryParam, null).getData().stream() + .map(commonAdminConverter::dto2voTeamUser) + .toList()); TeamPageQueryParam teamPageQueryParam = commonAdminConverter.request2paramTeam(request); - result.addAll(teamService.pageQuery(teamPageQueryParam, null) - .mapToList(commonAdminConverter::dto2voTeamUser) - .getData()); + result.addAll(teamService.pageQuery(teamPageQueryParam, null).getData().stream() + .map(commonAdminConverter::dto2voTeamUser) + .toList()); return ListResult.of(result); } @@ -74,8 +74,9 @@ public ListResult teamUserList(@Valid CommonQueryRequest request */ @GetMapping("/user/list") public ListResult userList(@Valid CommonQueryRequest request) { - return userService.pageQuery(commonAdminConverter.request2paramUser(request), null) - .mapToList(commonAdminConverter::dto2voUser); + return ListResult.of(userService.pageQuery(commonAdminConverter.request2paramUser(request), null).getData().stream() + .map(commonAdminConverter::dto2voUser) + .toList()); } /** @@ -87,8 +88,9 @@ public ListResult userList(@Valid CommonQueryRequest request) { */ @GetMapping("/team/list") public ListResult teamList(@Valid CommonQueryRequest request) { - return teamService.pageQuery(commonAdminConverter.request2paramTeam(request), null) - .mapToList(commonAdminConverter::dto2voTeam); + return ListResult.of(teamService.pageQuery(commonAdminConverter.request2paramTeam(request), null).getData().stream() + .map(commonAdminConverter::dto2voTeam) + .toList()); } /** @@ -100,8 +102,9 @@ public ListResult teamList(@Valid CommonQueryRequest request) { */ @GetMapping("/data_source/list") public ListResult dataSourceList(@Valid CommonQueryRequest request) { - return dataSourceService.queryPageWithPermission(commonAdminConverter.request2paramDataSource(request), - DATA_SOURCE_SELECTOR) - .mapToList(commonAdminConverter::dto2voDataSource); + return ListResult.of(dataSourceService.queryPageWithPermission(commonAdminConverter.request2paramDataSource(request), + DATA_SOURCE_SELECTOR).getData().stream() + .map(commonAdminConverter::dto2voDataSource) + .toList()); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceAccessAdminController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceAccessAdminController.java index 1e9c66690..ea42f8f14 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceAccessAdminController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceAccessAdminController.java @@ -48,9 +48,11 @@ public class DataSourceAccessAdminController { */ @GetMapping("/page") public WebPageResult page(@Valid DataSourceAccessPageQueryRequest request) { - return dataSourceAccessService.comprehensivePageQuery(dataSourceAccessAdminConverter.request2param(request), - DATA_SOURCE_ACCESS_SELECTOR) - .mapToWeb(dataSourceAccessAdminConverter::dto2vo); + var servicePage = dataSourceAccessService.comprehensivePageQuery(dataSourceAccessAdminConverter.request2param(request), + DATA_SOURCE_ACCESS_SELECTOR); + return WebPageResult.of(servicePage.getData().stream() + .map(dataSourceAccessAdminConverter::dto2vo) + .toList(), servicePage.getTotal(), servicePage.getPageNo(), servicePage.getPageSize()); } /** @@ -79,7 +81,8 @@ public ActionResult batchCreate(@Valid @RequestBody DataSourceAccessBatchCreateR */ @DeleteMapping("/{id}") public DataResult delete(@PathVariable Long id) { - return dataSourceAccessService.delete(id).toBooleaSuccessnDataResult(); + dataSourceAccessService.delete(id); + return DataResult.of(true); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceAdminController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceAdminController.java index e6e1ad78f..c11cfac3e 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceAdminController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceAdminController.java @@ -53,8 +53,10 @@ public class DataSourceAdminController { public WebPageResult page(@Valid CommonPageQueryRequest request) { DataSourcePageQueryParam param = dataSourceAdminConverter.request2param(request); param.orderBy(OrderCondition.ID_DESC); - return dataSourceService.queryPageWithPermission(param, DATA_SOURCE_SELECTOR) - .mapToWeb(dataSourceAdminConverter::dto2vo); + var servicePage = dataSourceService.queryPageWithPermission(param, DATA_SOURCE_SELECTOR); + return WebPageResult.of(servicePage.getData().stream() + .map(dataSourceAdminConverter::dto2vo) + .toList(), servicePage.getTotal(), servicePage.getPageNo(), servicePage.getPageSize()); } /** @@ -67,7 +69,7 @@ public WebPageResult page(@Valid CommonPageQueryRequest r @PostMapping("/create") public DataResult create(@Valid @RequestBody DataSourceCreateRequest request) { DataSourceCreateParam param = dataSourceAdminConverter.createReq2param(request); - return dataSourceService.createWithPermission(param); + return DataResult.of(dataSourceService.createWithPermission(param)); } /** @@ -80,7 +82,7 @@ public DataResult create(@Valid @RequestBody DataSourceCreateRequest reque @PostMapping("/update") public DataResult update(@Valid @RequestBody DataSourceUpdateRequest request) { DataSourceUpdateParam param = dataSourceAdminConverter.updateReq2param(request); - return dataSourceService.updateWithPermission(param); + return DataResult.of(dataSourceService.updateWithPermission(param)); } /** @@ -92,7 +94,7 @@ public DataResult update(@Valid @RequestBody DataSourceUpdateRequest reque */ @PostMapping("/clone") public DataResult clone(@RequestBody DataSourceCloneRequest request) { - return dataSourceService.copyByIdWithPermission(request.getId()); + return DataResult.of(dataSourceService.copyByIdWithPermission(request.getId())); } /** @@ -104,6 +106,7 @@ public DataResult clone(@RequestBody DataSourceCloneRequest request) { */ @DeleteMapping("/{id}") public DataResult delete(@PathVariable Long id) { - return dataSourceService.deleteWithPermission(id).toBooleaSuccessnDataResult(); + dataSourceService.deleteWithPermission(id); + return DataResult.of(true); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamAdminController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamAdminController.java index d718db121..4f6a5340f 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamAdminController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamAdminController.java @@ -50,8 +50,10 @@ public class TeamAdminController { public WebPageResult page(@Valid CommonPageQueryRequest request) { TeamPageQueryParam param = teamAdminConverter.request2param(request); param.orderBy(OrderCondition.ID_DESC); - return teamService.pageQuery(param, TEAM_SELECTOR) - .mapToWeb(teamAdminConverter::dto2vo); + var servicePage = teamService.pageQuery(param, TEAM_SELECTOR); + return WebPageResult.of(servicePage.getData().stream() + .map(teamAdminConverter::dto2vo) + .toList(), servicePage.getTotal(), servicePage.getPageNo(), servicePage.getPageSize()); } /** @@ -63,7 +65,7 @@ public WebPageResult page(@Valid CommonPageQueryRequest request */ @PostMapping("/create") public DataResult create(@RequestBody TeamCreateRequest request) { - return teamService.create(teamAdminConverter.request2param(request)); + return DataResult.of(teamService.create(teamAdminConverter.request2param(request))); } /** @@ -75,7 +77,7 @@ public DataResult create(@RequestBody TeamCreateRequest request) { */ @PostMapping("/update") public DataResult update(@RequestBody TeamUpdateRequest request) { - return teamService.update(teamAdminConverter.request2param(request)); + return DataResult.of(teamService.update(teamAdminConverter.request2param(request))); } /** @@ -86,6 +88,7 @@ public DataResult update(@RequestBody TeamUpdateRequest request) { */ @DeleteMapping("/{id}") public DataResult delete(@PathVariable Long id) { - return teamService.delete(id).toBooleaSuccessnDataResult(); + teamService.delete(id); + return DataResult.of(true); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamDataSourceAdminController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamDataSourceAdminController.java index 1d4c84379..fd5e632c1 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamDataSourceAdminController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamDataSourceAdminController.java @@ -53,9 +53,11 @@ public class TeamDataSourceAdminController { */ @GetMapping("/page") public WebPageResult page(@Valid TeamPageCommonQueryRequest request) { - return dataSourceAccessService.comprehensivePageQuery(teamDataSourcesAdminConverter.request2param(request), - DATA_SOURCE_ACCESS_SELECTOR) - .mapToWeb(teamDataSourcesAdminConverter::dto2vo); + var servicePage = dataSourceAccessService.comprehensivePageQuery(teamDataSourcesAdminConverter.request2param(request), + DATA_SOURCE_ACCESS_SELECTOR); + return WebPageResult.of(servicePage.getData().stream() + .map(teamDataSourcesAdminConverter::dto2vo) + .toList(), servicePage.getTotal(), servicePage.getPageNo(), servicePage.getPageSize()); } /** @@ -74,7 +76,7 @@ public ActionResult create(@Valid @RequestBody TeamDataSourceBatchCreateRequest dataSourceAccessPageQueryParam.setAccessObjectType(AccessObjectTypeEnum.TEAM.getCode()); dataSourceAccessPageQueryParam.setAccessObjectId(request.getTeamId()); dataSourceAccessPageQueryParam.queryOne(); - if (dataSourceAccessService.pageQuery(dataSourceAccessPageQueryParam, null).hasData()) { + if (dataSourceAccessService.pageQuery(dataSourceAccessPageQueryParam, null).isNotEmpty()) { return; } dataSourceAccessService.create(DataSourceAccessCreatParam.builder() @@ -94,6 +96,7 @@ public ActionResult create(@Valid @RequestBody TeamDataSourceBatchCreateRequest */ @DeleteMapping("/{id}") public DataResult delete(@PathVariable Long id) { - return dataSourceAccessService.delete(id).toBooleaSuccessnDataResult(); + dataSourceAccessService.delete(id); + return DataResult.of(true); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamUserAdminController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamUserAdminController.java index d5581d314..c9fcecdfb 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamUserAdminController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamUserAdminController.java @@ -48,8 +48,10 @@ public class TeamUserAdminController { */ @GetMapping("/page") public WebPageResult page(@Valid TeamPageCommonQueryRequest request) { - return teamUserService.comprehensivePageQuery(teamUserAdminConverter.request2param(request), TEAM_USER_SELECTOR) - .mapToWeb(teamUserAdminConverter::dto2vo); + var servicePage = teamUserService.comprehensivePageQuery(teamUserAdminConverter.request2param(request), TEAM_USER_SELECTOR); + return WebPageResult.of(servicePage.getData().stream() + .map(teamUserAdminConverter::dto2vo) + .toList(), servicePage.getTotal(), servicePage.getPageNo(), servicePage.getPageSize()); } /** @@ -67,7 +69,7 @@ public ActionResult create(@Valid @RequestBody TeamUserBatchCreateRequest reques teamUserPageQueryParam.setTeamId(request.getTeamId()); teamUserPageQueryParam.setUserId(userId); teamUserPageQueryParam.queryOne(); - if (teamUserService.pageQuery(teamUserPageQueryParam, null).hasData()) { + if (teamUserService.pageQuery(teamUserPageQueryParam, null).isNotEmpty()) { return; } teamUserService.create(TeamUserCreatParam.builder() @@ -86,6 +88,7 @@ public ActionResult create(@Valid @RequestBody TeamUserBatchCreateRequest reques */ @DeleteMapping("/{id}") public DataResult delete(@PathVariable Long id) { - return teamUserService.delete(id).toBooleaSuccessnDataResult(); + teamUserService.delete(id); + return DataResult.of(true); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserAdminController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserAdminController.java index 5f45bd79d..f9ebb6a84 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserAdminController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserAdminController.java @@ -51,8 +51,10 @@ public class UserAdminController { public WebPageResult page(@Valid CommonPageQueryRequest request) { UserPageQueryParam param = userAdminConverter.request2param(request); param.orderBy(OrderCondition.ID_DESC); - return userService.pageQuery(param, USER_SELECTOR) - .mapToWeb(userAdminConverter::dto2vo); + var servicePage = userService.pageQuery(param, USER_SELECTOR); + return WebPageResult.of(servicePage.getData().stream() + .map(userAdminConverter::dto2vo) + .toList(), servicePage.getTotal(), servicePage.getPageNo(), servicePage.getPageSize()); } /** @@ -64,7 +66,7 @@ public WebPageResult page(@Valid CommonPageQueryRequest request */ @PostMapping("/create") public DataResult create(@Valid @RequestBody UserCreateRequest request) { - return userService.create(userAdminConverter.request2param(request)); + return DataResult.of(userService.create(userAdminConverter.request2param(request))); } /** @@ -76,7 +78,7 @@ public DataResult create(@Valid @RequestBody UserCreateRequest request) { */ @PostMapping("/update") public DataResult update(@RequestBody UserUpdateRequest request) { - return userService.update(userAdminConverter.request2param(request)); + return DataResult.of(userService.update(userAdminConverter.request2param(request))); } /** @@ -87,6 +89,7 @@ public DataResult update(@RequestBody UserUpdateRequest request) { */ @DeleteMapping("/{id}") public DataResult delete(@PathVariable Long id) { - return userService.delete(id).toBooleaSuccessnDataResult(); + userService.delete(id); + return DataResult.of(true); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserDataSourceAdminController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserDataSourceAdminController.java index c30cc4409..1de2d4b53 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserDataSourceAdminController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserDataSourceAdminController.java @@ -53,9 +53,11 @@ public class UserDataSourceAdminController { */ @GetMapping("/page") public WebPageResult page(@Valid UserPageCommonQueryRequest request) { - return dataSourceAccessService.comprehensivePageQuery(userDataSourcesAdminConverter.request2param(request), - DATA_SOURCE_ACCESS_SELECTOR) - .mapToWeb(userDataSourcesAdminConverter::dto2vo); + var servicePage = dataSourceAccessService.comprehensivePageQuery(userDataSourcesAdminConverter.request2param(request), + DATA_SOURCE_ACCESS_SELECTOR); + return WebPageResult.of(servicePage.getData().stream() + .map(userDataSourcesAdminConverter::dto2vo) + .toList(), servicePage.getTotal(), servicePage.getPageNo(), servicePage.getPageSize()); } /** @@ -74,7 +76,7 @@ public ActionResult create(@RequestBody UserDataSourceBatchCreateRequest request dataSourceAccessPageQueryParam.setAccessObjectType(AccessObjectTypeEnum.USER.getCode()); dataSourceAccessPageQueryParam.setAccessObjectId(request.getUserId()); dataSourceAccessPageQueryParam.queryOne(); - if (dataSourceAccessService.pageQuery(dataSourceAccessPageQueryParam, null).hasData()) { + if (dataSourceAccessService.pageQuery(dataSourceAccessPageQueryParam, null).isNotEmpty()) { return; } dataSourceAccessService.create(DataSourceAccessCreatParam.builder() @@ -94,6 +96,7 @@ public ActionResult create(@RequestBody UserDataSourceBatchCreateRequest request */ @DeleteMapping("/{id}") public DataResult delete(@PathVariable Long id) { - return dataSourceAccessService.delete(id).toBooleaSuccessnDataResult(); + dataSourceAccessService.delete(id); + return DataResult.of(true); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserTeamAdminController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserTeamAdminController.java index 06d1d3b8e..02150038b 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserTeamAdminController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserTeamAdminController.java @@ -47,8 +47,10 @@ public class UserTeamAdminController { */ @GetMapping("/page") public WebPageResult page(@Valid UserPageCommonQueryRequest request) { - return teamUserService.comprehensivePageQuery(userTeamAdminConverter.request2param(request), TEAM_USER_SELECTOR) - .mapToWeb(userTeamAdminConverter::dto2vo); + var servicePage = teamUserService.comprehensivePageQuery(userTeamAdminConverter.request2param(request), TEAM_USER_SELECTOR); + return WebPageResult.of(servicePage.getData().stream() + .map(userTeamAdminConverter::dto2vo) + .toList(), servicePage.getTotal(), servicePage.getPageNo(), servicePage.getPageSize()); } /** @@ -66,7 +68,7 @@ public ActionResult bacthCreate(@Valid @RequestBody UserTeamBatchCreateRequest r teamUserPageQueryParam.setTeamId(teamId); teamUserPageQueryParam.setUserId(request.getUserId()); teamUserPageQueryParam.queryOne(); - if (teamUserService.pageQuery(teamUserPageQueryParam, null).hasData()) { + if (teamUserService.pageQuery(teamUserPageQueryParam, null).isNotEmpty()) { return; } teamUserService.create(TeamUserCreatParam.builder() @@ -85,6 +87,7 @@ public ActionResult bacthCreate(@Valid @RequestBody UserTeamBatchCreateRequest r */ @DeleteMapping("/{id}") public DataResult delete(@PathVariable Long id) { - return teamUserService.delete(id).toBooleaSuccessnDataResult(); + teamUserService.delete(id); + return DataResult.of(true); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-common-api/src/main/java/ai/chat2db/server/common/api/controller/converter/EnvironmentCommonConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-common-api/src/main/java/ai/chat2db/server/common/api/controller/converter/EnvironmentCommonConverter.java index 74978830f..3fd558413 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-common-api/src/main/java/ai/chat2db/server/common/api/controller/converter/EnvironmentCommonConverter.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-common-api/src/main/java/ai/chat2db/server/common/api/controller/converter/EnvironmentCommonConverter.java @@ -2,10 +2,11 @@ import java.util.List; +import org.mapstruct.Mapper; + import ai.chat2db.server.common.api.controller.vo.SimpleEnvironmentVO; import ai.chat2db.server.domain.api.model.Environment; import lombok.extern.slf4j.Slf4j; -import org.mapstruct.Mapper; /** * converter @@ -17,6 +18,7 @@ public abstract class EnvironmentCommonConverter { + public abstract SimpleEnvironmentVO dto2vo(Environment environment); /** * convert * diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/pom.xml b/chat2db-server/chat2db-server-web/chat2db-server-web-api/pom.xml index cf32aae04..875a22c61 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/pom.xml +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/pom.xml @@ -43,8 +43,20 @@ chatgpt-java - com.theokanning.openai-gpt3-java - service + org.springframework.ai + spring-ai-client-chat + + + org.springframework.ai + spring-ai-openai + + + org.springframework.ai + spring-ai-anthropic + + + org.springframework.ai + spring-ai-azure-openai commons-io @@ -104,6 +116,28 @@ org.springframework.boot spring-boot-starter-websocket + + org.springframework.statemachine + spring-statemachine-starter + 4.0.0 + + + io.micrometer + context-propagation + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + net.datafaker + datafaker + 2.1.0 + diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/aspect/ConnectionInfoHandler.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/aspect/ConnectionInfoHandler.java index c8958d783..5b87d14e9 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/aspect/ConnectionInfoHandler.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/aspect/ConnectionInfoHandler.java @@ -66,9 +66,8 @@ public Object connectionInfoHandler(ProceedingJoinPoint proceedingJoinPoint) thr } public ConnectInfo toInfo(Long dataSourceId, String database, Long consoleId, String schemaName) { - DataResult result = dataSourceService.queryById(dataSourceId); - DataSource dataSource = result.getData(); - if (!result.success() || dataSource == null) { + DataSource dataSource = dataSourceService.queryById(dataSourceId); + if (dataSource == null) { throw new ParamBusinessException("dataSourceId"); } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/config/AiChatConfig.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/config/AiChatConfig.java new file mode 100644 index 000000000..6eae99983 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/config/AiChatConfig.java @@ -0,0 +1,200 @@ +package ai.chat2db.server.web.api.config; + +import ai.chat2db.server.domain.api.enums.AiSqlSourceEnum; +import ai.chat2db.server.domain.api.model.Config; +import ai.chat2db.server.domain.api.service.ConfigService; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; +import ai.chat2db.server.web.api.controller.ai.enums.PromptType; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.TypeReference; +import com.google.common.collect.ImmutableMap; +import lombok.Data; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.ai.anthropic.AnthropicChatModel; +import org.springframework.ai.anthropic.AnthropicChatOptions; +import org.springframework.ai.anthropic.api.AnthropicApi; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.openai.OpenAiChatModel; +import org.springframework.ai.openai.OpenAiChatOptions; +import org.springframework.ai.openai.api.OpenAiApi; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.annotation.concurrent.Immutable; +import java.util.*; + +@Service +public class AiChatConfig { + private static final String MODEL_SERVICE_CONFIG_CODE = "ai.model.services"; + private static final String MODEL_DEFAULT_CONFIG_CODE = "ai.model.default"; + + @Autowired + private ConfigService configService; + + public ChatClient createChatClient(PromptType promptType) { + ModelRuntimeConfig runtimeConfig = loadModelRuntimeConfig(); + boolean useFastModel = promptType != null && promptType.isSimpleTask() + && runtimeConfig.fastModel != null && runtimeConfig.fastService != null; + if (useFastModel) { + return createChatClientByModel(runtimeConfig.fastService, runtimeConfig.fastModel, true); + } + return createChatClientByModel(runtimeConfig.defaultService, runtimeConfig.defaultModel, false); + } + + public ChatClient createFastChatClient() { + ModelRuntimeConfig runtimeConfig = loadModelRuntimeConfig(); + if (runtimeConfig.fastModel != null && runtimeConfig.fastService != null) { + return createChatClientByModel(runtimeConfig.fastService, runtimeConfig.fastModel, true); + } + return createChatClientByModel(runtimeConfig.defaultService, runtimeConfig.defaultModel, false); + } + + public ChatResponse testModelService(String provider, String apiHost, String apiKey, String model) { + ModelServiceConfig service = new ModelServiceConfig(); + service.setProvider(provider); + service.setApiHost(apiHost); + service.setApiKey(apiKey); + ModelItem modelItem = new ModelItem(); + modelItem.setModel(model); + return createChatClientByModel(service, modelItem, true) + .prompt("ping") + .call() + .chatResponse(); + } + + private ChatClient createChatClientByModel(ModelServiceConfig service, ModelItem modelItem, boolean fastMode) { + AiSqlSourceEnum aiSqlSourceEnum = AiSqlSourceEnum.getByName(service.getProvider()); + if (aiSqlSourceEnum == null) { + aiSqlSourceEnum = AiSqlSourceEnum.OPENAI; + } + + if (aiSqlSourceEnum == AiSqlSourceEnum.ANTHROPIC) { + AnthropicApi anthropicApi = AnthropicApi.builder().apiKey(service.getApiKey()).build(); + AnthropicChatOptions options = AnthropicChatOptions.builder() + .model(modelItem.getModel()) + .temperature(fastMode ? 0.5 : 0.7) + .maxTokens(fastMode ? 1024 : 4096) + .build(); + AnthropicChatModel chatModel = AnthropicChatModel.builder() + .anthropicApi(anthropicApi) + .defaultOptions(options) + .build(); + return ChatClient.builder(chatModel).build(); + } + + String apiHost = service.getApiHost(); + OpenAiApi openAiApi = OpenAiApi.builder().baseUrl(apiHost).apiKey(service.getApiKey()).build(); + Map extraBody = new HashMap<>(); + if (StringUtils.contains(modelItem.getModel(), "qwen")) { + extraBody.put("enable_thinking", !fastMode); + } else if (StringUtils.contains(modelItem.getModel(), "hy")) { + extraBody.put("reasoning", ImmutableMap.of("enable", !fastMode)); + } + OpenAiChatOptions options = OpenAiChatOptions.builder() + .model(modelItem.getModel()) + .temperature(fastMode ? 0.5 : 0.7) + .maxTokens(fastMode ? 1024 : 4096) + .extraBody(extraBody) + .build(); + OpenAiChatModel chatModel = OpenAiChatModel.builder() + .openAiApi(openAiApi) + .defaultOptions(options) + .build(); + return ChatClient.builder(chatModel).build(); + } + + private ModelRuntimeConfig loadModelRuntimeConfig() { + String serviceJson = getConfigValue(MODEL_SERVICE_CONFIG_CODE); + String defaultJson = getConfigValue(MODEL_DEFAULT_CONFIG_CODE); + if (serviceJson.isEmpty() || defaultJson.isEmpty()) { + throw new IllegalStateException("Model service or default model config is empty."); + } + + List serviceList = JSON.parseObject(serviceJson, new TypeReference<>() { + }); + DefaultModelConfig defaultModelConfig = JSON.parseObject(defaultJson, DefaultModelConfig.class); + if (serviceList == null || serviceList.isEmpty() || defaultModelConfig == null + || defaultModelConfig.getDefaultModelId() == null || defaultModelConfig.getDefaultModelId().isEmpty()) { + throw new IllegalStateException("Default model config is invalid."); + } + + ModelLocateResult defaultResult = locateModel(serviceList, defaultModelConfig.getDefaultModelId()); + if (defaultResult == null) { + throw new IllegalStateException("Default model not found in model services."); + } + + ModelLocateResult fastResult = null; + if (defaultModelConfig.getFastModelId() != null && !defaultModelConfig.getFastModelId().isEmpty()) { + fastResult = locateModel(serviceList, defaultModelConfig.getFastModelId()); + } + + ModelRuntimeConfig runtimeConfig = new ModelRuntimeConfig(); + runtimeConfig.defaultService = defaultResult.service; + runtimeConfig.defaultModel = defaultResult.model; + if (fastResult != null) { + runtimeConfig.fastService = fastResult.service; + runtimeConfig.fastModel = fastResult.model; + } + return runtimeConfig; + } + + private ModelLocateResult locateModel(List serviceList, String modelId) { + for (ModelServiceConfig service : serviceList) { + if (service.getModelList() == null) { + continue; + } + for (ModelItem model : service.getModelList()) { + if (Objects.equals(model.getId(), modelId)) { + ModelLocateResult result = new ModelLocateResult(); + result.service = service; + result.model = model; + return result; + } + } + } + return null; + } + + private String getConfigValue(String code) { + Config result = configService.find(code); + if (result != null && result.getContent() != null + && !result.getContent().isEmpty()) { + return result.getContent(); + } + return ""; + } + + @Data + private static class DefaultModelConfig { + private String defaultModelId; + private String fastModelId; + } + + @Data + private static class ModelItem { + private String id; + private String model; + } + + @Data + private static class ModelServiceConfig { + private String provider; + private String apiKey; + private String apiHost; + private List modelList = new ArrayList<>(); + } + + private static class ModelLocateResult { + private ModelServiceConfig service; + private ModelItem model; + } + + private static class ModelRuntimeConfig { + private ModelServiceConfig defaultService; + private ModelItem defaultModel; + private ModelServiceConfig fastService; + private ModelItem fastModel; + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/config/ReactorContextConfig.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/config/ReactorContextConfig.java new file mode 100644 index 000000000..00f17df11 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/config/ReactorContextConfig.java @@ -0,0 +1,14 @@ +package ai.chat2db.server.web.api.config; + +import jakarta.annotation.PostConstruct; +import org.springframework.stereotype.Component; +import reactor.core.publisher.Hooks; + +@Component +public class ReactorContextConfig { + + @PostConstruct + public void init() { + Hooks.enableAutomaticContextPropagation(); + } +} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/AiConfigController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/AiConfigController.java deleted file mode 100644 index 2142d5a4a..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/AiConfigController.java +++ /dev/null @@ -1,131 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai; - -import java.util.Objects; - -import ai.chat2db.server.domain.api.enums.AiSqlSourceEnum; -import ai.chat2db.server.domain.api.enums.RoleCodeEnum; -import ai.chat2db.server.domain.api.model.Config; -import ai.chat2db.server.domain.api.param.SystemConfigParam; -import ai.chat2db.server.domain.api.service.ConfigService; -import ai.chat2db.server.tools.base.wrapper.result.DataResult; -import ai.chat2db.server.tools.common.config.Chat2dbProperties; -import ai.chat2db.server.tools.common.model.LoginUser; -import ai.chat2db.server.tools.common.util.ContextUtils; -import ai.chat2db.server.tools.common.util.I18nUtils; -import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; -import ai.chat2db.server.web.api.controller.ai.chat2db.client.Chat2dbAIClient; -import ai.chat2db.server.web.api.controller.ai.rest.client.RestAIClient; -import ai.chat2db.server.web.api.http.GatewayClientService; -import ai.chat2db.server.web.api.http.response.ApiKeyResponse; -import ai.chat2db.server.web.api.http.response.InviteQrCodeResponse; -import ai.chat2db.server.web.api.http.response.QrCodeResponse; -import jakarta.annotation.Resource; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -/** - * AI configuration information interface - * - * @author Jiaju Zhuang - */ -@RestController -@ConnectionInfoAspect -@RequestMapping("/api/ai/config") -@Slf4j -public class AiConfigController { - - @Resource - private GatewayClientService gatewayClientService; - - @Autowired - private ConfigService configService; - @Resource - private Chat2dbProperties chat2dbProperties; - - /** - * AI configuration information interface - * - * @return - */ - @GetMapping("/getLoginQrCode") - public DataResult getLoginQrCode() { - LoginUser loginUser = ContextUtils.getLoginUser(); - if (RoleCodeEnum.USER.getCode().equals(loginUser.getRoleCode())) { - return DataResult.of( - QrCodeResponse.builder().tip(I18nUtils.getMessage("settings.permissionDeniedForAiConfig")).build()); - } - return gatewayClientService.getLoginQrCode(); - } - - /** - * Query login status - * - * @param token - * @return - */ - @GetMapping("/getLoginStatus") - public DataResult getLoginStatus(@RequestParam(required = false) String token) { - LoginUser loginUser = ContextUtils.getLoginUser(); - if (RoleCodeEnum.USER.getCode().equals(loginUser.getRoleCode())) { - return DataResult.of(QrCodeResponse.builder().build()); - } - DataResult dataResult = gatewayClientService.getLoginStatus(token); - QrCodeResponse qrCodeResponse = dataResult.getData(); - // Representative successfully logged in - if (StringUtils.isNotBlank(qrCodeResponse.getApiKey())) { - SystemConfigParam sqlSourceParam = SystemConfigParam.builder().code(RestAIClient.AI_SQL_SOURCE) - .content(AiSqlSourceEnum.CHAT2DBAI.getCode()).build(); - configService.createOrUpdate(sqlSourceParam); - SystemConfigParam param = SystemConfigParam.builder() - .code(Chat2dbAIClient.CHAT2DB_OPENAI_KEY).content(qrCodeResponse.getApiKey()) - .build(); - configService.createOrUpdate(param); - SystemConfigParam hostParam = SystemConfigParam.builder() - .code(Chat2dbAIClient.CHAT2DB_OPENAI_HOST) - .content(chat2dbProperties.getGateway().getModelBaseUrl() + "/model") - .build(); - configService.createOrUpdate(hostParam); - Chat2dbAIClient.refresh(); - } - return dataResult; - } - - /** - * Return remaining times - * - * @return - */ - @GetMapping("/remaininguses") - public DataResult remaininguses() { - String apiKey = getApiKey(); - if (StringUtils.isBlank(apiKey)) { - return DataResult.of(ApiKeyResponse.builder() - .build()); - } - return gatewayClientService.remaininguses(apiKey); - } - - /** - * Obtain invitation QR code - * - * @return - */ - @GetMapping("/getInviteQrCode") - public DataResult getInviteQrCode() { - String apiKey = getApiKey(); - if (StringUtils.isBlank(apiKey)) { - return DataResult.of(new InviteQrCodeResponse()); - } - return gatewayClientService.getInviteQrCode(apiKey); - } - - private String getApiKey() { - DataResult apiKey = configService.find(Chat2dbAIClient.CHAT2DB_OPENAI_KEY); - return Objects.nonNull(apiKey.getData()) ? apiKey.getData().getContent() : null; - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java index c9e77806f..bd9cb79d0 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java @@ -1,89 +1,54 @@ package ai.chat2db.server.web.api.controller.ai; -import ai.chat2db.server.domain.api.enums.AiSqlSourceEnum; -import ai.chat2db.server.domain.api.model.Config; -import ai.chat2db.server.domain.api.model.DataSource; -import ai.chat2db.server.domain.api.param.ShowCreateTableParam; -import ai.chat2db.server.domain.api.param.TableQueryParam; -import ai.chat2db.server.domain.api.service.ConfigService; +import java.io.IOException; +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.statemachine.StateMachine; +import org.springframework.statemachine.config.StateMachineFactory; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import ai.chat2db.server.domain.api.model.AiConversation; +import ai.chat2db.server.domain.api.param.ai.AiConversationCreateParam; +import ai.chat2db.server.domain.api.service.AiConversationService; import ai.chat2db.server.domain.api.service.DataSourceService; -import ai.chat2db.server.domain.api.service.TableService; -import ai.chat2db.server.tools.base.enums.WhiteListTypeEnum; -import ai.chat2db.server.tools.base.wrapper.result.DataResult; +import ai.chat2db.server.domain.repository.Dbutils; +import ai.chat2db.server.tools.base.enums.DataSourceTypeEnum; import ai.chat2db.server.tools.common.exception.ParamBusinessException; -import ai.chat2db.server.tools.common.util.EasyEnumUtils; +import ai.chat2db.server.tools.common.model.Context; +import ai.chat2db.server.tools.common.model.LoginUser; +import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; -import ai.chat2db.server.web.api.controller.ai.azure.client.AzureOpenAIClient; -import ai.chat2db.server.web.api.controller.ai.azure.listener.AzureOpenAIEventSourceListener; -import ai.chat2db.server.web.api.controller.ai.azure.model.AzureChatMessage; -import ai.chat2db.server.web.api.controller.ai.azure.model.AzureChatRole; -import ai.chat2db.server.web.api.controller.ai.baichuan.client.BaichuanAIClient; -import ai.chat2db.server.web.api.controller.ai.baichuan.listener.BaichuanChatAIEventSourceListener; -import ai.chat2db.server.web.api.controller.ai.chat2db.client.Chat2dbAIClient; -import ai.chat2db.server.web.api.controller.ai.chat2db.listener.Chat2dbAIEventSourceListener; -import ai.chat2db.server.web.api.controller.ai.claude.client.ClaudeAIClient; -import ai.chat2db.server.web.api.controller.ai.claude.listener.ClaudeAIEventSourceListener; -import ai.chat2db.server.web.api.controller.ai.claude.model.ClaudeChatCompletionsOptions; -import ai.chat2db.server.web.api.controller.ai.claude.model.ClaudeChatMessage; -import ai.chat2db.server.web.api.controller.ai.config.LocalCache; -import ai.chat2db.server.web.api.controller.ai.converter.ChatConverter; +import ai.chat2db.server.web.api.config.AiChatConfig; import ai.chat2db.server.web.api.controller.ai.enums.PromptType; -import ai.chat2db.server.web.api.controller.ai.fastchat.client.FastChatAIClient; -import ai.chat2db.server.web.api.controller.ai.fastchat.embeddings.FastChatEmbeddingResponse; -import ai.chat2db.server.web.api.controller.ai.fastchat.listener.FastChatAIEventSourceListener; -import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatMessage; -import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatRole; -import ai.chat2db.server.web.api.controller.ai.openai.client.OpenAIClient; -import ai.chat2db.server.web.api.controller.ai.openai.listener.OpenAIEventSourceListener; import ai.chat2db.server.web.api.controller.ai.request.ChatQueryRequest; -import ai.chat2db.server.web.api.controller.ai.request.ChatRequest; -import ai.chat2db.server.web.api.controller.ai.rest.client.RestAIClient; -import ai.chat2db.server.web.api.controller.ai.rest.listener.RestAIEventSourceListener; -import ai.chat2db.server.web.api.controller.ai.tongyi.client.TongyiChatAIClient; -import ai.chat2db.server.web.api.controller.ai.tongyi.listener.TongyiChatAIEventSourceListener; -import ai.chat2db.server.web.api.controller.ai.wenxin.client.WenxinAIClient; -import ai.chat2db.server.web.api.controller.ai.wenxin.listener.WenxinAIEventSourceListener; -import ai.chat2db.server.web.api.controller.ai.zhipu.client.ZhipuChatAIClient; -import ai.chat2db.server.web.api.controller.ai.zhipu.listener.ZhipuChatAIEventSourceListener; -import ai.chat2db.server.web.api.http.GatewayClientService; -import ai.chat2db.server.web.api.http.model.EsTableSchema; -import ai.chat2db.server.web.api.http.model.TableSchema; -import ai.chat2db.server.web.api.http.request.EsTableSchemaRequest; -import ai.chat2db.server.web.api.http.request.TableSchemaRequest; -import ai.chat2db.server.web.api.http.request.WhiteListRequest; -import ai.chat2db.server.web.api.http.response.EsTableSchemaResponse; -import ai.chat2db.server.web.api.http.response.TableSchemaResponse; -import ai.chat2db.server.web.api.util.ApplicationContextUtil; +import ai.chat2db.server.web.api.controller.ai.statemachine.ChatContext; +import ai.chat2db.server.web.api.controller.ai.statemachine.ChatEvent; +import ai.chat2db.server.web.api.controller.ai.statemachine.ChatState; +import ai.chat2db.server.web.api.controller.ai.task.AiConversationTitleTask; +import ai.chat2db.spi.sql.Chat2DBContext; +import ai.chat2db.spi.sql.ConnectInfo; import cn.hutool.core.util.StrUtil; -import cn.hutool.json.JSONUtil; -import com.alibaba.fastjson2.JSON; -import com.google.common.collect.Lists; -import com.unfbx.chatgpt.entity.chat.Message; -import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; +import reactor.core.publisher.Mono; -import java.io.IOException; -import java.math.BigDecimal; -import java.time.Duration; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.stream.Collectors; - -/** - * 描述: - * - * @author https:www.unfbx.com - * @date 2023-03-01 - */ @RestController @ConnectionInfoAspect @RequestMapping("/api/ai") @@ -91,725 +56,258 @@ public class ChatController { @Autowired - private TableService tableService; + private StateMachineFactory stateMachineFactory; @Autowired - private ChatConverter chatConverter; + private AiChatConfig aiChatConfig; @Autowired - private DataSourceService dataSourceService; + private AiConversationService aiConversationService; - @Value("${chatgpt.context.length}") - private Integer contextLength; - - @Value("${chatgpt.version}") - private String gptVersion; + @Autowired + private AiConversationTitleTask aiConversationTitleTask; - @Resource - private GatewayClientService gatewayClientService; + @Autowired + private DataSourceService dataSourceService; - /** - * chat的超时时间 - */ private static final Long CHAT_TIMEOUT = Duration.ofMinutes(50).toMillis(); - /** - * 提示语最大token数 - */ - private Integer MAX_PROMPT_LENGTH = 3850; - - /** - * token转换字符串长度 - */ - private Integer TOKEN_CONVERT_CHAR_LENGTH = 4; - - /** - * 返回token大小 - */ - private Integer RETURN_TOKEN_LENGTH = 150; - - - /** - * 自定义模型流式输出接口DEMO - *

- * Note:使用自己本地的流式输出的自定义AI,接口输入和输出需与该样例保持一致 - *

- * - * @param queryRequest - * @return - * @throws IOException - */ - @PostMapping("/custom/stream/chat") - @CrossOrigin - public SseEmitter customChat(@RequestBody ChatRequest queryRequest) throws IOException { - SseEmitter emitter = new SseEmitter(CHAT_TIMEOUT); - - // 设置 SSEEmitter 的事件处理程序 - emitter.onCompletion(() -> log.info(LocalDateTime.now() + ", on completion")); - emitter.onTimeout(() -> { - log.info(LocalDateTime.now() + ", uid# on timeout"); - emitter.complete(); - }); - - // 启动一个新的线程来生成 SSE 事件 - new Thread(() -> { - try { - for (int i = 0; i < 10; i++) { - emitter.send(SseEmitter.event().name("message").data("Event " + i)); - Thread.sleep(1000); - } - } catch (Exception e) { - emitter.completeWithError(e); - } finally { - emitter.complete(); - } - }).start(); + private final ConcurrentHashMap> activeSessions = new ConcurrentHashMap<>(); + private final ConcurrentHashMap activeContexts = new ConcurrentHashMap<>(); + private final ConcurrentHashMap pendingPayloads = new ConcurrentHashMap<>(); - return emitter; - } + private static final long PAYLOAD_TIMEOUT = Duration.ofMinutes(5).toMillis(); - /** - * 自定义模型非流式输出接口DEMO - *

- * Note:使用自己本地的飞流式输出自定义AI,接口输入和输出需与该样例保持一致 - *

- * - * @param queryRequest - * @return - * @throws IOException - */ - @PostMapping("/custom/non/stream/chat") + @PostMapping("/chat/payload") @CrossOrigin - public String customNonStreamChat(@RequestBody ChatRequest queryRequest) throws IOException { - String data = "自定义AI样例接口连接成功!!!!"; - return data; + public ResponseEntity> createChatPayload(@RequestBody ChatQueryRequest queryRequest) { + cleanupExpiredPayloads(); + + String payloadId = UUID.randomUUID().toString(); + pendingPayloads.put(payloadId, new PendingChatPayload(queryRequest, System.currentTimeMillis())); + return ResponseEntity.ok(Map.of("payloadId", payloadId)); } - /** - * SQL转换模型 - * - * @param queryRequest - * @param headers - * @return - * @throws IOException - */ @GetMapping("/chat") @CrossOrigin - public SseEmitter completions(ChatQueryRequest queryRequest, @RequestHeader Map headers) - throws IOException { - //默认30秒超时,设置为0L则永不超时 - SseEmitter sseEmitter = new SseEmitter(CHAT_TIMEOUT); + public SseEmitter chat(ChatQueryRequest queryRequest, @RequestHeader Map headers) + throws IOException { String uid = headers.get("uid"); if (StrUtil.isBlank(uid)) { throw new ParamBusinessException("uid"); } - //提示消息不得为空 - if (StringUtils.isBlank(queryRequest.getMessage())) { - throw new ParamBusinessException("message"); - } - - return distributeAISql(queryRequest, sseEmitter, uid); - } + log.info("[ChatController] Received uid from client: {}", uid); + queryRequest = resolvePayload(queryRequest); + SseEmitter sseEmitter = new SseEmitter(CHAT_TIMEOUT); - /** - * distribute with different AI - * - * @return - */ - public SseEmitter distributeAISql(ChatQueryRequest queryRequest, SseEmitter sseEmitter, String uid) throws IOException { - ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); - Config config = configService.find(RestAIClient.AI_SQL_SOURCE).getData(); - String aiSqlSource = AiSqlSourceEnum.CHAT2DBAI.getCode(); - if (Objects.nonNull(config)) { - aiSqlSource = config.getContent(); - } - AiSqlSourceEnum aiSqlSourceEnum = AiSqlSourceEnum.getByName(aiSqlSource); - if (Objects.isNull(aiSqlSourceEnum)) { - aiSqlSourceEnum = AiSqlSourceEnum.OPENAI; - } - uid = aiSqlSourceEnum.getCode() + uid; - switch (Objects.requireNonNull(aiSqlSourceEnum)) { - case OPENAI : - return chatWithOpenAi(queryRequest, sseEmitter, uid); - case CHAT2DBAI: - return chatWithChat2dbAi(queryRequest, sseEmitter, uid); - case RESTAI : - case FASTCHATAI: - return chatWithFastChatAi(queryRequest, sseEmitter, uid); - case AZUREAI : - return chatWithAzureAi(queryRequest, sseEmitter, uid); - case CLAUDEAI: - return chatWithClaudeAi(queryRequest, sseEmitter, uid); - case WENXINAI: - return chatWithWenxinAi(queryRequest, sseEmitter, uid); - case BAICHUANAI: - return chatWithBaichuanAi(queryRequest, sseEmitter, uid); - case TONGYIQIANWENAI: - return chatWithTongyiChatAi(queryRequest, sseEmitter, uid); - case ZHIPUAI: - return chatWithZhipuChatAi(queryRequest, sseEmitter, uid); + // 根据提示类型选择是否使用快速模型 + PromptType promptType = null; + if (StrUtil.isNotBlank(queryRequest.getPromptType())) { + try { + promptType = PromptType.valueOf(queryRequest.getPromptType()); + } catch (Exception ignored) { + log.warn("[ChatController] Invalid promptType: {}", queryRequest.getPromptType()); + } } - return chatWithOpenAi(queryRequest, sseEmitter, uid); - } - /** - * 使用自定义AI接口进行聊天 - * - * @param prompt - * @param sseEmitter - * @return - */ - private SseEmitter chatWithRestAi(ChatQueryRequest prompt, SseEmitter sseEmitter) { - RestAIEventSourceListener eventSourceListener = new RestAIEventSourceListener(sseEmitter); - RestAIClient.getInstance().restCompletions(buildPrompt(prompt), eventSourceListener); - return sseEmitter; - } - - /** - * 使用OPENAI SQL接口 - * - * @param queryRequest - * @param sseEmitter - * @param uid - * @return - * @throws IOException - */ - private SseEmitter chatWithOpenAi(ChatQueryRequest queryRequest, SseEmitter sseEmitter, String uid) - throws IOException { - String prompt = buildPrompt(queryRequest); - if (prompt.length() / TOKEN_CONVERT_CHAR_LENGTH > MAX_PROMPT_LENGTH) { - log.error("提示语超出最大长度:{},输入长度:{}, 请重新输入", MAX_PROMPT_LENGTH, - prompt.length() / TOKEN_CONVERT_CHAR_LENGTH); - throw new ParamBusinessException(); + ChatClient chatClient = aiChatConfig.createChatClient(promptType); + + LoginUser loginUser = ContextUtils.getLoginUser(); + ConnectInfo connectInfo = Chat2DBContext.getConnectInfo().copy(); + + ensureConversationExists(queryRequest, loginUser); + boolean firstTurn = isFirstTurnOfConversation(queryRequest, loginUser); + + ChatContext ctx = ChatContext.builder() + .uid(uid) + .request(queryRequest) + .sseEmitter(sseEmitter) + .chatClient(chatClient) + .cancelled(false) + .loginUser(loginUser) + .connectInfo(connectInfo) + .userMessageId(queryRequest.getUserMessageId()) + .firstTurnOfConversation(firstTurn) + .build(); + + setupSseCallbacks(sseEmitter, uid, ctx); + + sseEmitter.send(SseEmitter.event() + .id(uid) + .name("connect") + .data(LocalDateTime.now().toString()) + .reconnectTime(3000)); + + StateMachine stateMachine = stateMachineFactory.getStateMachine(uid); + stateMachine.getExtendedState().getVariables().put("chatContext", ctx); + + activeSessions.put(uid, stateMachine); + activeContexts.put(uid, ctx); + log.info("[ChatController] Session stored with uid: {}, activeSessions size: {}, activeContexts size: {}", + uid, activeSessions.size(), activeContexts.size()); + + stateMachine.startReactively().subscribe(); + ChatEvent initialEvent = determineInitialEvent(queryRequest); + stateMachine.sendEvent( + Mono.just(MessageBuilder.withPayload(initialEvent).build()) + ).subscribe(); + + if (firstTurn && promptType == PromptType.NL_2_SQL) { + try { + aiConversationTitleTask.generateTitleAsync(queryRequest.getConversationId(), queryRequest.getMessage()); + } catch (Exception e) { + log.warn("[ChatController] Failed to dispatch title task for {}: {}", queryRequest.getConversationId(), e.getMessage()); + } } - List messages = new ArrayList<>(); - prompt = prompt.replaceAll("#", ""); - log.info(prompt); - Message currentMessage = Message.builder().content(prompt).role(Message.Role.USER).build(); - messages.add(currentMessage); - buildSseEmitter(sseEmitter, uid); - - OpenAIEventSourceListener openAIEventSourceListener = new OpenAIEventSourceListener(sseEmitter); - OpenAIClient.getInstance().streamChatCompletion(messages, openAIEventSourceListener); - LocalCache.CACHE.put(uid, JSONUtil.toJsonStr(messages), LocalCache.TIMEOUT); return sseEmitter; } - /** - * 使用OPENAI SQL接口 - * - * @param queryRequest - * @param sseEmitter - * @param uid - * @return - * @throws IOException - */ - private SseEmitter chatWithChat2dbAi(ChatQueryRequest queryRequest, SseEmitter sseEmitter, String uid) - throws IOException { - String prompt = buildPrompt(queryRequest); - if (prompt.length() / TOKEN_CONVERT_CHAR_LENGTH > MAX_PROMPT_LENGTH) { - log.error("exceed max token length:{},input length:{}", MAX_PROMPT_LENGTH, - prompt.length() / TOKEN_CONVERT_CHAR_LENGTH); - throw new ParamBusinessException(); + private boolean isFirstTurnOfConversation(ChatQueryRequest request, LoginUser loginUser) { + if (request == null || StrUtil.isBlank(request.getConversationId())) { + return false; + } + try { + AiConversation conversation = aiConversationService.findByConversationId(request.getConversationId()); + return conversation == null || conversation.getMessageCount() == null || conversation.getMessageCount() == 0; + } catch (Exception e) { + log.warn("[ChatController] Check first turn failed for {}: {}", + request.getConversationId(), e.getMessage()); + return false; } - - prompt = prompt.replaceAll("#", ""); - log.info(prompt); - Message currentMessage = Message.builder().content(prompt).role(Message.Role.USER).build(); - List messages = new ArrayList<>(); - messages.add(currentMessage); - buildSseEmitter(sseEmitter, uid); - - Chat2dbAIEventSourceListener openAIEventSourceListener = new Chat2dbAIEventSourceListener(sseEmitter); - Chat2dbAIClient.getInstance().streamCompletions(messages, openAIEventSourceListener); - LocalCache.CACHE.put(uid, JSONUtil.toJsonStr(messages), LocalCache.TIMEOUT); - return sseEmitter; } - /** - * chat with azure openai - * - * @param queryRequest - * @param sseEmitter - * @param uid - * @return - * @throws IOException - */ - private SseEmitter chatWithAzureAi(ChatQueryRequest queryRequest, SseEmitter sseEmitter, String uid) throws IOException { - String prompt = buildPrompt(queryRequest); - if (prompt.length() / TOKEN_CONVERT_CHAR_LENGTH > MAX_PROMPT_LENGTH) { - log.error("提示语超出最大长度:{},输入长度:{}, 请重新输入", MAX_PROMPT_LENGTH, - prompt.length() / TOKEN_CONVERT_CHAR_LENGTH); - throw new ParamBusinessException(); + private void ensureConversationExists(ChatQueryRequest request, LoginUser loginUser) { + if (request == null || StrUtil.isBlank(request.getConversationId())) { + return; } - List messages = (List)LocalCache.CACHE.get(uid); - if (CollectionUtils.isNotEmpty(messages)) { - if (messages.size() >= contextLength) { - messages = messages.subList(1, contextLength); + try { + if (aiConversationService.findByConversationId(request.getConversationId()) != null) { + return; } - } else { - messages = Lists.newArrayList(); + AiConversationCreateParam param = new AiConversationCreateParam(); + param.setConversationId(request.getConversationId()); + param.setUserId(loginUser == null ? null : loginUser.getId()); + param.setDataSourceId(request.getDataSourceId()); + param.setDatabaseName(request.getDatabaseName()); + param.setSchemaName(request.getSchemaName()); + param.setTitle("新对话"); + aiConversationService.create(param); + } catch (Exception e) { + log.warn("[ChatController] Ensure conversation failed for {}: {}", + request.getConversationId(), e.getMessage()); } - AzureChatMessage currentMessage = new AzureChatMessage(AzureChatRole.USER).setContent(prompt); - messages.add(currentMessage); - - buildSseEmitter(sseEmitter, uid); - - AzureOpenAIEventSourceListener sourceListener = new AzureOpenAIEventSourceListener(sseEmitter); - AzureOpenAIClient.getInstance().streamCompletions(messages, sourceListener); - LocalCache.CACHE.put(uid, messages, LocalCache.TIMEOUT); - return sseEmitter; } - /** - * chat with fast chat openai - * - * @param queryRequest - * @param sseEmitter - * @param uid - * @return - * @throws IOException - */ - private SseEmitter chatWithFastChatAi(ChatQueryRequest queryRequest, SseEmitter sseEmitter, String uid) throws IOException { - String prompt = buildPrompt(queryRequest); - List messages = getFastChatMessage(uid, prompt); - - buildSseEmitter(sseEmitter, uid); - - FastChatAIEventSourceListener sourceListener = new FastChatAIEventSourceListener(sseEmitter); - FastChatAIClient.getInstance().streamCompletions(messages, sourceListener); - LocalCache.CACHE.put(uid, messages, LocalCache.TIMEOUT); - return sseEmitter; - } - - /** - * chat with zhipu chat openai - * - * @param queryRequest - * @param sseEmitter - * @param uid - * @return - * @throws IOException - */ - private SseEmitter chatWithZhipuChatAi(ChatQueryRequest queryRequest, SseEmitter sseEmitter, String uid) throws IOException { - String prompt = buildPrompt(queryRequest); - List messages = getFastChatMessage(uid, prompt); - - buildSseEmitter(sseEmitter, uid); - - ZhipuChatAIEventSourceListener sourceListener = new ZhipuChatAIEventSourceListener(sseEmitter); - ZhipuChatAIClient.getInstance().streamCompletions(messages, sourceListener); - LocalCache.CACHE.put(uid, messages, LocalCache.TIMEOUT); - return sseEmitter; - } - - /** - * chat with tongyi chat openai - * - * @param queryRequest - * @param sseEmitter - * @param uid - * @return - * @throws IOException - */ - private SseEmitter chatWithTongyiChatAi(ChatQueryRequest queryRequest, SseEmitter sseEmitter, String uid) throws IOException { - String prompt = buildPrompt(queryRequest); - List messages = getFastChatMessage(uid, prompt); - - buildSseEmitter(sseEmitter, uid); - - TongyiChatAIEventSourceListener sourceListener = new TongyiChatAIEventSourceListener(sseEmitter); - TongyiChatAIClient.getInstance().streamCompletions(messages, sourceListener); - LocalCache.CACHE.put(uid, messages, LocalCache.TIMEOUT); - return sseEmitter; - } - - /** - * chat with baichuan chat openai - * - * @param queryRequest - * @param sseEmitter - * @param uid - * @return - * @throws IOException - */ - private SseEmitter chatWithBaichuanAi(ChatQueryRequest queryRequest, SseEmitter sseEmitter, String uid) throws IOException { - String prompt = buildPrompt(queryRequest); - List messages = getFastChatMessage(uid, prompt); - - buildSseEmitter(sseEmitter, uid); - - BaichuanChatAIEventSourceListener sourceListener = new BaichuanChatAIEventSourceListener(sseEmitter); - BaichuanAIClient.getInstance().streamCompletions(messages, sourceListener); - LocalCache.CACHE.put(uid, messages, LocalCache.TIMEOUT); - return sseEmitter; - } - - /** - * get fast chat message - * - * @param uid - * @param prompt - * @return - */ - private List getFastChatMessage(String uid, String prompt) { - List messages = (List)LocalCache.CACHE.get(uid); - if (CollectionUtils.isNotEmpty(messages)) { - if (messages.size() >= contextLength) { - messages = messages.subList(1, contextLength); + @DeleteMapping("/chat/{uid}") + @CrossOrigin + public ResponseEntity cancelChat(@PathVariable String uid) { + log.info("[ChatController] Cancel request received for uid: {}", uid); + log.info("[ChatController] activeSessions keys: {}", activeSessions.keySet()); + log.info("[ChatController] activeContexts keys: {}", activeContexts.keySet()); + ChatContext ctx = activeContexts.get(uid); + if (ctx != null) { + log.info("[ChatController] Found context for uid: {}, cancelling...", uid); + ctx.setCancelled(true); + try { + ctx.getSseEmitter().complete(); + } catch (Exception ignored) { } } else { - messages = Lists.newArrayList(); + log.warn("[ChatController] No context found for uid: {}", uid); } - FastChatMessage currentMessage = new FastChatMessage(FastChatRole.USER).setContent(prompt); - messages.add(currentMessage); - return messages; - } - /** - * chat with wenxin chat openai - * - * @param queryRequest - * @param sseEmitter - * @param uid - * @return - * @throws IOException - */ - private SseEmitter chatWithWenxinAi(ChatQueryRequest queryRequest, SseEmitter sseEmitter, String uid) throws IOException { - String prompt = buildPrompt(queryRequest); - List messages = getFastChatMessage(uid, prompt); - if (messages.size() >= 2 && messages.size() % 2 == 0) { - messages.remove(messages.size() - 1); + StateMachine sm = activeSessions.get(uid); + if (sm != null) { + log.info("[ChatController] Found state machine for uid: {}, sending CANCEL event", uid); + sm.sendEvent(MessageBuilder.withPayload(ChatEvent.CANCEL).build()); + } else { + log.warn("[ChatController] No state machine found for uid: {}", uid); } - buildSseEmitter(sseEmitter, uid); - - WenxinAIEventSourceListener sourceListener = new WenxinAIEventSourceListener(sseEmitter); - WenxinAIClient.getInstance().streamCompletions(messages, sourceListener); - LocalCache.CACHE.put(uid, messages, LocalCache.TIMEOUT); - return sseEmitter; + cleanupSession(uid); + return ResponseEntity.ok().build(); } + private ChatEvent determineInitialEvent(ChatQueryRequest request) { + boolean hasTables = CollectionUtils.isNotEmpty(request.getTableNames()); - /** - * chat with claude ai - * - * @param queryRequest - * @param sseEmitter - * @param uid - * @return - * @throws IOException - */ - private SseEmitter chatWithClaudeAi(ChatQueryRequest queryRequest, SseEmitter sseEmitter, String uid) throws IOException { - String prompt = buildPrompt(queryRequest); - ClaudeChatMessage claudeChatMessage = new ClaudeChatMessage(); - claudeChatMessage.setText(prompt); - ClaudeChatCompletionsOptions chatCompletionsOptions = new ClaudeChatCompletionsOptions(); - chatCompletionsOptions.setPrompt(prompt); - claudeChatMessage.setCompletion(chatCompletionsOptions); - - buildSseEmitter(sseEmitter, uid); - - ClaudeAIEventSourceListener sourceListener = new ClaudeAIEventSourceListener(sseEmitter); - ClaudeAIClient.getInstance().streamCompletions(claudeChatMessage, sourceListener); - return sseEmitter; - } - - /** - * construct sseEmitter - * - * @param sseEmitter - * @param uid - * @return - * @throws IOException - */ - private SseEmitter buildSseEmitter(SseEmitter sseEmitter, String uid) throws IOException { - sseEmitter.send(SseEmitter.event().id(uid).name("connect successfully!!!!").data(LocalDateTime.now()).reconnectTime(3000)); - sseEmitter.onCompletion(() -> { - log.info(LocalDateTime.now() + ", uid#" + uid + ", on completion"); - }); - sseEmitter.onTimeout( - () -> log.info(LocalDateTime.now() + ", uid#" + uid + ", on timeout#" + sseEmitter.getTimeout())); - sseEmitter.onError( - throwable -> { - try { - log.info(LocalDateTime.now() + ", uid#" + "765431" + ", on error#" + throwable.toString()); - sseEmitter.send(SseEmitter.event().id("765431").name("发生异常!").data(throwable.getMessage()) - .reconnectTime(3000)); - } catch (IOException e) { - e.printStackTrace(); - } - } - ); - return sseEmitter; - } + String promptType = request.getPromptType(); - /** - * 构建schema参数 - * - * @param tableQueryParam - * @param tableNames - * @return - */ - private String buildTableColumn(TableQueryParam tableQueryParam, - List tableNames) { - if (CollectionUtils.isEmpty(tableNames)) { - return ""; - } - List schemaContent = Lists.newArrayList(); - try { - schemaContent = tableNames.stream().map(tableName -> { - tableQueryParam.setTableName(tableName); - return queryTableDdl(tableName, tableQueryParam); - }).collect(Collectors.toList()); - } catch (Exception exception) { - log.error("query table error, do nothing"); + if (PromptType.TEXT_GENERATION.getCode().equals(promptType) + || PromptType.TITLE_GENERATION.getCode().equals(promptType)) { + return ChatEvent.TABLES_NOT_NEEDED; } - return JSON.toJSONString(schemaContent); - } - - /** - * query table schema - * - * @param tableName - * @param request - * @return - */ - private String queryTableDdl(String tableName, TableQueryParam request) { - ShowCreateTableParam param = new ShowCreateTableParam(); - param.setTableName(tableName); - param.setDataSourceId(request.getDataSourceId()); - param.setDatabaseName(request.getDatabaseName()); - param.setSchemaName(request.getSchemaName()); - DataResult tableSchema = tableService.showCreateTable(param); - return tableSchema.getData(); - } - - /** - * 构建prompt - * - * @param queryRequest - * @return - */ - private String buildPrompt(ChatQueryRequest queryRequest) { - if (PromptType.TEXT_GENERATION.getCode().equals(queryRequest.getPromptType())) { - return queryRequest.getMessage(); + if (isRedisNl2Sql(request, promptType)) { + return ChatEvent.TABLES_NOT_NEEDED; } - // 查询schema信息 - String dataSourceType = queryDatabaseType(queryRequest); - String properties = ""; - if (CollectionUtils.isNotEmpty(queryRequest.getTableNames())) { - TableQueryParam queryParam = chatConverter.chat2tableQuery(queryRequest); - properties = buildTableColumn(queryParam, queryRequest.getTableNames()); - } else { - properties = mappingDatabaseSchema(queryRequest); + if (!hasTables && PromptType.SQL_OPTIMIZER.getCode().equals(promptType)) { + return ChatEvent.EXPLAIN_TABLES_NOT_SELECTED; } - String prompt = queryRequest.getMessage(); - String promptType = StringUtils.isBlank(queryRequest.getPromptType()) ? PromptType.NL_2_SQL.getCode() - : queryRequest.getPromptType(); - PromptType pType = EasyEnumUtils.getEnum(PromptType.class, promptType); - String ext = StringUtils.isNotBlank(queryRequest.getExt()) ? queryRequest.getExt() : ""; - String schemaProperty = StringUtils.isNotEmpty(properties) ? String.format( - "### 请根据以下table properties和SQL input%s. %s\n#\n### %s SQL tables, with their properties:\n#\n# " - + "%s\n#\n#\n### SQL input: %s", pType.getDescription(), ext, dataSourceType, - properties, prompt) : String.format("### 请根据以下SQL input%s. %s\n#\n### SQL input: %s", - pType.getDescription(), ext, prompt); - switch (pType) { - case SQL_2_SQL: - schemaProperty = StringUtils.isNotBlank(queryRequest.getDestSqlType()) ? String.format( - "%s\n#\n### 目标SQL类型: %s", schemaProperty, queryRequest.getDestSqlType()) : String.format( - "%s\n#\n### 目标SQL类型: %s", schemaProperty, dataSourceType); - default: - break; - } - String cleanedInput = schemaProperty.replaceAll("[\r\t]", ""); - return cleanedInput; + + return hasTables ? ChatEvent.TABLES_PROVIDED : ChatEvent.TABLES_NOT_PROVIDED; } - /** - * query chat2db apikey - * - * @return - */ - public String getApiKey() { - ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); - Config config = configService.find(RestAIClient.AI_SQL_SOURCE).getData(); - String aiSqlSource = AiSqlSourceEnum.CHAT2DBAI.getCode(); - // only sync for chat2db ai - if (Objects.isNull(config) || !aiSqlSource.equals(config.getContent())) { - return null; + private boolean isRedisNl2Sql(ChatQueryRequest request, String promptType) { + String effectivePromptType = StrUtil.isBlank(promptType) ? PromptType.NL_2_SQL.getCode() : promptType; + if (!PromptType.NL_2_SQL.getCode().equals(effectivePromptType)) { + return false; } - Config keyConfig = configService.find(Chat2dbAIClient.CHAT2DB_OPENAI_KEY).getData(); - if (Objects.isNull(keyConfig) || StringUtils.isBlank(keyConfig.getContent())) { - return null; + try { + return DataSourceTypeEnum.REDIS.getCode().equalsIgnoreCase( + dataSourceService.queryDatabaseType(request.getDataSourceId())); + } catch (Exception e) { + log.warn("[ChatController] Query database type failed, dataSourceId: {}, error: {}", + request.getDataSourceId(), e.getMessage()); + return false; } - return keyConfig.getContent(); } - /** - * query database type - * - * @param queryRequest - * @return - */ - public String queryDatabaseType(ChatQueryRequest queryRequest) { - // 查询schema信息 - DataResult dataResult = dataSourceService.queryById(queryRequest.getDataSourceId()); - String dataSourceType = dataResult.getData().getType(); - if (StringUtils.isBlank(dataSourceType)) { - dataSourceType = "MYSQL"; - } - return dataSourceType; - } + private void setupSseCallbacks(SseEmitter sseEmitter, String uid, ChatContext ctx) { + sseEmitter.onCompletion(() -> { + log.info("SSE completed for uid: {}", uid); + cleanupSession(uid); + }); - public String mappingDatabaseSchema(ChatQueryRequest queryRequest) { - String properties = ""; - String apiKey = getApiKey(); - if (StringUtils.isNotBlank(apiKey)) { - boolean res = gatewayClientService.checkInWhite(new WhiteListRequest(apiKey, WhiteListTypeEnum.VECTOR.getCode())).getData(); - if (res) { -// properties = queryDatabaseSchema(queryRequest) + querySchemaByEs(queryRequest); - properties = queryDatabaseSchema(queryRequest); - } - } - return properties; - } + sseEmitter.onTimeout(() -> { + log.info("SSE timeout for uid: {}", uid); + ctx.setCancelled(true); + cleanupSession(uid); + }); - /** - * query database schema - * - * @param queryRequest - * @return - * @throws IOException - */ - public String queryDatabaseSchema(ChatQueryRequest queryRequest) { - // request embedding - FastChatEmbeddingResponse response = distributeAIEmbedding(queryRequest.getMessage()); - List> contentVector = new ArrayList<>(); - if (Objects.isNull(response) || CollectionUtils.isEmpty(response.getData())) { - return ""; - } - contentVector.add(response.getData().get(0).getEmbedding()); - - // search embedding - TableSchemaRequest tableSchemaRequest = new TableSchemaRequest(); - tableSchemaRequest.setSchemaVector(contentVector); - tableSchemaRequest.setDataSourceId(queryRequest.getDataSourceId()); - tableSchemaRequest.setDatabaseName(queryRequest.getDatabaseName()); - tableSchemaRequest.setDataSourceSchema(queryRequest.getSchemaName()); - ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); - Config keyConfig = configService.find(Chat2dbAIClient.CHAT2DB_OPENAI_KEY).getData(); - if (Objects.isNull(keyConfig) || StringUtils.isBlank(keyConfig.getContent())) { - return ""; - } - tableSchemaRequest.setApiKey(keyConfig.getContent()); - try { - DataResult result = gatewayClientService.schemaVectorSearch(tableSchemaRequest); - List schemas = Lists.newArrayList(); - if (Objects.nonNull(result.getData()) && CollectionUtils.isNotEmpty(result.getData().getTableSchemas())) { - for(TableSchema data: result.getData().getTableSchemas()){ - schemas.add(data.getTableSchema()); - } - } - if (CollectionUtils.isEmpty(schemas)) { - return ""; - } - String res = JSON.toJSONString(schemas); - log.info("search vector result:{}", res); - return res; - } catch (Exception exception) { - log.error("query table error, do nothing"); - return ""; - } + sseEmitter.onError(throwable -> { + log.error("SSE error for uid: {}, error: {}", uid, throwable.getMessage()); + ctx.setCancelled(true); + cleanupSession(uid); + }); } - /** - * query database schema - * - * @param queryRequest - * @return - * @throws IOException - */ - public String querySchemaByEs(ChatQueryRequest queryRequest) { - // search embedding - EsTableSchemaRequest tableSchemaRequest = new EsTableSchemaRequest(); - tableSchemaRequest.setSearchKey(queryRequest.getMessage()); - tableSchemaRequest.setDataSourceId(queryRequest.getDataSourceId()); - tableSchemaRequest.setDatabaseName(queryRequest.getDatabaseName()); - tableSchemaRequest.setSchemaName(queryRequest.getSchemaName()); - ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); - Config keyConfig = configService.find(Chat2dbAIClient.CHAT2DB_OPENAI_KEY).getData(); - if (Objects.isNull(keyConfig) || StringUtils.isBlank(keyConfig.getContent())) { - return ""; - } - tableSchemaRequest.setApiKey(keyConfig.getContent()); - try { - DataResult result = gatewayClientService.schemaEsSearch(tableSchemaRequest); - List schemas = Lists.newArrayList(); - if (Objects.nonNull(result.getData()) && CollectionUtils.isNotEmpty(result.getData().getTableSchemas())) { - for(EsTableSchema data: result.getData().getTableSchemas()){ - schemas.add(data.getTableSchemaContent()); - } - } - if (CollectionUtils.isEmpty(schemas)) { - return ""; - } - String res = JSON.toJSONString(schemas); - log.info("search es result:{}", res); - return res; - } catch (Exception exception) { - log.error("query es table error, do nothing"); - return ""; - } + private void cleanupSession(String uid) { + activeSessions.remove(uid); + activeContexts.remove(uid); } - /** - * distribute embedding with different AI - * - * @return - */ - public FastChatEmbeddingResponse distributeAIEmbedding(String input) { - ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); - Config config = configService.find(RestAIClient.AI_SQL_SOURCE).getData(); - String aiSqlSource = config.getContent(); - if (Objects.isNull(aiSqlSource)) { - return null; + private ChatQueryRequest resolvePayload(ChatQueryRequest queryRequest) { + if (StrUtil.isBlank(queryRequest.getPayloadId())) { + return queryRequest; } - AiSqlSourceEnum aiSqlSourceEnum = AiSqlSourceEnum.getByName(aiSqlSource); - switch (Objects.requireNonNull(aiSqlSourceEnum)) { - case CHAT2DBAI: - return embeddingWithChat2dbAi(input); - case FASTCHATAI: - return embeddingWithFastChatAi(input); + + PendingChatPayload pendingPayload = pendingPayloads.remove(queryRequest.getPayloadId()); + if (pendingPayload == null) { + throw new ParamBusinessException("payloadId"); } - return null; - } - /** - * embedding with fast chat openai - * - * @param input - * @return - * @throws IOException - */ - private FastChatEmbeddingResponse embeddingWithFastChatAi(String input) { - FastChatEmbeddingResponse response = FastChatAIClient.getInstance().embeddings(input); - return response; + return pendingPayload.queryRequest(); } - /** - * embedding with open ai - * - * @param input - * @return - */ - private FastChatEmbeddingResponse embeddingWithChat2dbAi(String input) { - FastChatEmbeddingResponse embeddings = Chat2dbAIClient.getInstance().embeddings(input); - return embeddings; + private void cleanupExpiredPayloads() { + long now = System.currentTimeMillis(); + pendingPayloads.entrySet().removeIf(entry -> now - entry.getValue().createdAt() > PAYLOAD_TIMEOUT); } + private record PendingChatPayload(ChatQueryRequest queryRequest, long createdAt) { + } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/EmbeddingController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/EmbeddingController.java deleted file mode 100644 index c8c694309..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/EmbeddingController.java +++ /dev/null @@ -1,367 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai; - -import ai.chat2db.server.domain.api.enums.AiSqlSourceEnum; -import ai.chat2db.server.domain.api.model.Config; -import ai.chat2db.server.domain.api.param.ShowCreateTableParam; -import ai.chat2db.server.domain.api.param.TablePageQueryParam; -import ai.chat2db.server.domain.api.param.TableSelector; -import ai.chat2db.server.domain.api.param.TableVectorParam; -import ai.chat2db.server.domain.api.service.ConfigService; -import ai.chat2db.server.domain.api.service.TableService; -import ai.chat2db.server.tools.base.enums.WhiteListTypeEnum; -import ai.chat2db.server.tools.base.wrapper.result.ActionResult; -import ai.chat2db.server.tools.base.wrapper.result.DataResult; -import ai.chat2db.server.tools.base.wrapper.result.PageResult; -import ai.chat2db.server.tools.common.exception.ParamBusinessException; -import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; -import ai.chat2db.server.web.api.controller.ai.chat2db.client.Chat2dbAIClient; -import ai.chat2db.server.web.api.controller.ai.fastchat.embeddings.FastChatEmbeddingResponse; -import ai.chat2db.server.web.api.controller.ai.rest.client.RestAIClient; -import ai.chat2db.server.web.api.controller.rdb.converter.RdbWebConverter; -import ai.chat2db.server.web.api.controller.rdb.request.TableBriefQueryRequest; -import ai.chat2db.server.web.api.controller.rdb.request.TableMilvusQueryRequest; -import ai.chat2db.server.web.api.http.GatewayClientService; -import ai.chat2db.server.web.api.http.request.EsTableSchemaRequest; -import ai.chat2db.server.web.api.http.request.TableSchemaRequest; -import ai.chat2db.server.web.api.http.request.WhiteListRequest; -import ai.chat2db.server.web.api.util.ApplicationContextUtil; -import ai.chat2db.spi.model.Table; -import com.google.common.collect.Lists; -import jakarta.annotation.Resource; -import jakarta.validation.Valid; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.*; - -import java.io.IOException; -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -/** - * @author moji - */ -@RestController -@ConnectionInfoAspect -@RequestMapping("/api/ai/embedding") -@Slf4j -public class EmbeddingController extends ChatController { - - - @Resource - private GatewayClientService gatewayClientService; - - @Autowired - private RdbWebConverter rdbWebConverter; - - @Autowired - private TableService tableService; - - /** - * check if in white list - */ - @GetMapping("/white/check") - public DataResult checkInWhite(WhiteListRequest request) { - request.setWhiteType(WhiteListTypeEnum.VECTOR.getCode()); - if (StringUtils.isBlank(request.getApiKey())) { - return DataResult.of(false); - } - try { - DataResult res = gatewayClientService.checkInWhite(request); - } catch (Exception ex) { - log.error("checkInWhite error", ex); - } - return DataResult.of(false); - } - - /** - * save datasource embeddings - * - * @param request - * @return - * @throws IOException - */ - @PostMapping("/datasource") - @CrossOrigin - public ActionResult embeddings(@Valid TableMilvusQueryRequest request) - throws Exception { - - // query tables - request.setPageNo(1); - request.setPageSize(1000); - TablePageQueryParam queryParam = rdbWebConverter.tablePageRequest2param(request); - TableSelector tableSelector = new TableSelector(); - tableSelector.setColumnList(false); - tableSelector.setIndexList(false); - PageResult
tableDTOPageResult = tableService.pageQuery(queryParam, tableSelector); - - List
tables = tableDTOPageResult.getData(); - if (CollectionUtils.isEmpty(tables)) { - return ActionResult.isSuccess(); - } - - String tableName = tables.get(0).getName(); - String tableSchema = queryTableDdl(tableName, request); - - if (StringUtils.isBlank(tableSchema)) { - throw new ParamBusinessException("tableSchema is empty"); - } - - // save first table embedding - TableSchemaRequest tableSchemaRequest = new TableSchemaRequest(); - tableSchemaRequest.setDataSourceId(request.getDataSourceId()); - tableSchemaRequest.setApiKey(request.getApikey()); - tableSchemaRequest.setDeleteBeforeInsert(true); - tableSchemaRequest.setDataSourceSchema(request.getSchemaName()); - tableSchemaRequest.setDatabaseName(request.getDatabaseName()); - - saveTableEmbedding(tableSchema, tableSchemaRequest); - - // save other table embedding - tableSchemaRequest.setDeleteBeforeInsert(false); - for (int i = 1; i < tables.size(); i++) { - tableName = tables.get(i).getName(); - tableSchema = queryTableDdl(tableName, request); - if (StringUtils.isBlank(tableSchema)) { - continue; - } - saveTableEmbedding(tableSchema, tableSchemaRequest); - } - - // query all the tables - Long totalTableCount = tableDTOPageResult.getTotal(); - Integer pageSize = queryParam.getPageSize(); - if (pageSize < totalTableCount) { - for (int i = 2; i < totalTableCount/pageSize + 1; i++) { - queryParam.setPageNo(i); - tableDTOPageResult = tableService.pageQuery(queryParam, tableSelector); - tables = tableDTOPageResult.getData(); - for (Table table : tables) { - tableName = table.getName(); - tableSchema = queryTableDdl(tableName, request); - if (StringUtils.isBlank(tableSchema)) { - continue; - } - saveTableEmbedding(tableSchema, tableSchemaRequest); - } - } - } - - return ActionResult.isSuccess(); - } - - /** - * save datasource schema - * - * @param request - * @return - * @throws IOException - */ - @PostMapping("/datasource/es") - @CrossOrigin - public ActionResult es(@Valid EsTableSchemaRequest request) - throws Exception { - - // query tables - TablePageQueryParam queryParam = rdbWebConverter.schemaReq2page(request); - TableSelector tableSelector = new TableSelector(); - tableSelector.setColumnList(false); - tableSelector.setIndexList(false); - queryParam.setPageNo(1); - queryParam.setPageSize(1000); - PageResult
tableDTOPageResult = tableService.pageQuery(queryParam, tableSelector); - - List
tables = tableDTOPageResult.getData(); - if (CollectionUtils.isEmpty(tables)) { - return ActionResult.isSuccess(); - } - String tableName = tables.get(0).getName(); - String tableSchema = queryTableDdlByEs(tableName, request); - request.setTableName(tableName); - request.setTableSchemaContent(tableSchema); - - if (StringUtils.isBlank(tableSchema)) { - throw new ParamBusinessException("tableSchema is empty"); - } - - // save first table embedding - request.setDeleteBeforeInsert(true); - saveTableEs(request); - - // save other table embedding - request.setDeleteBeforeInsert(false); - for (int i = 1; i < tables.size(); i++) { - tableName = tables.get(i).getName(); - tableSchema = queryTableDdlByEs(tableName, request); - if (StringUtils.isBlank(tableSchema)) { - continue; - } - request.setTableName(tableName); - request.setTableSchemaContent(tableSchema); - saveTableEs(request); - } - - // query all the tables - Long totalTableCount = tableDTOPageResult.getTotal(); - Integer pageSize = queryParam.getPageSize(); - if (pageSize < totalTableCount) { - for (int i = 2; i < totalTableCount/pageSize + 1; i++) { - queryParam.setPageNo(i); - tableDTOPageResult = tableService.pageQuery(queryParam, tableSelector); - tables = tableDTOPageResult.getData(); - for (Table table : tables) { - tableName = table.getName(); - tableSchema = queryTableDdlByEs(tableName, request); - if (StringUtils.isBlank(tableSchema)) { - continue; - } - request.setTableName(tableName); - request.setTableSchemaContent(tableSchema); - saveTableEs(request); - } - } - } - - return ActionResult.isSuccess(); - } - - /** - * sync table vector - * - * @param param - */ - public void syncTableVector(TableBriefQueryRequest param) throws Exception { - TableVectorParam vectorParam = rdbWebConverter.param2param(param); - if (Objects.isNull(vectorParam.getDataSourceId())) { - return; - } - if (StringUtils.isBlank(vectorParam.getDatabase()) && StringUtils.isBlank(vectorParam.getSchema())) { - return; - } - - String apiKey = getApiKey(); - if (StringUtils.isBlank(apiKey)) { - return; - } - - TableMilvusQueryRequest request = rdbWebConverter.request2request(param); - request.setApikey(apiKey); - - vectorParam.setApiKey(apiKey); - DataResult result = tableService.checkTableVector(vectorParam); - if (result.getData()) { - return; - } - - // check if in white list - boolean res = gatewayClientService.checkInWhite(new WhiteListRequest(apiKey, WhiteListTypeEnum.VECTOR.getCode())).getData(); - if (!res) { - return; - } - - embeddings(request); - - tableService.saveTableVector(vectorParam); - } - - /** - * save table embedding - * - * @param tableSchema - * @param tableSchemaRequest - * @throws Exception - */ - private void saveTableEmbedding(String tableSchema, TableSchemaRequest tableSchemaRequest) throws Exception{ - List schemaList = Lists.newArrayList(tableSchema); - tableSchemaRequest.setSchemaList(schemaList); - - List> contentVector = new ArrayList<>(); - for(String str : schemaList){ - // request embedding - FastChatEmbeddingResponse response = distributeAIEmbedding(str); - if(response == null){ - throw new ParamBusinessException(); - } - contentVector.add(response.getData().get(0).getEmbedding()); - } - if (CollectionUtils.isEmpty(contentVector)) { - throw new ParamBusinessException(); - } - tableSchemaRequest.setSchemaVector(contentVector); - - // save table embedding - gatewayClientService.schemaVectorSave(tableSchemaRequest); - } - - /** - * sync table vector - * - * @param param - */ - public void syncTableEs(TableBriefQueryRequest param) throws Exception { - EsTableSchemaRequest esParam = rdbWebConverter.req2req(param); - if (Objects.isNull(esParam.getDataSourceId())) { - return; - } - if (StringUtils.isBlank(esParam.getDatabaseName()) && StringUtils.isBlank(esParam.getSchemaName())) { - return; - } - - String apiKey = getApiKey(); - if (StringUtils.isBlank(apiKey)) { - return; - } - - esParam.setApiKey(apiKey); - es(esParam); - } - - /** - * save table schema - * - * @param tableSchemaRequest - * @throws Exception - */ - private void saveTableEs(EsTableSchemaRequest tableSchemaRequest) throws Exception{ - // save table es - gatewayClientService.schemaEsSave(tableSchemaRequest); - } - - /** - * query table schema - * - * @param tableName - * @param request - * @return - */ - private String queryTableDdl(String tableName, TableBriefQueryRequest request) { - ShowCreateTableParam param = new ShowCreateTableParam(); - param.setTableName(tableName); - param.setDataSourceId(request.getDataSourceId()); - param.setDatabaseName(request.getDatabaseName()); - param.setSchemaName(request.getSchemaName()); - DataResult tableSchema = tableService.showCreateTable(param); - return tableSchema.getData(); - } - - /** - * query table schema - * - * @param tableName - * @param request - * @return - */ - private String queryTableDdlByEs(String tableName, EsTableSchemaRequest request) { - ShowCreateTableParam param = new ShowCreateTableParam(); - param.setTableName(tableName); - param.setDataSourceId(request.getDataSourceId()); - param.setDatabaseName(request.getDatabaseName()); - param.setSchemaName(request.getSchemaName()); - DataResult tableSchema = tableService.showCreateTable(param); - return tableSchema.getData(); - } - -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/KnowledgeController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/KnowledgeController.java deleted file mode 100644 index 6ff16ee09..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/KnowledgeController.java +++ /dev/null @@ -1,134 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai; - -import ai.chat2db.server.tools.base.wrapper.result.ActionResult; -import ai.chat2db.server.tools.base.wrapper.result.DataResult; -import ai.chat2db.server.tools.common.exception.ParamBusinessException; -import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; -import ai.chat2db.server.web.api.controller.ai.DocParser.AbstractParser; -import ai.chat2db.server.web.api.controller.ai.DocParser.PdfParse; -import ai.chat2db.server.web.api.controller.ai.enums.PromptType; -import ai.chat2db.server.web.api.controller.ai.fastchat.embeddings.FastChatEmbeddingResponse; -import ai.chat2db.server.web.api.controller.ai.request.ChatQueryRequest; -import ai.chat2db.server.web.api.http.GatewayClientService; -import ai.chat2db.server.web.api.http.model.Knowledge; -import ai.chat2db.server.web.api.http.request.KnowledgeRequest; -import ai.chat2db.server.web.api.http.response.KnowledgeResponse; -import cn.hutool.core.util.StrUtil; -import com.alibaba.fastjson2.JSON; -import jakarta.annotation.Resource; -import jakarta.servlet.http.HttpServletRequest; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang3.StringUtils; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; -import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; - -import java.io.IOException; -import java.math.BigDecimal; -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -/** - * @author moji - */ -@RestController -@ConnectionInfoAspect -@RequestMapping("/api/ai/knowledge") -@Slf4j -public class KnowledgeController extends ChatController { - - - /** - * chat的超时时间 - */ - private static final Long CHAT_TIMEOUT = Duration.ofMinutes(50).toMillis(); - - - @Resource - private GatewayClientService gatewayClientService; - - /** - * save knowledge from pdf file - * - * @param file - * @return - * @throws IOException - */ - @PostMapping("/embeddings") - @CrossOrigin - public ActionResult embeddings(MultipartFile file, HttpServletRequest request) - throws Exception { - AbstractParser pdfParse = new PdfParse(); - List sentenceList = pdfParse.parse(file.getInputStream()); - - List contentWordCount = new ArrayList<>(); - List> contentVector = new ArrayList<>(); - for(String str : sentenceList){ - contentWordCount.add(str.length()); - - // request embedding - FastChatEmbeddingResponse response = distributeAIEmbedding(str); - if(response == null){ - continue; - } - contentVector.add(response.getData().get(0).getEmbedding()); - } - - KnowledgeRequest knowledgeRequest = new KnowledgeRequest(); - knowledgeRequest.setContentVector(contentVector); - knowledgeRequest.setSentenceList(sentenceList); - // save knowledge embedding - ActionResult actionResult = gatewayClientService.knowledgeVectorSave(knowledgeRequest); - return actionResult; - } - - /** - * search knowledge - * - * @param queryRequest - * @return - * @throws IOException - */ - @GetMapping("/search") - @CrossOrigin - public SseEmitter search(ChatQueryRequest queryRequest, @RequestHeader Map headers) - throws Exception { - // request embedding - FastChatEmbeddingResponse response = distributeAIEmbedding(queryRequest.getMessage()); - List> contentVector = new ArrayList<>(); - contentVector.add(response.getData().get(0).getEmbedding()); - - // search embedding - KnowledgeRequest knowledgeRequest = new KnowledgeRequest(); - knowledgeRequest.setContentVector(contentVector); - DataResult result = gatewayClientService.knowledgeVectorSearch(knowledgeRequest); - queryRequest.setPromptType(PromptType.TEXT_GENERATION.getCode()); - String prompt = queryRequest.getMessage(); - if (CollectionUtils.isNotEmpty(result.getData().getKnowledgeList())) { - List contents = new ArrayList<>(); - for(Knowledge data: result.getData().getKnowledgeList()){ - contents.add(data.getContent()); - } - - prompt = String.format("基于%s。请回答%s。", JSON.toJSONString(contents), prompt); - queryRequest.setMessage(prompt); - } - - // chat with AI - SseEmitter sseEmitter = new SseEmitter(CHAT_TIMEOUT); - String uid = headers.get("uid"); - if (StrUtil.isBlank(uid)) { - throw new ParamBusinessException("uid"); - } - - if (StringUtils.isBlank(queryRequest.getMessage())) { - throw new ParamBusinessException("message"); - } - - return distributeAISql(queryRequest, sseEmitter, uid); - } - -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/TextGenerationController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/TextGenerationController.java deleted file mode 100644 index 0c6180667..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/TextGenerationController.java +++ /dev/null @@ -1,92 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai; - -import ai.chat2db.server.tools.common.exception.ParamBusinessException; -import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; -import ai.chat2db.server.web.api.controller.ai.enums.PromptType; -import ai.chat2db.server.web.api.controller.ai.request.ChatQueryRequest; -import ai.chat2db.server.web.api.http.GatewayClientService; -import cn.hutool.core.util.StrUtil; -import jakarta.annotation.Resource; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; - -import java.io.IOException; -import java.time.Duration; -import java.util.Map; - -/** - * @author moji - */ -@RestController -@ConnectionInfoAspect -@RequestMapping("/api/ai/text/generation") -@Slf4j -public class TextGenerationController extends ChatController { - - - /** - * chat timeout time - */ - private static final Long CHAT_TIMEOUT = Duration.ofMinutes(50).toMillis(); - - - @Resource - private GatewayClientService gatewayClientService; - - /** - * sql auto complete - * - * @param queryRequest - * @return - * @throws IOException - */ - @GetMapping("/prompt") - @CrossOrigin - public SseEmitter prompt(ChatQueryRequest queryRequest, @RequestHeader Map headers) - throws Exception { - queryRequest.setPromptType(PromptType.TEXT_GENERATION.getCode()); - - String promptTemplate = "### Instructions:\n" + - "Your task is generate a SQL query according to the prompt %s.\n" + - "Adhere to these rules:\n" + - "- **Deliberately go through the prompt and database schema word by word** to appropriately answer the question\n" + - "- **Use Table Aliases** to prevent ambiguity. For example, `SELECT table1.col1, table2.col1 FROM table1 JOIN table2 ON table1.id = table2.id`.\n" + - "\n" + - "### Input:\n" + - "Generate a SQL query according to the prompt `%s`.\n" + - "%s\n" + - "\n" + - "### Response:\n" + - "Based on your instructions, here is the SQL query I have generated to complete the prompt `{%s}`:\n" + - "```sql"; - - // query database schema info - String databaseType = queryDatabaseType(queryRequest); - String schemas = queryDatabaseSchema(queryRequest); - if (StringUtils.isNotBlank(schemas)) { - databaseType = String.format(", given a %s database schema", databaseType); - schemas = String.format("This query will run on a database whose schema is represented in this string:\n$s", schemas); - } else { - databaseType = ""; - schemas = ""; - } - String prompt = String.format(promptTemplate, databaseType, queryRequest.getMessage(), schemas, queryRequest.getMessage()); - queryRequest.setMessage(prompt); - - // chat with AI - SseEmitter sseEmitter = new SseEmitter(CHAT_TIMEOUT); - String uid = headers.get("uid"); - if (StrUtil.isBlank(uid)) { - throw new ParamBusinessException("uid"); - } - - if (StringUtils.isBlank(queryRequest.getMessage())) { - throw new ParamBusinessException("message"); - } - - return distributeAISql(queryRequest, sseEmitter, uid); - } - -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/client/AzureOpenAIClient.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/client/AzureOpenAIClient.java deleted file mode 100644 index 60e38f772..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/client/AzureOpenAIClient.java +++ /dev/null @@ -1,89 +0,0 @@ - -package ai.chat2db.server.web.api.controller.ai.azure.client; - -import ai.chat2db.server.domain.api.model.Config; -import ai.chat2db.server.domain.api.service.ConfigService; -import ai.chat2db.server.web.api.util.ApplicationContextUtil; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; - -/** - * @author jipengfei - * @version : OpenAIClient.java - */ -@Slf4j -public class AzureOpenAIClient { - - /** - * AZURE OPENAI KEY - */ - public static final String AZURE_CHATGPT_API_KEY = "azure.chatgpt.apiKey"; - - /** - * AZURE OPENAI ENDPOINT - */ - public static final String AZURE_CHATGPT_ENDPOINT = "azure.chatgpt.endpoint"; - - /** - * AZURE OPENAI DEPLOYMENT ID - */ - public static final String AZURE_CHATGPT_DEPLOYMENT_ID = "azure.chatgpt.deployment.id"; - - private static AzureOpenAiStreamClient OPEN_AI_CLIENT; - private static String apiKey; - - public static AzureOpenAiStreamClient getInstance() { - if (OPEN_AI_CLIENT != null) { - return OPEN_AI_CLIENT; - } else { - return singleton(); - } - } - - private static AzureOpenAiStreamClient singleton() { - if (OPEN_AI_CLIENT == null) { - synchronized (AzureOpenAIClient.class) { - if (OPEN_AI_CLIENT == null) { - refresh(); - } - } - } - return OPEN_AI_CLIENT; - } - - public static void refresh() { - String key = ""; - String apiEndpoint = ""; - String deployId = "gpt-3.5-turbo"; - ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); - Config apiHostConfig = configService.find(AZURE_CHATGPT_ENDPOINT).getData(); - if (apiHostConfig != null && StringUtils.isNotBlank(apiHostConfig.getContent())) { - apiEndpoint = apiHostConfig.getContent(); - } - Config config = configService.find(AZURE_CHATGPT_API_KEY).getData(); - if (config != null && StringUtils.isNotBlank(config.getContent())) { - key = config.getContent(); - } - Config deployConfig = configService.find(AZURE_CHATGPT_DEPLOYMENT_ID).getData(); - if (deployConfig != null && StringUtils.isNotBlank(deployConfig.getContent())) { - deployId = deployConfig.getContent(); - } - log.info("refresh azure openai apikey:{}", maskApiKey(key)); - OPEN_AI_CLIENT = AzureOpenAiStreamClient.builder().apiKey(key).endpoint(apiEndpoint).deployId(deployId) - .build(); - apiKey = key; - } - - private static String maskApiKey(String input) { - if (input == null) { - return input; - } - - StringBuilder maskedString = new StringBuilder(input); - for (int i = input.length() / 2; i < input.length() / 2; i++) { - maskedString.setCharAt(i, '*'); - } - return maskedString.toString(); - } - -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/client/AzureOpenAiStreamClient.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/client/AzureOpenAiStreamClient.java deleted file mode 100644 index 338f5b1c1..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/client/AzureOpenAiStreamClient.java +++ /dev/null @@ -1,190 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.azure.client; - -import java.util.List; -import java.util.Objects; -import java.util.concurrent.TimeUnit; - -import ai.chat2db.server.tools.common.exception.ParamBusinessException; -import ai.chat2db.server.web.api.controller.ai.azure.interceptor.AzureHeaderAuthorizationInterceptor; -import ai.chat2db.server.web.api.controller.ai.azure.model.AzureChatCompletionsOptions; -import ai.chat2db.server.web.api.controller.ai.azure.model.AzureChatMessage; -import cn.hutool.http.ContentType; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import okhttp3.MediaType; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.sse.EventSource; -import okhttp3.sse.EventSourceListener; -import okhttp3.sse.EventSources; -import org.apache.commons.collections4.CollectionUtils; -import org.jetbrains.annotations.NotNull; - -/** - * 自定义AI接口client - * - * @author moji - */ -@Slf4j -public class AzureOpenAiStreamClient { - - /** - * apikey - */ - @Getter - @NotNull - private String apiKey; - - /** - * endpoint - */ - @Getter - @NotNull - private String endpoint; - - /** - * deployId - */ - @Getter - private String deployId; - - /** - * okHttpClient - */ - @Getter - private OkHttpClient okHttpClient; - - - /** - * @param builder - */ - private AzureOpenAiStreamClient(Builder builder) { - this.apiKey = builder.apiKey; - this.endpoint = builder.endpoint; - this.deployId = builder.deployId; - if (Objects.isNull(builder.okHttpClient)) { - builder.okHttpClient = this.okHttpClient(); - } - okHttpClient = builder.okHttpClient; - } - - /** - * okhttpclient - */ - private OkHttpClient okHttpClient() { - OkHttpClient okHttpClient = new OkHttpClient - .Builder() - .addInterceptor(new AzureHeaderAuthorizationInterceptor(this.apiKey)) - .connectTimeout(10, TimeUnit.SECONDS) - .writeTimeout(50, TimeUnit.SECONDS) - .readTimeout(50, TimeUnit.SECONDS) - .build(); - return okHttpClient; - } - - /** - * 构造 - * - * @return - */ - public static AzureOpenAiStreamClient.Builder builder() { - return new AzureOpenAiStreamClient.Builder(); - } - - public static final class Builder { - private String apiKey; - - private String endpoint; - - private String deployId; - - /** - * 自定义OkhttpClient - */ - private OkHttpClient okHttpClient; - - public Builder() { - } - - public AzureOpenAiStreamClient.Builder apiKey(String apiKeyValue) { - this.apiKey = apiKeyValue; - return this; - } - - /** - * @param endpointValue - * @return - */ - public AzureOpenAiStreamClient.Builder endpoint(String endpointValue) { - this.endpoint = endpointValue; - return this; - } - - /** - * @param deployIdValue - * @return - */ - public AzureOpenAiStreamClient.Builder deployId(String deployIdValue) { - this.deployId = deployIdValue; - return this; - } - - public AzureOpenAiStreamClient.Builder okHttpClient(OkHttpClient val) { - this.okHttpClient = val; - return this; - } - - public AzureOpenAiStreamClient build() { - return new AzureOpenAiStreamClient(this); - } - - } - - /** - * 问答接口 stream 形式 - * - * @param chatMessages - * @param eventSourceListener - */ - public void streamCompletions(List chatMessages, EventSourceListener eventSourceListener) { - if (CollectionUtils.isEmpty(chatMessages)) { - log.error("param error:Azure Prompt cannot be empty"); - throw new ParamBusinessException("prompt"); - } - if (Objects.isNull(eventSourceListener)) { - log.error("param error:AzureEventSourceListener cannot be empty"); - throw new ParamBusinessException(); - } - log.info("Azure Open AI, prompt:{}", chatMessages.get(chatMessages.size() - 1).getContent()); - try { - - AzureChatCompletionsOptions chatCompletionsOptions = new AzureChatCompletionsOptions(chatMessages); - chatCompletionsOptions.setStream(true); - chatCompletionsOptions.setModel(this.deployId); - - EventSource.Factory factory = EventSources.createFactory(this.okHttpClient); - ObjectMapper mapper = new ObjectMapper(); - mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - String requestBody = mapper.writeValueAsString(chatCompletionsOptions); - if (!endpoint.endsWith("/")) { - endpoint = endpoint + "/"; - } - String url = this.endpoint + "openai/deployments/"+ deployId + "/chat/completions?api-version=2023-05-15"; - Request request = new Request.Builder() - .url(url) - .post(RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), requestBody)) - .build(); - //创建事件 - EventSource eventSource = factory.newEventSource(request, eventSourceListener); - log.info("finish invoking azure ai"); - } catch (Exception e) { - log.error("azure ai error", e); - eventSourceListener.onFailure(null, e, null); - throw new ParamBusinessException(); - } - } - -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/interceptor/AzureHeaderAuthorizationInterceptor.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/interceptor/AzureHeaderAuthorizationInterceptor.java deleted file mode 100644 index 0a07325e0..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/interceptor/AzureHeaderAuthorizationInterceptor.java +++ /dev/null @@ -1,42 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.azure.interceptor; - -import java.io.IOException; -import java.util.List; -import java.util.Map; -import java.util.function.Function; -import java.util.stream.Collectors; - -import cn.hutool.core.util.RandomUtil; -import cn.hutool.http.ContentType; -import cn.hutool.http.Header; -import lombok.Getter; -import okhttp3.Interceptor; -import okhttp3.Request; -import okhttp3.Response; - -/** - * 描述:请求增加header apikey - * - * @author grt - * @since 2023-03-23 - */ -@Getter -public class AzureHeaderAuthorizationInterceptor implements Interceptor { - - private String apiKey; - - public AzureHeaderAuthorizationInterceptor(String apiKey) { - this.apiKey = apiKey; - } - - @Override - public Response intercept(Chain chain) throws IOException { - Request original = chain.request(); - Request request = original.newBuilder() - .header("api-key", apiKey) - .header(Header.CONTENT_TYPE.getValue(), ContentType.JSON.getValue()) - .method(original.method(), original.body()) - .build(); - return chain.proceed(request); - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/listener/AzureOpenAIEventSourceListener.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/listener/AzureOpenAIEventSourceListener.java deleted file mode 100644 index 4488bd6b8..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/listener/AzureOpenAIEventSourceListener.java +++ /dev/null @@ -1,148 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.azure.listener; - -import java.io.IOException; -import java.util.Objects; - -import ai.chat2db.server.web.api.controller.ai.azure.model.AzureChatChoice; -import ai.chat2db.server.web.api.controller.ai.azure.model.AzureChatCompletions; -import ai.chat2db.server.web.api.controller.ai.azure.model.AzureChatMessage; -import ai.chat2db.server.web.api.controller.ai.azure.model.AzureCompletionsUsage; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.unfbx.chatgpt.entity.chat.Message; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import okhttp3.Response; -import okhttp3.ResponseBody; -import okhttp3.sse.EventSource; -import okhttp3.sse.EventSourceListener; -import org.apache.commons.lang3.StringUtils; -import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; - -/** - * 描述:OpenAIEventSourceListener - * - * @author https:www.unfbx.com - * @date 2023-02-22 - */ -@Slf4j -public class AzureOpenAIEventSourceListener extends EventSourceListener { - - private SseEmitter sseEmitter; - - private ObjectMapper mapper = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); - - public AzureOpenAIEventSourceListener(SseEmitter sseEmitter) { - this.sseEmitter = sseEmitter; - } - - /** - * {@inheritDoc} - */ - @Override - public void onOpen(EventSource eventSource, Response response) { - log.info("AzureOpenAI建立sse连接..."); - } - - /** - * {@inheritDoc} - */ - @SneakyThrows - @Override - public void onEvent(EventSource eventSource, String id, String type, String data) { - log.info("AzureOpenAI返回数据:{}", data); - if (data.equals("[DONE]")) { - log.info("AzureOpenAI返回数据结束了"); - sseEmitter.send(SseEmitter.event() - .id("[DONE]") - .data("[DONE]") - .reconnectTime(3000)); - sseEmitter.complete(); - return; - } - - AzureChatCompletions chatCompletions = mapper.readValue(data, AzureChatCompletions.class); - String text = ""; - log.info("Model ID={} is created at {}.", chatCompletions.getId(), - chatCompletions.getCreated()); - for (AzureChatChoice choice : chatCompletions.getChoices()) { - AzureChatMessage message = choice.getDelta(); - if (message != null) { - log.info("Index: {}, Chat Role: {}", choice.getIndex(), message.getRole()); - if (message.getContent() != null) { - text = message.getContent(); - } - } - } - - AzureCompletionsUsage usage = chatCompletions.getUsage(); - if (usage != null) { - log.info( - "Usage: number of prompt token is {}, number of completion token is {}, and number of total " - + "tokens in request and response is {}.%n", usage.getPromptTokens(), - usage.getCompletionTokens(), usage.getTotalTokens()); - } - - Message message = new Message(); - message.setContent(text); - sseEmitter.send(SseEmitter.event() - .id(null) - .data(message) - .reconnectTime(3000)); - } - - @Override - public void onClosed(EventSource eventSource) { - try { - sseEmitter.send(SseEmitter.event() - .id("[DONE]") - .data("[DONE]")); - } catch (IOException e) { - throw new RuntimeException(e); - } - sseEmitter.complete(); - log.info("AzureOpenAI close sse connection..."); - } - - @Override - public void onFailure(EventSource eventSource, Throwable t, Response response) { - try { - if (Objects.isNull(response)) { - String message = t.getMessage(); - Message sseMessage = new Message(); - sseMessage.setContent(message); - sseEmitter.send(SseEmitter.event() - .id("[ERROR]") - .data(sseMessage)); - sseEmitter.send(SseEmitter.event() - .id("[DONE]") - .data("[DONE]")); - sseEmitter.complete(); - return; - } - ResponseBody body = response.body(); - String bodyString = Objects.nonNull(t) ? t.getMessage() : ""; - if (Objects.nonNull(body)) { - bodyString = body.string(); - if (StringUtils.isBlank(bodyString) && Objects.nonNull(t)) { - bodyString = t.getMessage(); - } - log.error("Azure OpenAI sse response:{}", bodyString); - } else { - log.error("Azure OpenAI sse response:{},error:{}", response, t); - } - eventSource.cancel(); - Message message = new Message(); - message.setContent("Azure OpenAI error:" + bodyString); - sseEmitter.send(SseEmitter.event() - .id("[ERROR]") - .data(message)); - sseEmitter.send(SseEmitter.event() - .id("[DONE]") - .data("[DONE]")); - sseEmitter.complete(); - } catch (Exception exception) { - log.error("Azure OpenAI发送数据异常:", exception); - } - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureChatChoice.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureChatChoice.java deleted file mode 100644 index e1cc20a61..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureChatChoice.java +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -// Code generated by Microsoft (R) AutoRest Code Generator. -package ai.chat2db.server.web.api.controller.ai.azure.model; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; - -/** - * The representation of a single prompt completion as part of an overall chat completions request. Generally, `n` - * choices are generated per provided prompt with a default value of 1. Token limits and other settings may limit the - * number of choices generated. - */ -@Data -public final class AzureChatChoice { - - /* - * The chat message for a given chat completions prompt. - */ - @JsonProperty(value = "message") - private AzureChatMessage message; - - /* - * The ordered index associated with this chat completions choice. - */ - @JsonProperty(value = "index") - private int index; - - /* - * The reason that this chat completions choice completed its generated. - */ - @JsonProperty(value = "finish_reason") - private AzureCompletionsFinishReason finishReason; - - /* - * The delta message content for a streaming response. - */ - @JsonProperty(value = "delta") - private AzureChatMessage delta; - - /** - * Creates an instance of ChatChoice class. - * - * @param index the index value to set. - * @param finishReason the finishReason value to set. - */ - @JsonCreator - private AzureChatChoice( - @JsonProperty(value = "index") int index, - @JsonProperty(value = "finish_reason") AzureCompletionsFinishReason finishReason) { - this.index = index; - this.finishReason = finishReason; - } - - /** - * Get the message property: The chat message for a given chat completions prompt. - * - * @return the message value. - */ - public AzureChatMessage getMessage() { - return this.message; - } - - /** - * Get the index property: The ordered index associated with this chat completions choice. - * - * @return the index value. - */ - public int getIndex() { - return this.index; - } - - /** - * Get the finishReason property: The reason that this chat completions choice completed its generated. - * - * @return the finishReason value. - */ - public AzureCompletionsFinishReason getFinishReason() { - return this.finishReason; - } - - /** - * Get the delta property: The delta message content for a streaming response. - * - * @return the delta value. - */ - public AzureChatMessage getDelta() { - return this.delta; - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureChatCompletions.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureChatCompletions.java deleted file mode 100644 index f7e8356f7..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureChatCompletions.java +++ /dev/null @@ -1,96 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.azure.model; - -import java.util.List; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; - -@Data -public class AzureChatCompletions { - - /* - * A unique identifier associated with this chat completions response. - */ - private String id; - - /* - * The first timestamp associated with generation activity for this completions response, - * represented as seconds since the beginning of the Unix epoch of 00:00 on 1 Jan 1970. - */ - private int created; - - /* - * The collection of completions choices associated with this completions response. - * Generally, `n` choices are generated per provided prompt with a default value of 1. - * Token limits and other settings may limit the number of choices generated. - */ - @JsonProperty(value = "choices") - private List choices; - - /* - * Usage information for tokens processed and generated as part of this completions operation. - */ - private AzureCompletionsUsage usage; - - /** - * Creates an instance of ChatCompletions class. - * - * @param id the id value to set. - * @param created the created value to set. - * @param choices the choices value to set. - * @param usage the usage value to set. - */ - @JsonCreator - private AzureChatCompletions( - @JsonProperty(value = "id") String id, - @JsonProperty(value = "created") int created, - @JsonProperty(value = "choices") List choices, - @JsonProperty(value = "usage") AzureCompletionsUsage usage) { - this.id = id; - this.created = created; - this.choices = choices; - this.usage = usage; - } - - /** - * Get the id property: A unique identifier associated with this chat completions response. - * - * @return the id value. - */ - public String getId() { - return this.id; - } - - /** - * Get the created property: The first timestamp associated with generation activity for this completions response, - * represented as seconds since the beginning of the Unix epoch of 00:00 on 1 Jan 1970. - * - * @return the created value. - */ - public int getCreated() { - return this.created; - } - - /** - * Get the choices property: The collection of completions choices associated with this completions response. - * Generally, `n` choices are generated per provided prompt with a default value of 1. Token limits and other - * settings may limit the number of choices generated. - * - * @return the choices value. - */ - public List getChoices() { - return this.choices; - } - - /** - * Get the usage property: Usage information for tokens processed and generated as part of this completions - * operation. - * - * @return the usage value. - */ - public AzureCompletionsUsage getUsage() { - return this.usage; - } - -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureChatCompletionsOptions.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureChatCompletionsOptions.java deleted file mode 100644 index 1d6198e57..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureChatCompletionsOptions.java +++ /dev/null @@ -1,394 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -// Code generated by Microsoft (R) AutoRest Code Generator. -package ai.chat2db.server.web.api.controller.ai.azure.model; - -import java.util.List; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; - -/** - * The configuration information for a chat completions request. Completions support a wide variety of tasks and - * generate text that continues from or "completes" provided prompt data. - */ -@Data -public final class AzureChatCompletionsOptions { - - /* - * The collection of context messages associated with this chat completions request. - * Typical usage begins with a chat message for the System role that provides instructions for - * the behavior of the assistant, followed by alternating messages between the User and - * Assistant roles. - */ - private List messages; - - ///* - // * The maximum number of tokens to generate. - // */ - ////@JsonProperty(value = "max_tokens") - //private Integer maxTokens; - // - ///* - // * The sampling temperature to use that controls the apparent creativity of generated completions. - // * Higher values will make output more random while lower values will make results more focused - // * and deterministic. - // * It is not recommended to modify temperature and top_p for the same completions request as the - // * interaction of these two settings is difficult to predict. - // */ - ////@JsonProperty(value = "temperature") - //private Double temperature; - // - ///* - // * An alternative to sampling with temperature called nucleus sampling. This value causes the - // * model to consider the results of tokens with the provided probability mass. As an example, a - // * value of 0.15 will cause only the tokens comprising the top 15% of probability mass to be - // * considered. - // * It is not recommended to modify temperature and top_p for the same completions request as the - // * interaction of these two settings is difficult to predict. - // */ - ////@JsonProperty(value = "top_p") - //private Double topP; - // - ///* - // * A map between GPT token IDs and bias scores that influences the probability of specific tokens - // * appearing in a completions response. Token IDs are computed via external tokenizer tools, while - // * bias scores reside in the range of -100 to 100 with minimum and maximum values corresponding to - // * a full ban or exclusive selection of a token, respectively. The exact behavior of a given bias - // * score varies by model. - // */ - ////@JsonProperty(value = "logit_bias") - //private Map logitBias; - // - ///* - // * An identifier for the caller or end user of the operation. This may be used for tracking - // * or rate-limiting purposes. - // */ - ////@JsonProperty(value = "user") - //private String user; - // - ///* - // * The number of chat completions choices that should be generated for a chat completions - // * response. - // * Because this setting can generate many completions, it may quickly consume your token quota. - // * Use carefully and ensure reasonable settings for max_tokens and stop. - // */ - ////@JsonProperty(value = "n") - //private Integer n; - // - ///* - // * A collection of textual sequences that will end completions generation. - // */ - ////@JsonProperty(value = "stop") - //private List stop; - // - ///* - // * A value that influences the probability of generated tokens appearing based on their existing - // * presence in generated text. - // * Positive values will make tokens less likely to appear when they already exist and increase the - // * model's likelihood to output new topics. - // */ - ////@JsonProperty(value = "presence_penalty") - //private Double presencePenalty; - // - ///* - // * A value that influences the probability of generated tokens appearing based on their cumulative - // * frequency in generated text. - // * Positive values will make tokens less likely to appear as their frequency increases and - // * decrease the likelihood of the model repeating the same statements verbatim. - // */ - ////@JsonProperty(value = "frequency_penalty") - //private Double frequencyPenalty; - - /* - * A value indicating whether chat completions should be streamed for this request. - */ - //@JsonProperty(value = "stream") - private Boolean stream; - // - /* - * The model name to provide as part of this completions request. - * Not applicable to Azure OpenAI, where deployment information should be included in the Azure - * resource URI that's connected to. - */ - //@JsonProperty(value = "model") - private String model; - - /** - * Creates an instance of ChatCompletionsOptions class. - * - * @param messages the messages value to set. - */ - @JsonCreator - public AzureChatCompletionsOptions(@JsonProperty(value = "messages") List messages) { - this.messages = messages; - } - // - ///** - // * Get the messages property: The collection of context messages associated with this chat completions request. - // * Typical usage begins with a chat message for the System role that provides instructions for the behavior of the - // * assistant, followed by alternating messages between the User and Assistant roles. - // * - // * @return the messages value. - // */ - //public List getMessages() { - // return this.messages; - //} - // - ///** - // * Get the maxTokens property: The maximum number of tokens to generate. - // * - // * @return the maxTokens value. - // */ - //public Integer getMaxTokens() { - // return this.maxTokens; - //} - // - ///** - // * Set the maxTokens property: The maximum number of tokens to generate. - // * - // * @param maxTokens the maxTokens value to set. - // * @return the ChatCompletionsOptions object itself. - // */ - //public AzureChatCompletionsOptions setMaxTokens(Integer maxTokens) { - // this.maxTokens = maxTokens; - // return this; - //} - // - ///** - // * Get the temperature property: The sampling temperature to use that controls the apparent creativity of generated - // * completions. Higher values will make output more random while lower values will make results more focused and - // * deterministic. It is not recommended to modify temperature and top_p for the same completions request as the - // * interaction of these two settings is difficult to predict. - // * - // * @return the temperature value. - // */ - //public Double getTemperature() { - // return this.temperature; - //} - // - ///** - // * Set the temperature property: The sampling temperature to use that controls the apparent creativity of generated - // * completions. Higher values will make output more random while lower values will make results more focused and - // * deterministic. It is not recommended to modify temperature and top_p for the same completions request as the - // * interaction of these two settings is difficult to predict. - // * - // * @param temperature the temperature value to set. - // * @return the ChatCompletionsOptions object itself. - // */ - //public AzureChatCompletionsOptions setTemperature(Double temperature) { - // this.temperature = temperature; - // return this; - //} - // - ///** - // * Get the topP property: An alternative to sampling with temperature called nucleus sampling. This value causes the - // * model to consider the results of tokens with the provided probability mass. As an example, a value of 0.15 will - // * cause only the tokens comprising the top 15% of probability mass to be considered. It is not recommended to - // * modify temperature and top_p for the same completions request as the interaction of these two settings is - // * difficult to predict. - // * - // * @return the topP value. - // */ - //public Double getTopP() { - // return this.topP; - //} - // - ///** - // * Set the topP property: An alternative to sampling with temperature called nucleus sampling. This value causes the - // * model to consider the results of tokens with the provided probability mass. As an example, a value of 0.15 will - // * cause only the tokens comprising the top 15% of probability mass to be considered. It is not recommended to - // * modify temperature and top_p for the same completions request as the interaction of these two settings is - // * difficult to predict. - // * - // * @param topP the topP value to set. - // * @return the ChatCompletionsOptions object itself. - // */ - //public AzureChatCompletionsOptions setTopP(Double topP) { - // this.topP = topP; - // return this; - //} - // - ///** - // * Get the logitBias property: A map between GPT token IDs and bias scores that influences the probability of - // * specific tokens appearing in a completions response. Token IDs are computed via external tokenizer tools, while - // * bias scores reside in the range of -100 to 100 with minimum and maximum values corresponding to a full ban or - // * exclusive selection of a token, respectively. The exact behavior of a given bias score varies by model. - // * - // * @return the logitBias value. - // */ - //public Map getLogitBias() { - // return this.logitBias; - //} - // - ///** - // * Set the logitBias property: A map between GPT token IDs and bias scores that influences the probability of - // * specific tokens appearing in a completions response. Token IDs are computed via external tokenizer tools, while - // * bias scores reside in the range of -100 to 100 with minimum and maximum values corresponding to a full ban or - // * exclusive selection of a token, respectively. The exact behavior of a given bias score varies by model. - // * - // * @param logitBias the logitBias value to set. - // * @return the ChatCompletionsOptions object itself. - // */ - //public AzureChatCompletionsOptions setLogitBias(Map logitBias) { - // this.logitBias = logitBias; - // return this; - //} - // - ///** - // * Get the user property: An identifier for the caller or end user of the operation. This may be used for tracking - // * or rate-limiting purposes. - // * - // * @return the user value. - // */ - //public String getUser() { - // return this.user; - //} - // - ///** - // * Set the user property: An identifier for the caller or end user of the operation. This may be used for tracking - // * or rate-limiting purposes. - // * - // * @param user the user value to set. - // * @return the ChatCompletionsOptions object itself. - // */ - //public AzureChatCompletionsOptions setUser(String user) { - // this.user = user; - // return this; - //} - // - ///** - // * Get the n property: The number of chat completions choices that should be generated for a chat completions - // * response. Because this setting can generate many completions, it may quickly consume your token quota. Use - // * carefully and ensure reasonable settings for max_tokens and stop. - // * - // * @return the n value. - // */ - //public Integer getN() { - // return this.n; - //} - // - ///** - // * Set the n property: The number of chat completions choices that should be generated for a chat completions - // * response. Because this setting can generate many completions, it may quickly consume your token quota. Use - // * carefully and ensure reasonable settings for max_tokens and stop. - // * - // * @param n the n value to set. - // * @return the ChatCompletionsOptions object itself. - // */ - //public AzureChatCompletionsOptions setN(Integer n) { - // this.n = n; - // return this; - //} - // - ///** - // * Get the stop property: A collection of textual sequences that will end completions generation. - // * - // * @return the stop value. - // */ - //public List getStop() { - // return this.stop; - //} - // - ///** - // * Set the stop property: A collection of textual sequences that will end completions generation. - // * - // * @param stop the stop value to set. - // * @return the ChatCompletionsOptions object itself. - // */ - //public AzureChatCompletionsOptions setStop(List stop) { - // this.stop = stop; - // return this; - //} - // - ///** - // * Get the presencePenalty property: A value that influences the probability of generated tokens appearing based on - // * their existing presence in generated text. Positive values will make tokens less likely to appear when they - // * already exist and increase the model's likelihood to output new topics. - // * - // * @return the presencePenalty value. - // */ - //public Double getPresencePenalty() { - // return this.presencePenalty; - //} - // - ///** - // * Set the presencePenalty property: A value that influences the probability of generated tokens appearing based on - // * their existing presence in generated text. Positive values will make tokens less likely to appear when they - // * already exist and increase the model's likelihood to output new topics. - // * - // * @param presencePenalty the presencePenalty value to set. - // * @return the ChatCompletionsOptions object itself. - // */ - //public AzureChatCompletionsOptions setPresencePenalty(Double presencePenalty) { - // this.presencePenalty = presencePenalty; - // return this; - //} - // - ///** - // * Get the frequencyPenalty property: A value that influences the probability of generated tokens appearing based on - // * their cumulative frequency in generated text. Positive values will make tokens less likely to appear as their - // * frequency increases and decrease the likelihood of the model repeating the same statements verbatim. - // * - // * @return the frequencyPenalty value. - // */ - //public Double getFrequencyPenalty() { - // return this.frequencyPenalty; - //} - // - ///** - // * Set the frequencyPenalty property: A value that influences the probability of generated tokens appearing based on - // * their cumulative frequency in generated text. Positive values will make tokens less likely to appear as their - // * frequency increases and decrease the likelihood of the model repeating the same statements verbatim. - // * - // * @param frequencyPenalty the frequencyPenalty value to set. - // * @return the ChatCompletionsOptions object itself. - // */ - //public AzureChatCompletionsOptions setFrequencyPenalty(Double frequencyPenalty) { - // this.frequencyPenalty = frequencyPenalty; - // return this; - //} - - /** - * Get the stream property: A value indicating whether chat completions should be streamed for this request. - * - * @return the stream value. - */ - public Boolean isStream() { - return this.stream; - } - - /** - * Set the stream property: A value indicating whether chat completions should be streamed for this request. - * - * @param stream the stream value to set. - * @return the ChatCompletionsOptions object itself. - */ - public AzureChatCompletionsOptions setStream(Boolean stream) { - this.stream = stream; - return this; - } - - /** - * Get the model property: The model name to provide as part of this completions request. Not applicable to Azure - * OpenAI, where deployment information should be included in the Azure resource URI that's connected to. - * - * @return the model value. - */ - public String getModel() { - return this.model; - } - - /** - * Set the model property: The model name to provide as part of this completions request. Not applicable to Azure - * OpenAI, where deployment information should be included in the Azure resource URI that's connected to. - * - * @param model the model value to set. - * @return the ChatCompletionsOptions object itself. - */ - public AzureChatCompletionsOptions setModel(String model) { - this.model = model; - return this; - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureChatMessage.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureChatMessage.java deleted file mode 100644 index 6ea6b8fee..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureChatMessage.java +++ /dev/null @@ -1,61 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.azure.model; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; - -@Data -public class AzureChatMessage { - - - /* - * The role associated with this message payload. - */ - @JsonProperty(value = "role") - private AzureChatRole role; - - /* - * The text associated with this message payload. - */ - @JsonProperty(value = "content") - private String content; - - /** - * Creates an instance of ChatMessage class. - * - * @param role the role value to set. - */ - @JsonCreator - public AzureChatMessage(@JsonProperty(value = "role") AzureChatRole role) { - this.role = role; - } - - /** - * Get the role property: The role associated with this message payload. - * - * @return the role value. - */ - public AzureChatRole getRole() { - return this.role; - } - - /** - * Get the content property: The text associated with this message payload. - * - * @return the content value. - */ - public String getContent() { - return this.content; - } - - /** - * Set the content property: The text associated with this message payload. - * - * @param content the content value to set. - * @return the ChatMessage object itself. - */ - public AzureChatMessage setContent(String content) { - this.content = content; - return this; - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureChatRole.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureChatRole.java deleted file mode 100644 index 7b2d3c12e..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureChatRole.java +++ /dev/null @@ -1,46 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.azure.model; - -import java.util.Collection; - -import com.fasterxml.jackson.annotation.JsonCreator; - -public class AzureChatRole extends AzureExpandableStringEnum { - - /** The role that instructs or sets the behavior of the assistant. */ - public static final AzureChatRole SYSTEM = fromString("system"); - - /** The role that provides responses to system-instructed, user-prompted input. */ - public static final AzureChatRole ASSISTANT = fromString("assistant"); - - /** The role that provides input for chat completions. */ - public static final AzureChatRole USER = fromString("user"); - - /** - * Creates a new instance of ChatRole value. - * - * @deprecated Use the {@link #fromString(String)} factory method. - */ - @Deprecated - public AzureChatRole() {} - - /** - * Creates or finds a ChatRole from its string representation. - * - * @param name a name to look for. - * @return the corresponding ChatRole. - */ - @JsonCreator - public static AzureChatRole fromString(String name) { - return fromString(name, AzureChatRole.class); - } - - - /** - * Gets known ChatRole values. - * - * @return known ChatRole values. - */ - public static Collection values() { - return values(AzureChatRole.class); - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureChoice.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureChoice.java deleted file mode 100644 index 060929ae7..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureChoice.java +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -// Code generated by Microsoft (R) AutoRest Code Generator. -package ai.chat2db.server.web.api.controller.ai.azure.model; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; - -/** - * The representation of a single prompt completion as part of an overall completions request. Generally, `n` choices - * are generated per provided prompt with a default value of 1. Token limits and other settings may limit the number of - * choices generated. - */ -@Data -public final class AzureChoice { - - /* - * The generated text for a given completions prompt. - */ - @JsonProperty(value = "text") - private String text; - - /* - * The ordered index associated with this completions choice. - */ - @JsonProperty(value = "index") - private int index; - - /* - * The log probabilities model for tokens associated with this completions choice. - */ - @JsonProperty(value = "logprobs") - private AzureCompletionsLogProbabilityModel logprobs; - - /* - * Reason for finishing - */ - @JsonProperty(value = "finish_reason") - private AzureCompletionsFinishReason finishReason; - - /** - * Creates an instance of Choice class. - * - * @param text the text value to set. - * @param index the index value to set. - * @param logprobs the logprobs value to set. - * @param finishReason the finishReason value to set. - */ - @JsonCreator - private AzureChoice( - @JsonProperty(value = "text") String text, - @JsonProperty(value = "index") int index, - @JsonProperty(value = "logprobs") AzureCompletionsLogProbabilityModel logprobs, - @JsonProperty(value = "finish_reason") AzureCompletionsFinishReason finishReason) { - this.text = text; - this.index = index; - this.logprobs = logprobs; - this.finishReason = finishReason; - } - - /** - * Get the text property: The generated text for a given completions prompt. - * - * @return the text value. - */ - public String getText() { - return this.text; - } - - /** - * Get the index property: The ordered index associated with this completions choice. - * - * @return the index value. - */ - public int getIndex() { - return this.index; - } - - /** - * Get the logprobs property: The log probabilities model for tokens associated with this completions choice. - * - * @return the logprobs value. - */ - public AzureCompletionsLogProbabilityModel getLogprobs() { - return this.logprobs; - } - - /** - * Get the finishReason property: Reason for finishing. - * - * @return the finishReason value. - */ - public AzureCompletionsFinishReason getFinishReason() { - return this.finishReason; - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureCompletions.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureCompletions.java deleted file mode 100644 index b32fb5e98..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureCompletions.java +++ /dev/null @@ -1,98 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.azure.model; - -import java.util.List; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; - -@Data -public class AzureCompletions { - - /* - * A unique identifier associated with this completions response. - */ - @JsonProperty(value = "id") - private String id; - - /* - * The first timestamp associated with generation activity for this completions response, - * represented as seconds since the beginning of the Unix epoch of 00:00 on 1 Jan 1970. - */ - @JsonProperty(value = "created") - private int created; - - /* - * The collection of completions choices associated with this completions response. - * Generally, `n` choices are generated per provided prompt with a default value of 1. - * Token limits and other settings may limit the number of choices generated. - */ - @JsonProperty(value = "choices") - private List choices; - - /* - * Usage information for tokens processed and generated as part of this completions operation. - */ - @JsonProperty(value = "usage") - private AzureCompletionsUsage usage; - - /** - * Creates an instance of Completions class. - * - * @param id the id value to set. - * @param created the created value to set. - * @param choices the choices value to set. - * @param usage the usage value to set. - */ - @JsonCreator - private AzureCompletions( - @JsonProperty(value = "id") String id, - @JsonProperty(value = "created") int created, - @JsonProperty(value = "choices") List choices, - @JsonProperty(value = "usage") AzureCompletionsUsage usage) { - this.id = id; - this.created = created; - this.choices = choices; - this.usage = usage; - } - - /** - * Get the id property: A unique identifier associated with this completions response. - * - * @return the id value. - */ - public String getId() { - return this.id; - } - - /** - * Get the created property: The first timestamp associated with generation activity for this completions response, - * represented as seconds since the beginning of the Unix epoch of 00:00 on 1 Jan 1970. - * - * @return the created value. - */ - public int getCreated() { - return this.created; - } - - /** - * Get the choices property: The collection of completions choices associated with this completions response. - * Generally, `n` choices are generated per provided prompt with a default value of 1. Token limits and other - * settings may limit the number of choices generated. - * - * @return the choices value. - */ - public List getChoices() { - return this.choices; - } - - /** - * Get the usage property: Usage information for tokens processed and generated as part of this completions - * operation. - * - * @return the usage value. - */ - public AzureCompletionsUsage getUsage() { - return this.usage; - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureCompletionsFinishReason.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureCompletionsFinishReason.java deleted file mode 100644 index 144fe2b76..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureCompletionsFinishReason.java +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -// Code generated by Microsoft (R) AutoRest Code Generator. -package ai.chat2db.server.web.api.controller.ai.azure.model; - -import java.util.Collection; - -import com.fasterxml.jackson.annotation.JsonCreator; - -/** Representation of the manner in which a completions response concluded. */ -public final class AzureCompletionsFinishReason extends AzureExpandableStringEnum { - - /** Completions ended normally and reached its end of token generation. */ - public static final AzureCompletionsFinishReason STOPPED = fromString("stopped"); - - /** Completions exhausted available token limits before generation could complete. */ - public static final AzureCompletionsFinishReason TOKEN_LIMIT_REACHED = fromString("tokenLimitReached"); - - /** - * Completions generated a response that was identified as potentially sensitive per content moderation policies. - */ - public static final AzureCompletionsFinishReason CONTENT_FILTERED = fromString("contentFiltered"); - - /** - * Creates a new instance of CompletionsFinishReason value. - * - * @deprecated Use the {@link #fromString(String)} factory method. - */ - @Deprecated - public AzureCompletionsFinishReason() {} - - /** - * Creates or finds a CompletionsFinishReason from its string representation. - * - * @param name a name to look for. - * @return the corresponding CompletionsFinishReason. - */ - @JsonCreator - public static AzureCompletionsFinishReason fromString(String name) { - return fromString(name, AzureCompletionsFinishReason.class); - } - - /** - * Gets known CompletionsFinishReason values. - * - * @return known CompletionsFinishReason values. - */ - public static Collection values() { - return values(AzureCompletionsFinishReason.class); - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureCompletionsLogProbabilityModel.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureCompletionsLogProbabilityModel.java deleted file mode 100644 index dba305fc9..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureCompletionsLogProbabilityModel.java +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -// Code generated by Microsoft (R) AutoRest Code Generator. -package ai.chat2db.server.web.api.controller.ai.azure.model; - -import java.util.List; -import java.util.Map; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; - -/** Representation of a log probabilities model for a completions generation. */ -@Data -public final class AzureCompletionsLogProbabilityModel { - - /* - * The textual forms of tokens evaluated in this probability model. - */ - @JsonProperty(value = "tokens") - private List tokens; - - /* - * A collection of log probability values for the tokens in this completions data. - */ - @JsonProperty(value = "token_logprobs") - private List tokenLogprobs; - - /* - * A mapping of tokens to maximum log probability values in this completions data. - */ - @JsonProperty(value = "top_logprobs") - private List> topLogprobs; - - /* - * The text offsets associated with tokens in this completions data. - */ - @JsonProperty(value = "text_offset") - private List textOffset; - - /** - * Creates an instance of CompletionsLogProbabilityModel class. - * - * @param tokens the tokens value to set. - * @param tokenLogprobs the tokenLogprobs value to set. - * @param topLogprobs the topLogprobs value to set. - * @param textOffset the textOffset value to set. - */ - @JsonCreator - private AzureCompletionsLogProbabilityModel( - @JsonProperty(value = "tokens") List tokens, - @JsonProperty(value = "token_logprobs") List tokenLogprobs, - @JsonProperty(value = "top_logprobs") List> topLogprobs, - @JsonProperty(value = "text_offset") List textOffset) { - this.tokens = tokens; - this.tokenLogprobs = tokenLogprobs; - this.topLogprobs = topLogprobs; - this.textOffset = textOffset; - } - - /** - * Get the tokens property: The textual forms of tokens evaluated in this probability model. - * - * @return the tokens value. - */ - public List getTokens() { - return this.tokens; - } - - /** - * Get the tokenLogprobs property: A collection of log probability values for the tokens in this completions data. - * - * @return the tokenLogprobs value. - */ - public List getTokenLogprobs() { - return this.tokenLogprobs; - } - - /** - * Get the topLogprobs property: A mapping of tokens to maximum log probability values in this completions data. - * - * @return the topLogprobs value. - */ - public List> getTopLogprobs() { - return this.topLogprobs; - } - - /** - * Get the textOffset property: The text offsets associated with tokens in this completions data. - * - * @return the textOffset value. - */ - public List getTextOffset() { - return this.textOffset; - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureCompletionsUsage.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureCompletionsUsage.java deleted file mode 100644 index 0d830ad91..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureCompletionsUsage.java +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -// Code generated by Microsoft (R) AutoRest Code Generator. -package ai.chat2db.server.web.api.controller.ai.azure.model; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; - -/** - * Representation of the token counts processed for a completions request. Counts consider all tokens across prompts, - * choices, choice alternates, best_of generations, and other consumers. - */ -@Data -public final class AzureCompletionsUsage { - - /* - * The number of tokens generated across all completions emissions. - */ - @JsonProperty(value = "completion_tokens") - private int completionTokens; - - /* - * The number of tokens in the provided prompts for the completions request. - */ - @JsonProperty(value = "prompt_tokens") - private int promptTokens; - - /* - * The total number of tokens processed for the completions request and response. - */ - @JsonProperty(value = "total_tokens") - private int totalTokens; - - /** - * Creates an instance of CompletionsUsage class. - * - * @param completionTokens the completionTokens value to set. - * @param promptTokens the promptTokens value to set. - * @param totalTokens the totalTokens value to set. - */ - @JsonCreator - private AzureCompletionsUsage( - @JsonProperty(value = "completion_tokens") int completionTokens, - @JsonProperty(value = "prompt_tokens") int promptTokens, - @JsonProperty(value = "total_tokens") int totalTokens) { - this.completionTokens = completionTokens; - this.promptTokens = promptTokens; - this.totalTokens = totalTokens; - } - - /** - * Get the completionTokens property: The number of tokens generated across all completions emissions. - * - * @return the completionTokens value. - */ - public int getCompletionTokens() { - return this.completionTokens; - } - - /** - * Get the promptTokens property: The number of tokens in the provided prompts for the completions request. - * - * @return the promptTokens value. - */ - public int getPromptTokens() { - return this.promptTokens; - } - - /** - * Get the totalTokens property: The total number of tokens processed for the completions request and response. - * - * @return the totalTokens value. - */ - public int getTotalTokens() { - return this.totalTokens; - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureExpandableStringEnum.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureExpandableStringEnum.java deleted file mode 100644 index 57eb97e23..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureExpandableStringEnum.java +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package ai.chat2db.server.web.api.controller.ai.azure.model; - -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; - -import ai.chat2db.server.web.api.controller.ai.azure.util.AzureReflectionUtils; -import com.fasterxml.jackson.annotation.JsonValue; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import static java.lang.invoke.MethodType.methodType; - -/** - * Base implementation for expandable, single string enums. - * - * @param a specific expandable enum type - */ -public abstract class AzureExpandableStringEnum> { - private static final Map, MethodHandle> CONSTRUCTORS = new ConcurrentHashMap<>(); - private static final Map, ConcurrentHashMap>> VALUES - = new ConcurrentHashMap<>(); - - private static final Logger LOGGER = LoggerFactory.getLogger(AzureExpandableStringEnum.class); - private String name; - private Class clazz; - - /** - * Creates a new instance of {@link AzureExpandableStringEnum} without a {@link #toString()} value. - *

- * This constructor shouldn't be called as it will produce a {@link AzureExpandableStringEnum} which doesn't - * have a String enum value. - * - * @deprecated Use the {@link #fromString(String, Class)} factory method. - */ - @Deprecated - public AzureExpandableStringEnum() { - } - - /** - * Creates an instance of the specific expandable string enum from a String. - * - * @param name The value to create the instance from. - * @param clazz The class of the expandable string enum. - * @param the class of the expandable string enum. - * @return The expandable string enum instance. - * - * @throws RuntimeException wrapping implementation class constructor exception (if any is thrown). - */ - @SuppressWarnings({"unchecked", "deprecation"}) - protected static > T fromString(String name, Class clazz) { - if (name == null) { - return null; - } - - ConcurrentHashMap clazzValues = VALUES.computeIfAbsent(clazz, key -> new ConcurrentHashMap<>()); - T value = (T) clazzValues.get(name); - - if (value != null) { - return value; - } else { - MethodHandle ctor = CONSTRUCTORS.computeIfAbsent(clazz, AzureExpandableStringEnum::getDefaultConstructor); - - if (ctor == null) { - // logged in ExpandableStringEnum::getDefaultConstructor - return null; - } - - try { - value = (T) ctor.invoke(); - } catch (Throwable e) { - LOGGER.warn("Failed to create {}, default constructor threw exception", clazz.getName(), e); - return null; - } - - return value.nameAndAddValue(name, value, clazz); - } - } - - private static MethodHandle getDefaultConstructor(Class clazz) { - try { - MethodHandles.Lookup lookup = AzureReflectionUtils.getLookupToUse(clazz); - return lookup.findConstructor(clazz, methodType(void.class)); - } catch (NoSuchMethodException | IllegalAccessException e) { - LOGGER.info("Can't find or access default constructor for {}, make sure corresponding package is open to azure-core", clazz.getName(), e); - } catch (Exception e) { - LOGGER.info("Failed to get lookup for {}", clazz.getName(), e); - } - - return null; - } - - @SuppressWarnings("unchecked") - T nameAndAddValue(String name, T value, Class clazz) { - this.name = name; - this.clazz = clazz; - - ((ConcurrentHashMap) VALUES.get(clazz)).put(name, value); - return (T) this; - } - - /** - * Gets a collection of all known values to an expandable string enum type. - * - * @param clazz the class of the expandable string enum. - * @param the class of the expandable string enum. - * @return A collection of all known values for the given {@code clazz}. - */ - @SuppressWarnings("unchecked") - protected static > Collection values(Class clazz) { - return new ArrayList((Collection) VALUES.getOrDefault(clazz, new ConcurrentHashMap<>()).values()); - } - - @Override - @JsonValue - public String toString() { - return this.name; - } - - @Override - public int hashCode() { - return Objects.hash(this.clazz, this.name); - } - - @SuppressWarnings("unchecked") - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } else if (clazz == null || !clazz.isAssignableFrom(obj.getClass())) { - return false; - } else if (obj == this) { - return true; - } else if (this.name == null) { - return ((AzureExpandableStringEnum) obj).name == null; - } else { - return this.name.equals(((AzureExpandableStringEnum) obj).name); - } - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/util/AzureReflectionUtils.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/util/AzureReflectionUtils.java deleted file mode 100644 index 9b98959ee..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/util/AzureReflectionUtils.java +++ /dev/null @@ -1,190 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package ai.chat2db.server.web.api.controller.ai.azure.util; - -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; -import java.lang.reflect.Constructor; -import java.security.PrivilegedExceptionAction; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Utility methods that aid in performing reflective operations. - */ -@SuppressWarnings("deprecation") -public final class AzureReflectionUtils { - private static final Logger LOGGER = LoggerFactory.getLogger(AzureReflectionUtils.class); - - private static final boolean MODULE_BASED; - - private static final MethodHandle CLASS_GET_MODULE_METHOD_HANDLE; - private static final MethodHandle MODULE_IS_NAMED_METHOD_HANDLE; - private static final MethodHandle MODULE_ADD_READS_METHOD_HANDLE; - private static final MethodHandle METHOD_HANDLES_PRIVATE_LOOKUP_IN_METHOD_HANDLE; - private static final MethodHandle MODULE_IS_OPEN_UNCONDITIONALLY_METHOD_HANDLE; - private static final MethodHandle MODULE_IS_OPEN_TO_OTHER_MODULE_METHOD_HANDLE; - - private static final MethodHandles.Lookup LOOKUP; - private static final Object CORE_MODULE; - - private static final MethodHandle JDK_INTERNAL_PRIVATE_LOOKUP_IN_CONSTRUCTOR; - - static { - boolean moduleBased = false; - MethodHandle classGetModule = null; - MethodHandle moduleIsNamed = null; - MethodHandle moduleAddReads = null; - MethodHandle methodHandlesPrivateLookupIn = null; - MethodHandle moduleIsOpenUnconditionally = null; - MethodHandle moduleIsOpenToOtherModule = null; - - MethodHandles.Lookup lookup = MethodHandles.lookup(); - Object coreModule = null; - - MethodHandle jdkInternalPrivateLookupInConstructor = null; - - try { - Class moduleClass = Class.forName("java.lang.Module"); - classGetModule = lookup.unreflect(Class.class.getDeclaredMethod("getModule")); - moduleIsNamed = lookup.unreflect(moduleClass.getDeclaredMethod("isNamed")); - moduleAddReads = lookup.unreflect(moduleClass.getDeclaredMethod("addReads", moduleClass)); - methodHandlesPrivateLookupIn = lookup.findStatic(MethodHandles.class, "privateLookupIn", - MethodType.methodType(MethodHandles.Lookup.class, Class.class, MethodHandles.Lookup.class)); - moduleIsOpenUnconditionally = lookup.unreflect(moduleClass.getDeclaredMethod("isOpen", String.class)); - moduleIsOpenToOtherModule = lookup.unreflect( - moduleClass.getDeclaredMethod("isOpen", String.class, moduleClass)); - - coreModule = classGetModule.invokeWithArguments(AzureReflectionUtils.class); - moduleBased = true; - } catch (Throwable throwable) { - if (throwable instanceof Error) { - throw (Error) throwable; - } else { - LOGGER.error("Unable to create MethodHandles to use Java 9+ MethodHandles.privateLookupIn. " - + "Will attempt to fallback to using the package-private constructor.", throwable); - } - } - - if (!moduleBased) { - try { - Constructor privateLookupInConstructor = - MethodHandles.Lookup.class.getDeclaredConstructor(Class.class); - - if (!privateLookupInConstructor.isAccessible()) { - privateLookupInConstructor.setAccessible(true); - } - - jdkInternalPrivateLookupInConstructor = lookup.unreflectConstructor(privateLookupInConstructor); - } catch (ReflectiveOperationException ex) { - LOGGER.error("Unable to use package-private MethodHandles.Lookup constructor.", ex); - } - } - - MODULE_BASED = moduleBased; - CLASS_GET_MODULE_METHOD_HANDLE = classGetModule; - MODULE_IS_NAMED_METHOD_HANDLE = moduleIsNamed; - MODULE_ADD_READS_METHOD_HANDLE = moduleAddReads; - METHOD_HANDLES_PRIVATE_LOOKUP_IN_METHOD_HANDLE = methodHandlesPrivateLookupIn; - MODULE_IS_OPEN_UNCONDITIONALLY_METHOD_HANDLE = moduleIsOpenUnconditionally; - MODULE_IS_OPEN_TO_OTHER_MODULE_METHOD_HANDLE = moduleIsOpenToOtherModule; - LOOKUP = lookup; - CORE_MODULE = coreModule; - JDK_INTERNAL_PRIVATE_LOOKUP_IN_CONSTRUCTOR = jdkInternalPrivateLookupInConstructor; - } - - /** - * Gets the {@link MethodHandles.Lookup} to use when performing reflective operations. - *

- * If Java 8 is being used this will always return {@link MethodHandles.Lookup#publicLookup()} as Java 8 doesn't - * have module boundaries that will prevent reflective access to the {@code targetClass}. - *

- * If Java 9 or above is being used this will return a {@link MethodHandles.Lookup} based on whether the module - * containing the {@code targetClass} exports the package containing the class. Otherwise, the - * {@link MethodHandles.Lookup} associated to {@code com.azure.core} will attempt to read the module containing - * {@code targetClass}. - * - * @param targetClass The {@link Class} that will need to be reflectively accessed. - * @return The {@link MethodHandles.Lookup} that will allow {@code com.azure.core} to access the {@code targetClass} - * reflectively. - * @throws Exception If the underlying reflective calls throw an exception. - */ - public static MethodHandles.Lookup getLookupToUse(Class targetClass) throws Exception { - try { - if (MODULE_BASED) { - Object responseModule = CLASS_GET_MODULE_METHOD_HANDLE.invoke(targetClass); - - // The unnamed module is opened unconditionally, have Core read it and use a private proxy lookup to - // enable all lookup scenarios. - if (!(boolean) MODULE_IS_NAMED_METHOD_HANDLE.invoke(responseModule)) { - MODULE_ADD_READS_METHOD_HANDLE.invokeWithArguments(CORE_MODULE, responseModule); - return performSafePrivateLookupIn(targetClass); - } - - - // If the response module is the Core module return the Core private lookup. - if (responseModule == CORE_MODULE) { - return LOOKUP; - } - - // Next check if the target class module is opened either unconditionally or to Core's module. If so, - // also use a private proxy lookup to enable all lookup scenarios. - String packageName = targetClass.getPackage().getName(); - if ((boolean) MODULE_IS_OPEN_UNCONDITIONALLY_METHOD_HANDLE - .invokeWithArguments(responseModule, packageName) - || (boolean) MODULE_IS_OPEN_TO_OTHER_MODULE_METHOD_HANDLE - .invokeWithArguments(responseModule, packageName, CORE_MODULE)) { - MODULE_ADD_READS_METHOD_HANDLE.invokeWithArguments(CORE_MODULE, responseModule); - return performSafePrivateLookupIn(targetClass); - } - - // Otherwise, return the public lookup as there are no specialty ways to access the other module. - return MethodHandles.publicLookup(); - } else { - return (MethodHandles.Lookup) JDK_INTERNAL_PRIVATE_LOOKUP_IN_CONSTRUCTOR.invoke(targetClass); - } - } catch (Throwable throwable) { - // invoke(Class targetClass) throws Throwable { - // MethodHandles::privateLookupIn() throws SecurityException if denied by the security manager - if (System.getSecurityManager() == null) { - return (MethodHandles.Lookup) METHOD_HANDLES_PRIVATE_LOOKUP_IN_METHOD_HANDLE - .invokeExact(targetClass, LOOKUP); - } else { - return java.security.AccessController.doPrivileged((PrivilegedExceptionAction) () -> { - try { - return (MethodHandles.Lookup) METHOD_HANDLES_PRIVATE_LOOKUP_IN_METHOD_HANDLE - .invokeExact(targetClass, LOOKUP); - } catch (Throwable throwable) { - if (throwable instanceof Error) { - throw (Error) throwable; - } else { - throw (Exception) throwable; - } - } - }); - } - } - - public static boolean isModuleBased() { - return MODULE_BASED; - } - - AzureReflectionUtils() { - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/baichuan/client/BaichuanAIClient.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/baichuan/client/BaichuanAIClient.java deleted file mode 100644 index bcf952747..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/baichuan/client/BaichuanAIClient.java +++ /dev/null @@ -1,93 +0,0 @@ - -package ai.chat2db.server.web.api.controller.ai.baichuan.client; - -import ai.chat2db.server.domain.api.model.Config; -import ai.chat2db.server.domain.api.service.ConfigService; -import ai.chat2db.server.web.api.util.ApplicationContextUtil; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; - -/** - * @author moji - * @date 23/09/26 - */ -@Slf4j -public class BaichuanAIClient { - - /** - * BAICHUAN OPENAI KEY - */ - public static final String BAICHUAN_API_KEY = "baichuan.chatgpt.apiKey"; - - /** - * BAICHUAN OPENAI SECRET KEY - */ - public static final String BAICHUAN_SECRET_KEY = "baichuan.chatgpt.secretKey"; - - /** - * BAICHUAN OPENAI HOST - */ - public static final String BAICHUAN_HOST = "baichuan.host"; - - /** - * BAICHUAN OPENAI model - */ - public static final String BAICHUAN_MODEL= "baichuan.model"; - - /** - * BAICHUAN OPENAI embedding model - */ - public static final String BAICHUAN_EMBEDDING_MODEL = "baichuan.embedding.model"; - - private static BaichuanAIStreamClient BAICHUAN_AI_CLIENT; - - - public static BaichuanAIStreamClient getInstance() { - if (BAICHUAN_AI_CLIENT != null) { - return BAICHUAN_AI_CLIENT; - } else { - return singleton(); - } - } - - private static BaichuanAIStreamClient singleton() { - if (BAICHUAN_AI_CLIENT == null) { - synchronized (BaichuanAIClient.class) { - if (BAICHUAN_AI_CLIENT == null) { - refresh(); - } - } - } - return BAICHUAN_AI_CLIENT; - } - - public static void refresh() { - String apiKey = ""; - String apiHost = "https://api.baichuan-ai.com/v1/stream/chat"; - String model = "Baichuan2-53B"; - String secretKey = ""; - ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); - Config apiHostConfig = configService.find(BAICHUAN_HOST).getData(); - if (apiHostConfig != null && StringUtils.isNotBlank(apiHostConfig.getContent())) { - apiHost = apiHostConfig.getContent(); - if (apiHost.endsWith("/")) { - apiHost = apiHost.substring(0, apiHost.length() - 1); - } - } - Config config = configService.find(BAICHUAN_API_KEY).getData(); - if (config != null && StringUtils.isNotBlank(config.getContent())) { - apiKey = config.getContent(); - } - Config secretConfig = configService.find(BAICHUAN_SECRET_KEY).getData(); - if (secretConfig != null && StringUtils.isNotBlank(secretConfig.getContent())) { - secretKey = secretConfig.getContent(); - } - Config deployConfig = configService.find(BAICHUAN_MODEL).getData(); - if (deployConfig != null && StringUtils.isNotBlank(deployConfig.getContent())) { - model = deployConfig.getContent(); - } - BAICHUAN_AI_CLIENT = BaichuanAIStreamClient.builder().apiKey(apiKey).secretKey(secretKey) - .apiHost(apiHost).model(model).build(); - } - -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/baichuan/client/BaichuanAIStreamClient.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/baichuan/client/BaichuanAIStreamClient.java deleted file mode 100644 index 57aa33bc4..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/baichuan/client/BaichuanAIStreamClient.java +++ /dev/null @@ -1,227 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.baichuan.client; - -import ai.chat2db.server.tools.common.exception.ParamBusinessException; -import ai.chat2db.server.web.api.controller.ai.baichuan.interceptor.BaichuanHeaderAuthorizationInterceptor; -import ai.chat2db.server.web.api.controller.ai.baichuan.model.BaichuanChatCompletionsOptions; -import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatMessage; -import cn.hutool.http.ContentType; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import okhttp3.*; -import okhttp3.sse.EventSourceListener; -import okio.BufferedSource; -import org.apache.commons.collections4.CollectionUtils; -import org.jetbrains.annotations.NotNull; - -import java.io.IOException; -import java.util.List; -import java.util.Objects; -import java.util.concurrent.TimeUnit; - -/** - * Fast Chat Aligned Client - * - * @author moji - */ -@Slf4j -public class BaichuanAIStreamClient { - - /** - * apikey - */ - @Getter - @NotNull - private String apiKey; - - @Getter - @NotNull - private String secretKey; - - /** - * apiHost - */ - @Getter - @NotNull - private String apiHost; - - /** - * model - */ - @Getter - private String model; - - /** - * embeddingModel - */ - @Getter - private String embeddingModel; - - /** - * okHttpClient - */ - @Getter - private OkHttpClient okHttpClient; - - - /** - * @param builder - */ - private BaichuanAIStreamClient(Builder builder) { - this.apiKey = builder.apiKey; - this.apiHost = builder.apiHost; - this.model = builder.model; - this.secretKey = builder.secretKey; - this.embeddingModel = builder.embeddingModel; - if (Objects.isNull(builder.okHttpClient)) { - builder.okHttpClient = this.okHttpClient(); - } - okHttpClient = builder.okHttpClient; - } - - /** - * okhttpclient - */ - private OkHttpClient okHttpClient() { - OkHttpClient okHttpClient = new OkHttpClient - .Builder() - .addInterceptor(new BaichuanHeaderAuthorizationInterceptor(this.apiKey, this.secretKey)) - .connectTimeout(10, TimeUnit.SECONDS) - .writeTimeout(50, TimeUnit.SECONDS) - .readTimeout(50, TimeUnit.SECONDS) - .build(); - return okHttpClient; - } - - /** - * 构造 - * - * @return - */ - public static BaichuanAIStreamClient.Builder builder() { - return new BaichuanAIStreamClient.Builder(); - } - - /** - * builder - */ - public static final class Builder { - private String apiKey; - - private String secretKey; - - private String apiHost; - - private String model; - - private String embeddingModel; - - /** - * OkhttpClient - */ - private OkHttpClient okHttpClient; - - public Builder() { - } - - public BaichuanAIStreamClient.Builder apiKey(String apiKeyValue) { - this.apiKey = apiKeyValue; - return this; - } - - public BaichuanAIStreamClient.Builder secretKey(String secretKey) { - this.secretKey = secretKey; - return this; - } - - /** - * @param apiHostValue - * @return - */ - public BaichuanAIStreamClient.Builder apiHost(String apiHostValue) { - this.apiHost = apiHostValue; - return this; - } - - /** - * @param modelValue - * @return - */ - public BaichuanAIStreamClient.Builder model(String modelValue) { - this.model = modelValue; - return this; - } - - public BaichuanAIStreamClient.Builder embeddingModel(String embeddingModelValue) { - this.embeddingModel = embeddingModelValue; - return this; - } - - public BaichuanAIStreamClient.Builder okHttpClient(OkHttpClient val) { - this.okHttpClient = val; - return this; - } - - public BaichuanAIStreamClient build() { - return new BaichuanAIStreamClient(this); - } - - } - - /** - * 问答接口 stream 形式 - * - * @param chatMessages - * @param eventSourceListener - */ - public void streamCompletions(List chatMessages, EventSourceListener eventSourceListener) { - if (CollectionUtils.isEmpty(chatMessages)) { - log.error("param error:Baichuan Chat Prompt cannot be empty"); - throw new ParamBusinessException("prompt"); - } - if (Objects.isNull(eventSourceListener)) { - log.error("param error:Baichuan ChatEventSourceListener cannot be empty"); - throw new ParamBusinessException(); - } - log.info("Baichuan AI, prompt:{}", chatMessages.get(chatMessages.size() - 1).getContent()); - try { - - BaichuanChatCompletionsOptions chatCompletionsOptions = new BaichuanChatCompletionsOptions(); - chatCompletionsOptions.setModel(this.model); - chatCompletionsOptions.setMessages(chatMessages); - - ObjectMapper mapper = new ObjectMapper(); - mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - String requestBody = mapper.writeValueAsString(chatCompletionsOptions); - Request request = new Request.Builder() - .url(apiHost) - .post(RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), requestBody)) - .build(); - //创建事件 - // 发送请求并处理响应 - try (Response response = this.okHttpClient.newCall(request).execute()) { - if (!response.isSuccessful()) { - throw new IOException("Unexpected code " + response); - } - - // 读取并输出响应数据 - BufferedSource source = response.body().source(); - while (!source.exhausted()) { - String content = source.readUtf8Line(); - eventSourceListener.onEvent(null, "[DATA]", null, content); - } - eventSourceListener.onEvent(null, "[DONE]", null, "[DONE]"); - } catch (Exception e) { - log.error("baichuan ai error", e); - } - - log.info("finish invoking baichuan ai"); - } catch (Exception e) { - log.error("baichuan ai error", e); - eventSourceListener.onFailure(null, e, null); - throw new ParamBusinessException(); - } - } - -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/baichuan/interceptor/BaichuanHeaderAuthorizationInterceptor.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/baichuan/interceptor/BaichuanHeaderAuthorizationInterceptor.java deleted file mode 100644 index 3eb47f6eb..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/baichuan/interceptor/BaichuanHeaderAuthorizationInterceptor.java +++ /dev/null @@ -1,109 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.baichuan.interceptor; - -import cn.hutool.http.ContentType; -import cn.hutool.http.Header; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import okhttp3.*; -import okio.Buffer; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Base64; - -/** - * header apikey - * - * @author grt - * @since 2023-03-23 - */ -@Slf4j -@Getter -public class BaichuanHeaderAuthorizationInterceptor implements Interceptor { - - private String apiKey; - - private String secretKey; - - public BaichuanHeaderAuthorizationInterceptor(String apiKey, String secretKey) { - this.apiKey = apiKey; - this.secretKey = secretKey; - } - - - @Override - public Response intercept(Chain chain) throws IOException { - Request originalRequest = chain.request(); - - // 获取当前的时间戳(UTC标准时间戳) - long timestamp = System.currentTimeMillis() / 1000; - - // 获取原始的HTTP-Body - RequestBody originalRequestBody = originalRequest.body(); - Buffer buffer = new Buffer(); - if (originalRequestBody != null) { - originalRequestBody.writeTo(buffer); - } - String httpBody = buffer.readUtf8(); - - // 计算 X-BC-Signature - String signature = calculateSignature(secretKey, httpBody, timestamp); - - // 创建新的请求,并添加自定义请求头 - Request newRequest = originalRequest.newBuilder() - .addHeader("Authorization", "Bearer " + apiKey) - .addHeader("Content-Type", "application/json") - .addHeader("X-BC-Sign-Algo", "MD5") - .addHeader("X-BC-Timestamp", String.valueOf(timestamp)) - .addHeader("X-BC-Signature", signature) - .method(originalRequest.method(), RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), httpBody)) - .build(); - - return chain.proceed(newRequest); - } - - private String calculateSignature(String secretKey, String httpBody, long timestamp) { - String toHash = secretKey + httpBody + timestamp; - return md5(toHash); - } - - private String md5(String s) { - try { - MessageDigest digest = MessageDigest.getInstance("MD5"); - byte[] result = digest.digest(s.getBytes(StandardCharsets.UTF_8)); - StringBuilder sb = new StringBuilder(); - for (byte b : result) { - sb.append(String.format("%02x", b)); - } - return sb.toString(); - } catch (Exception e) { - log.error("baichuan secret key md5 error", e); - return ""; - } - } - - private String calculateSignature(String secretKey, RequestBody body, long timestamp) { - try { - String requestBody = bodyToString(body); - String rawSignature = secretKey + requestBody + timestamp; - - // 使用 MD5 计算签名 - MessageDigest md = MessageDigest.getInstance("MD5"); - byte[] mdBytes = md.digest(rawSignature.getBytes(StandardCharsets.UTF_8)); - - // 将 MD5 字节数组转换为 Base64 编码的字符串 - return Base64.getEncoder().encodeToString(mdBytes); - } catch (IOException | NoSuchAlgorithmException e) { - log.error("baichuan secret key md5 error", e); - return ""; - } - } - - private String bodyToString(RequestBody body) throws IOException { - // 将 RequestBody 转换为字符串 - return body == null ? "" : body.toString(); - } -} - diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/baichuan/listener/BaichuanChatAIEventSourceListener.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/baichuan/listener/BaichuanChatAIEventSourceListener.java deleted file mode 100644 index c84f05b5f..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/baichuan/listener/BaichuanChatAIEventSourceListener.java +++ /dev/null @@ -1,141 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.baichuan.listener; - -import ai.chat2db.server.web.api.controller.ai.baichuan.model.BaichuanChatCompletions; -import ai.chat2db.server.web.api.controller.ai.baichuan.model.BaichuanChatMessage; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.unfbx.chatgpt.entity.chat.Message; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import okhttp3.Response; -import okhttp3.ResponseBody; -import okhttp3.sse.EventSource; -import okhttp3.sse.EventSourceListener; -import org.apache.commons.lang3.StringUtils; -import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; - -import java.io.IOException; -import java.util.Objects; - -/** - * 描述:OpenAIEventSourceListener - * - * @author https:www.unfbx.com - * @date 2023-02-22 - */ -@Slf4j -public class BaichuanChatAIEventSourceListener extends EventSourceListener { - - private SseEmitter sseEmitter; - - private ObjectMapper mapper = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); - - public BaichuanChatAIEventSourceListener(SseEmitter sseEmitter) { - this.sseEmitter = sseEmitter; - } - - /** - * {@inheritDoc} - */ - @Override - public void onOpen(EventSource eventSource, Response response) { - log.info("Baichuan Chat Sse connecting..."); - } - - /** - * {@inheritDoc} - */ - @SneakyThrows - @Override - public void onEvent(EventSource eventSource, String id, String type, String data) { - log.info("Baichuan Chat AI response data:{}", data); - if (data.equals("[DONE]")) { - log.info("Baichuan Chat AI closed"); - sseEmitter.send(SseEmitter.event() - .id("[DONE]") - .data("[DONE]") - .reconnectTime(3000)); - sseEmitter.complete(); - return; - } - - BaichuanChatCompletions chatCompletions = mapper.readValue(data, BaichuanChatCompletions.class); - String text = ""; - log.info("code={} msg={}", chatCompletions.getCode(), chatCompletions.getMsg()); - for (BaichuanChatMessage message : chatCompletions.getData().getMessages()) { - if (message != null) { - log.info("message: {}, Chat Role: {}", message.getContent(), message.getRole()); - if (message.getContent() != null) { - text = message.getContent(); - } - } - } - - Message message = new Message(); - message.setContent(text); - sseEmitter.send(SseEmitter.event() - .id(null) - .data(message) - .reconnectTime(3000)); - } - - @Override - public void onClosed(EventSource eventSource) { - try { - sseEmitter.send(SseEmitter.event() - .id("[DONE]") - .data("[DONE]")); - } catch (IOException e) { - throw new RuntimeException(e); - } - sseEmitter.complete(); - log.info("FastChatAI close sse connection..."); - } - - @Override - public void onFailure(EventSource eventSource, Throwable t, Response response) { - try { - if (Objects.isNull(response)) { - String message = t.getMessage(); - Message sseMessage = new Message(); - sseMessage.setContent(message); - sseEmitter.send(SseEmitter.event() - .id("[ERROR]") - .data(sseMessage)); - sseEmitter.send(SseEmitter.event() - .id("[DONE]") - .data("[DONE]")); - sseEmitter.complete(); - return; - } - ResponseBody body = response.body(); - String bodyString = Objects.nonNull(t) ? t.getMessage() : ""; - if (Objects.nonNull(body)) { - bodyString = body.string(); - if (StringUtils.isBlank(bodyString)) { - if (Objects.nonNull(t)) { - bodyString = t.getMessage(); - } else { - bodyString = String.valueOf(response.code()); - } - - } - log.error("Baichuan Chat AI sse response:{}", bodyString); - } else { - log.error("Baichuan Chat AI sse response:{},error:{}", response, t); - } - eventSource.cancel(); - Message message = new Message(); - message.setContent("Baichuan Chat AI error:" + bodyString); - sseEmitter.send(SseEmitter.event() - .id("[ERROR]") - .data(message)); - sseEmitter.send(SseEmitter.event() - .id("[DONE]") - .data("[DONE]")); - sseEmitter.complete(); - } catch (Exception exception) { - log.error("Baichuan Chat AI send data error:", exception); - } - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/baichuan/model/BaichuanChatCompletions.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/baichuan/model/BaichuanChatCompletions.java deleted file mode 100644 index f11a1b251..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/baichuan/model/BaichuanChatCompletions.java +++ /dev/null @@ -1,52 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.baichuan.model; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; - -import java.util.List; - -@Data -public class BaichuanChatCompletions { - - /* - * A unique identifier associated with this chat completions response. - */ - private String msg; - - private int code; - - /* - * The collection of completions choices associated with this completions response. - * Generally, `n` choices are generated per provided prompt with a default value of 1. - * Token limits and other settings may limit the number of choices generated. - */ - @JsonProperty(value = "data") - private BaichuanChatData data; - - /* - * Usage information for tokens processed and generated as part of this completions operation. - */ - private BaichuanChatCompletionsUsage usage; - - /** - * Creates an instance of ChatCompletions class. - * - * @param msg the id value to set. - * @param code the created value to set. - * @param choices the choices value to set. - * @param usage the usage value to set. - */ - @JsonCreator - private BaichuanChatCompletions( - @JsonProperty(value = "msg") String msg, - @JsonProperty(value = "code") int code, - @JsonProperty(value = "data") BaichuanChatData choices, - @JsonProperty(value = "usage") BaichuanChatCompletionsUsage usage) { - this.msg = msg; - this.code = code; - this.data = choices; - this.usage = usage; - } - -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/baichuan/model/BaichuanChatCompletionsOptions.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/baichuan/model/BaichuanChatCompletionsOptions.java deleted file mode 100644 index 31988beec..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/baichuan/model/BaichuanChatCompletionsOptions.java +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -// Code generated by Microsoft (R) AutoRest Code Generator. -package ai.chat2db.server.web.api.controller.ai.baichuan.model; - -import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatMessage; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.util.List; - -/** - * The configuration information for a chat completions request. Completions support a wide variety of tasks and - * generate text that continues from or "completes" provided prompt data. - */ -@Data -@NoArgsConstructor -@AllArgsConstructor -public final class BaichuanChatCompletionsOptions { - - /* - * The collection of context messages associated with this chat completions request. - * Typical usage begins with a chat message for the System role that provides instructions for - * the behavior of the assistant, followed by alternating messages between the User and - * Assistant roles. - */ - private List messages; - - // - /* - * The model name to provide as part of this completions request. - * Not applicable to Fast Chat AI, where deployment information should be included in the Fast Chat - * resource URI that's connected to. - */ - private String model; -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/baichuan/model/BaichuanChatCompletionsUsage.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/baichuan/model/BaichuanChatCompletionsUsage.java deleted file mode 100644 index 37fc1ec6d..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/baichuan/model/BaichuanChatCompletionsUsage.java +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -// Code generated by Microsoft (R) AutoRest Code Generator. -package ai.chat2db.server.web.api.controller.ai.baichuan.model; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; -import lombok.NoArgsConstructor; - -/** - * Representation of the token counts processed for a completions request. Counts consider all tokens across prompts, - * choices, choice alternates, best_of generations, and other consumers. - */ -@Data -@NoArgsConstructor -public final class BaichuanChatCompletionsUsage { - - /* - * The number of tokens generated across all completions emissions. - */ - @JsonProperty(value = "answer_tokens") - private int answerTokens; - - /* - * The number of tokens in the provided prompts for the completions request. - */ - @JsonProperty(value = "prompt_tokens") - private int promptTokens; - - /* - * The total number of tokens processed for the completions request and response. - */ - @JsonProperty(value = "total_tokens") - private int totalTokens; - - /** - * Creates an instance of CompletionsUsage class. - * - * @param completionTokens the completionTokens value to set. - * @param promptTokens the promptTokens value to set. - * @param totalTokens the totalTokens value to set. - */ - @JsonCreator - private BaichuanChatCompletionsUsage( - @JsonProperty(value = "answer_tokens") int completionTokens, - @JsonProperty(value = "prompt_tokens") int promptTokens, - @JsonProperty(value = "total_tokens") int totalTokens) { - this.answerTokens = completionTokens; - this.promptTokens = promptTokens; - this.totalTokens = totalTokens; - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/baichuan/model/BaichuanChatData.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/baichuan/model/BaichuanChatData.java deleted file mode 100644 index 04b0aa5d2..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/baichuan/model/BaichuanChatData.java +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -// Code generated by Microsoft (R) AutoRest Code Generator. -package ai.chat2db.server.web.api.controller.ai.baichuan.model; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; - -import java.util.List; - -/** - * The representation of a single prompt completion as part of an overall completions request. Generally, `n` choices - * are generated per provided prompt with a default value of 1. Token limits and other settings may limit the number of - * choices generated. - */ -@Data -public final class BaichuanChatData { - - /* - * The log probabilities model for tokens associated with this completions choice. - */ - @JsonProperty(value = "messages") - private List messages; - - - - /** - * Creates an instance of Choice class. - * - * @param message the message value to set - */ - @JsonCreator - private BaichuanChatData( - @JsonProperty(value = "messages") List message) { - this.messages = message; - } - -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/baichuan/model/BaichuanChatMessage.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/baichuan/model/BaichuanChatMessage.java deleted file mode 100644 index 6dbde1858..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/baichuan/model/BaichuanChatMessage.java +++ /dev/null @@ -1,30 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.baichuan.model; - -import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatRole; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; - -@Data -public class BaichuanChatMessage { - - - /* - * The role associated with this message payload. - */ - @JsonProperty(value = "role") - private FastChatRole role; - - /* - * The text associated with this message payload. - */ - @JsonProperty(value = "content") - private String content; - - /* - * Reason for finishing - */ - @JsonProperty(value = "finish_reason") - private String finishReason; - -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/chat2db/client/Chat2DBAIStreamClient.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/chat2db/client/Chat2DBAIStreamClient.java deleted file mode 100644 index 0f0b6d84f..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/chat2db/client/Chat2DBAIStreamClient.java +++ /dev/null @@ -1,267 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.chat2db.client; - -import ai.chat2db.server.domain.api.enums.AiSqlSourceEnum; -import ai.chat2db.server.domain.api.model.Config; -import ai.chat2db.server.domain.api.service.ConfigService; -import ai.chat2db.server.tools.base.wrapper.result.DataResult; -import ai.chat2db.server.tools.common.exception.ParamBusinessException; -import ai.chat2db.server.web.api.controller.ai.chat2db.interceptor.Chat2dbHeaderAuthorizationInterceptor; -import ai.chat2db.server.web.api.controller.ai.fastchat.client.FastChatOpenAiApi; -import ai.chat2db.server.web.api.controller.ai.fastchat.embeddings.FastChatEmbedding; -import ai.chat2db.server.web.api.controller.ai.fastchat.embeddings.FastChatEmbeddingResponse; -import ai.chat2db.server.web.api.util.ApplicationContextUtil; -import cn.hutool.http.ContentType; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.unfbx.chatgpt.entity.chat.ChatCompletion; -import com.unfbx.chatgpt.entity.chat.Message; -import com.unfbx.chatgpt.interceptor.HeaderAuthorizationInterceptor; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import okhttp3.*; -import okhttp3.sse.EventSource; -import okhttp3.sse.EventSourceListener; -import okhttp3.sse.EventSources; -import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang3.StringUtils; -import org.jetbrains.annotations.NotNull; -import retrofit2.Retrofit; -import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; -import retrofit2.converter.jackson.JacksonConverterFactory; - -import java.util.List; -import java.util.Objects; -import java.util.concurrent.TimeUnit; - -/** - * Fast Chat Aligned Client - * - * @author moji - */ -@Slf4j -public class Chat2DBAIStreamClient { - - /** - * apikey - */ - @Getter - @NotNull - private String apiKey; - - /** - * apiHost - */ - @Getter - @NotNull - private String apiHost; - - /** - * model - */ - @Getter - private String model; - - /** - * embeddingModel - */ - @Getter - private String embeddingModel; - - /** - * okHttpClient - */ - @Getter - private OkHttpClient okHttpClient; - - @Getter - private FastChatOpenAiApi fastChatOpenAiApi; - - - /** - * @param builder - */ - private Chat2DBAIStreamClient(Builder builder) { - this.apiKey = builder.apiKey; - this.apiHost = builder.apiHost; - if (!apiHost.endsWith("/")){ - apiHost = apiHost + "/"; - } - this.model = builder.model; - this.embeddingModel = builder.embeddingModel; - if (Objects.isNull(builder.okHttpClient)) { - builder.okHttpClient = this.okHttpClient(); - } - okHttpClient = builder.okHttpClient; - this.fastChatOpenAiApi = new Retrofit.Builder() - .baseUrl(apiHost) - .client(okHttpClient) - .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) - .addConverterFactory(JacksonConverterFactory.create()) - .build().create(FastChatOpenAiApi.class); - } - - /** - * okhttpclient - */ - private OkHttpClient okHttpClient() { - OkHttpClient okHttpClient = new OkHttpClient - .Builder() - .addInterceptor(new Chat2dbHeaderAuthorizationInterceptor(this.apiKey, this.model)) - .connectTimeout(50, TimeUnit.SECONDS) - .writeTimeout(50, TimeUnit.SECONDS) - .readTimeout(50, TimeUnit.SECONDS) - .build(); - return okHttpClient; - } - - /** - * 构造 - * - * @return - */ - public static Chat2DBAIStreamClient.Builder builder() { - return new Chat2DBAIStreamClient.Builder(); - } - - /** - * builder - */ - public static final class Builder { - private String apiKey; - - private String apiHost; - - private String model; - - private String embeddingModel; - - /** - * OkhttpClient - */ - private OkHttpClient okHttpClient; - - public Builder() { - } - - public Chat2DBAIStreamClient.Builder apiKey(String apiKeyValue) { - this.apiKey = apiKeyValue; - return this; - } - - /** - * @param apiHostValue - * @return - */ - public Chat2DBAIStreamClient.Builder apiHost(String apiHostValue) { - this.apiHost = apiHostValue; - return this; - } - - /** - * @param modelValue - * @return - */ - public Chat2DBAIStreamClient.Builder model(String modelValue) { - this.model = modelValue; - return this; - } - - public Chat2DBAIStreamClient.Builder embeddingModel(String embeddingModelValue) { - this.embeddingModel = embeddingModelValue; - return this; - } - - public Chat2DBAIStreamClient.Builder okHttpClient(OkHttpClient val) { - this.okHttpClient = val; - return this; - } - - public Chat2DBAIStreamClient build() { - return new Chat2DBAIStreamClient(this); - } - - } - - /** - * 问答接口 stream 形式 - * - * @param chatMessages - * @param eventSourceListener - */ - public void streamCompletions(List chatMessages, EventSourceListener eventSourceListener) { - if (CollectionUtils.isEmpty(chatMessages)) { - log.error("param error:Chat Prompt cannot be empty"); - throw new ParamBusinessException("prompt"); - } - if (Objects.isNull(eventSourceListener)) { - log.error("param error:ChatEventSourceListener cannot be empty"); - throw new ParamBusinessException(); - } - try { - ChatCompletion chatCompletion = ChatCompletion.builder() - .messages(chatMessages) - .stream(true) - .build(); - - EventSource.Factory factory = EventSources.createFactory(this.okHttpClient); - ObjectMapper mapper = new ObjectMapper(); - mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - String requestBody = mapper.writeValueAsString(chatCompletion); - Request request = new Request.Builder() - .url(this.apiHost + "v1/chat/completions") - .post(RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), requestBody)) - .build(); - //创建事件 - EventSource eventSource = factory.newEventSource(request, eventSourceListener); - log.info("finish invoking chat ai"); - } catch (Exception e) { - log.error("chat ai error", e); - eventSourceListener.onFailure(null, e, null); - throw new ParamBusinessException(); - } - } - - /** - * Creates an embedding vector representing the input text. - * - * @param input - * @return EmbeddingResponse - */ - public FastChatEmbeddingResponse embeddings(String input) { - FastChatEmbedding embedding = FastChatEmbedding.builder().input(input).build(); - if (StringUtils.isNotBlank(this.embeddingModel)) { - embedding.setModel(this.embeddingModel); - } - return this.embeddings(embedding); - } - - /** - * Creates an embedding vector representing the input text. - * - * @param embedding - * @return EmbeddingResponse - */ - public FastChatEmbeddingResponse embeddings(FastChatEmbedding embedding) { - try { - ObjectMapper mapper = new ObjectMapper(); - mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - String requestBody = mapper.writeValueAsString(embedding); - Request request = new Request.Builder() - .url(this.apiHost + "v1/embeddings") - .post(RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), requestBody)) - .build(); - - FastChatEmbeddingResponse chatEmbeddingResponse = null; - Response response = this.okHttpClient.newCall(request).execute(); - if (response.isSuccessful()) { - String body = response.body().string(); - chatEmbeddingResponse = mapper.readValue(body, FastChatEmbeddingResponse.class); - } - log.info("finish invoking chat embedding"); - return chatEmbeddingResponse; - } catch (Exception e) { - log.error("chat ai error", e); - throw new ParamBusinessException(); - } - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/chat2db/client/Chat2dbAIClient.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/chat2db/client/Chat2dbAIClient.java deleted file mode 100644 index a687d4596..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/chat2db/client/Chat2dbAIClient.java +++ /dev/null @@ -1,97 +0,0 @@ - -package ai.chat2db.server.web.api.controller.ai.chat2db.client; - -import ai.chat2db.server.domain.api.model.Config; -import ai.chat2db.server.domain.api.service.ConfigService; -import ai.chat2db.server.web.api.util.ApplicationContextUtil; -import com.google.common.collect.Lists; -import com.unfbx.chatgpt.OpenAiStreamClient; -import com.unfbx.chatgpt.constant.OpenAIConst; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; - -/** - * @author jipengfei - * @version : OpenAIClient.java - */ -@Slf4j -public class Chat2dbAIClient { - - public static final String CHAT2DB_OPENAI_KEY = "chat2db.apiKey"; - - /** - * OPENAI接口域名 - */ - public static final String CHAT2DB_OPENAI_HOST = "chat2db.apiHost"; - - /** - * OPENAI模型 - */ - public static final String CHAT2DB_OPENAI_MODEL = "chat2db.model"; - - /** - * FASTCHAT OPENAI embedding model - */ - public static final String CHAT2DB_EMBEDDING_MODEL= "fastchat.embedding.model"; - - - private static Chat2DBAIStreamClient CHAT2DB_AI_STREAM_CLIENT; - - public static Chat2DBAIStreamClient getInstance() { - if (CHAT2DB_AI_STREAM_CLIENT != null) { - return CHAT2DB_AI_STREAM_CLIENT; - } else { - return singleton(); - } - } - - private static Chat2DBAIStreamClient singleton() { - if (CHAT2DB_AI_STREAM_CLIENT == null) { - synchronized (Chat2dbAIClient.class) { - if (CHAT2DB_AI_STREAM_CLIENT == null) { - refresh(); - } - } - } - return CHAT2DB_AI_STREAM_CLIENT; - } - - public static void refresh() { - String apikey; - String apiHost = ApplicationContextUtil.getProperty(CHAT2DB_OPENAI_HOST); - if (StringUtils.isBlank(apiHost)) { - apiHost = OpenAIConst.OPENAI_HOST; - } - ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); - Config apiHostConfig = configService.find(CHAT2DB_OPENAI_HOST).getData(); - if (apiHostConfig != null) { - apiHost = apiHostConfig.getContent(); - } - Config config = configService.find(CHAT2DB_OPENAI_KEY).getData(); - if (config != null) { - apikey = config.getContent(); - } else { - apikey = ApplicationContextUtil.getProperty(CHAT2DB_OPENAI_KEY); - } - Config modelConfig = configService.find(CHAT2DB_OPENAI_MODEL).getData(); - String model = ""; - if (modelConfig != null) { - model = modelConfig.getContent(); - } - log.info("refresh chat2db apikey:{}", maskApiKey(apikey)); - CHAT2DB_AI_STREAM_CLIENT = Chat2DBAIStreamClient.builder().apiHost(apiHost) - .apiKey(apikey).model(model).build(); - } - - private static String maskApiKey(String input) { - if (input == null) { - return input; - } - - StringBuilder maskedString = new StringBuilder(input); - for (int i = input.length() / 4; i < input.length() / 2; i++) { - maskedString.setCharAt(i, '*'); - } - return maskedString.toString(); - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/chat2db/interceptor/Chat2dbHeaderAuthorizationInterceptor.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/chat2db/interceptor/Chat2dbHeaderAuthorizationInterceptor.java deleted file mode 100644 index 446a84a11..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/chat2db/interceptor/Chat2dbHeaderAuthorizationInterceptor.java +++ /dev/null @@ -1,46 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.chat2db.interceptor; - -import ai.chat2db.server.domain.api.enums.AiSqlSourceEnum; -import ai.chat2db.server.web.api.util.StringUtils; -import cn.hutool.http.ContentType; -import cn.hutool.http.Header; -import lombok.Getter; -import okhttp3.Interceptor; -import okhttp3.Request; -import okhttp3.Response; - -import java.io.IOException; - -/** - * 描述:请求增加header apikey - * - * @author grt - * @since 2023-03-23 - */ -@Getter -public class Chat2dbHeaderAuthorizationInterceptor implements Interceptor { - - private String apiKey; - - private String model; - - public Chat2dbHeaderAuthorizationInterceptor(String apiKey, String model) { - this.apiKey = apiKey; - this.model = model; - if (StringUtils.isEmpty(model)) { - this.model = AiSqlSourceEnum.OPENAI.getCode(); - } - } - - @Override - public Response intercept(Chain chain) throws IOException { - Request original = chain.request(); - Request request = original.newBuilder() - .header(Header.AUTHORIZATION.getValue(), "Bearer " + apiKey) - .header("X-CHAT2DB-AI-TYPE", model) - .header(Header.CONTENT_TYPE.getValue(), ContentType.JSON.getValue()) - .method(original.method(), original.body()) - .build(); - return chain.proceed(request); - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/chat2db/listener/Chat2dbAIEventSourceListener.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/chat2db/listener/Chat2dbAIEventSourceListener.java deleted file mode 100644 index 24b0847a6..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/chat2db/listener/Chat2dbAIEventSourceListener.java +++ /dev/null @@ -1,129 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.chat2db.listener; - -import ai.chat2db.server.domain.api.enums.AiSqlSourceEnum; -import ai.chat2db.server.domain.api.model.Config; -import ai.chat2db.server.domain.api.service.ConfigService; -import ai.chat2db.server.tools.base.wrapper.result.DataResult; -import ai.chat2db.server.web.api.controller.ai.baichuan.model.BaichuanChatCompletions; -import ai.chat2db.server.web.api.controller.ai.baichuan.model.BaichuanChatMessage; -import ai.chat2db.server.web.api.controller.ai.chat2db.client.Chat2dbAIClient; -import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatMessage; -import ai.chat2db.server.web.api.controller.ai.response.ChatCompletionResponse; -import ai.chat2db.server.web.api.controller.ai.zhipu.model.ZhipuChatCompletions; -import ai.chat2db.server.web.api.util.ApplicationContextUtil; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.unfbx.chatgpt.entity.chat.Message; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import okhttp3.Response; -import okhttp3.ResponseBody; -import okhttp3.sse.EventSource; -import okhttp3.sse.EventSourceListener; -import org.apache.commons.lang3.StringUtils; -import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; - -import java.util.Objects; - -/** - * 描述:Chat2dbAIEventSourceListener - * - * @author https:www.unfbx.com - * @date 2023-02-22 - */ -@Slf4j -public class Chat2dbAIEventSourceListener extends EventSourceListener { - - private SseEmitter sseEmitter; - - public Chat2dbAIEventSourceListener(SseEmitter sseEmitter) { - this.sseEmitter = sseEmitter; - } - - /** - * {@inheritDoc} - */ - @Override - public void onOpen(EventSource eventSource, Response response) { - log.info("Chat2db AI 建立sse连接..."); - } - - /** - * {@inheritDoc} - */ - @SneakyThrows - @Override - public void onEvent(EventSource eventSource, String id, String type, String data) { - log.info("Chat2db AI 返回数据:{}", data); - if (data.equals("[DONE]")) { - log.info("Chat2db AI 返回数据结束了"); - sseEmitter.send(SseEmitter.event() - .id("[DONE]") - .data("[DONE]") - .reconnectTime(3000)); - sseEmitter.complete(); - return; - } - ObjectMapper mapper = new ObjectMapper(); - mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - ChatCompletionResponse completionResponse = mapper.readValue(data, ChatCompletionResponse.class); - String text = completionResponse.getChoices().get(0).getDelta() == null - ? completionResponse.getChoices().get(0).getText() - : completionResponse.getChoices().get(0).getDelta().getContent(); - String completionId = completionResponse.getId(); - - Message message = new Message(); - if (text != null) { - message.setContent(text); - sseEmitter.send(SseEmitter.event() - .id(completionId) - .data(message) - .reconnectTime(3000)); - } - } - - @Override - public void onClosed(EventSource eventSource) { - sseEmitter.complete(); - log.info("Chat2db AI 关闭sse连接..."); - } - - @Override - public void onFailure(EventSource eventSource, Throwable t, Response response) { - try { - if (Objects.isNull(response)) { - String message = t.getMessage(); - Message sseMessage = new Message(); - sseMessage.setContent(message); - sseEmitter.send(SseEmitter.event() - .id("[ERROR]") - .data(sseMessage)); - sseEmitter.send(SseEmitter.event() - .id("[DONE]") - .data("[DONE]")); - sseEmitter.complete(); - return; - } - ResponseBody body = response.body(); - String bodyString = null; - if (Objects.nonNull(body)) { - bodyString = body.string(); - log.error("Chat2db AI sse连接异常data:{}", bodyString, t); - } else { - log.error("Chat2db AI sse连接异常data:{}", response, t); - } - eventSource.cancel(); - Message message = new Message(); - message.setContent("Chat2db AI Error:" + bodyString); - sseEmitter.send(SseEmitter.event() - .id("[ERROR]") - .data(message)); - sseEmitter.send(SseEmitter.event() - .id("[DONE]") - .data("[DONE]")); - sseEmitter.complete(); - } catch (Exception exception) { - log.error("发送数据异常:", exception); - } - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/client/ClaudeAIClient.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/client/ClaudeAIClient.java deleted file mode 100644 index 77dd97f22..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/client/ClaudeAIClient.java +++ /dev/null @@ -1,87 +0,0 @@ - -package ai.chat2db.server.web.api.controller.ai.claude.client; - -import ai.chat2db.server.domain.api.model.Config; -import ai.chat2db.server.domain.api.service.ConfigService; -import ai.chat2db.server.web.api.util.ApplicationContextUtil; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; - -/** - * @author jipengfei - * @version : OpenAIClient.java - */ -@Slf4j -public class ClaudeAIClient { - - public static final String CLAUDE_SESSION_KEY = "claude.sessionKey"; - - public static final String CLAUDE_API_HOST = "claude.apiHost"; - - public static final String CLAUDE_ORG_ID = "claude.orgId"; - - public static final String CLAUDE_USER_ID = "claude.userId"; - - - private static ClaudeAiStreamClient CLAUDE_AI_STREAM_CLIENT; - private static String apiKey; - - public static ClaudeAiStreamClient getInstance() { - if (CLAUDE_AI_STREAM_CLIENT != null) { - return CLAUDE_AI_STREAM_CLIENT; - } else { - return singleton(); - } - } - - private static ClaudeAiStreamClient singleton() { - if (CLAUDE_AI_STREAM_CLIENT == null) { - synchronized (ClaudeAIClient.class) { - if (CLAUDE_AI_STREAM_CLIENT == null) { - refresh(); - } - } - } - return CLAUDE_AI_STREAM_CLIENT; - } - - public static void refresh() { - String apikey = ""; - String orgId = ""; - String userId = ""; - String apiHost = "https://claude.ai/api/append_message"; - ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); - Config apiHostConfig = configService.find(CLAUDE_API_HOST).getData(); - if (apiHostConfig != null) { - apiHost = apiHostConfig.getContent(); - } - Config config = configService.find(CLAUDE_SESSION_KEY).getData(); - if (config != null) { - apikey = config.getContent(); - } - Config orgConfig = configService.find(CLAUDE_ORG_ID).getData(); - if (orgConfig != null) { - orgId = orgConfig.getContent(); - } - Config userConfig = configService.find(CLAUDE_USER_ID).getData(); - if (userConfig != null) { - userId = userConfig.getContent(); - } - log.info("refresh claude sessionKey:{}", maskApiKey(apikey)); - CLAUDE_AI_STREAM_CLIENT = ClaudeAiStreamClient.builder().apiHost(apiHost) - .sessionKey(apikey).orgId(orgId).userId(userId).build(); - apiKey = apikey; - } - - private static String maskApiKey(String input) { - if (StringUtils.isBlank(input)) { - return input; - } - - StringBuilder maskedString = new StringBuilder(input); - for (int i = input.length() / 4; i < input.length() / 2; i++) { - maskedString.setCharAt(i, '*'); - } - return maskedString.toString(); - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/client/ClaudeAiStreamClient.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/client/ClaudeAiStreamClient.java deleted file mode 100644 index 692a32a72..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/client/ClaudeAiStreamClient.java +++ /dev/null @@ -1,188 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.claude.client; - -import ai.chat2db.server.tools.common.exception.ParamBusinessException; -import ai.chat2db.server.web.api.controller.ai.claude.interceptor.ClaudeHeaderAuthorizationInterceptor; -import ai.chat2db.server.web.api.controller.ai.claude.model.ClaudeChatMessage; -import cn.hutool.http.ContentType; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import okhttp3.MediaType; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.sse.EventSource; -import okhttp3.sse.EventSourceListener; -import okhttp3.sse.EventSources; -import org.jetbrains.annotations.NotNull; - -import java.util.Objects; -import java.util.concurrent.TimeUnit; - -/** - * 自定义AI接口client - * - * @author moji - */ -@Slf4j -public class ClaudeAiStreamClient { - - /** - * apikey - */ - @Getter - @NotNull - private String sessionKey; - - /** - * endpoint - */ - @Getter - @NotNull - private String orgId; - - /** - * deployId - */ - @Getter - private String apiHost; - - @Getter - private String userId; - - /** - * okHttpClient - */ - @Getter - private OkHttpClient okHttpClient; - - - /** - * @param builder - */ - private ClaudeAiStreamClient(Builder builder) { - this.sessionKey = builder.sessionKey; - this.orgId = builder.orgId; - this.apiHost = builder.apiHost; - this.userId = builder.userId; - if (Objects.isNull(builder.okHttpClient)) { - builder.okHttpClient = this.okHttpClient(); - } - okHttpClient = builder.okHttpClient; - } - - /** - * okhttpclient - */ - private OkHttpClient okHttpClient() { - OkHttpClient okHttpClient = new OkHttpClient - .Builder() - .addInterceptor(new ClaudeHeaderAuthorizationInterceptor(this.sessionKey, this.orgId)) - .connectTimeout(10, TimeUnit.SECONDS) - .writeTimeout(50, TimeUnit.SECONDS) - .readTimeout(50, TimeUnit.SECONDS) - .build(); - return okHttpClient; - } - - /** - * 构造 - * - * @return - */ - public static ClaudeAiStreamClient.Builder builder() { - return new ClaudeAiStreamClient.Builder(); - } - - public static final class Builder { - private String sessionKey; - - private String orgId; - - private String apiHost; - - private String userId; - - /** - * 自定义OkhttpClient - */ - private OkHttpClient okHttpClient; - - public Builder() { - } - - public ClaudeAiStreamClient.Builder sessionKey(String sessionKey) { - this.sessionKey = sessionKey; - return this; - } - - /** - * @param apiHost - * @return - */ - public ClaudeAiStreamClient.Builder apiHost(String apiHost) { - this.apiHost = apiHost; - return this; - } - - /** - * @param orgId - * @return - */ - public ClaudeAiStreamClient.Builder orgId(String orgId) { - this.orgId = orgId; - return this; - } - - public ClaudeAiStreamClient.Builder userId(String userId) { - this.userId = userId; - return this; - } - - public ClaudeAiStreamClient.Builder okHttpClient(OkHttpClient val) { - this.okHttpClient = val; - return this; - } - - public ClaudeAiStreamClient build() { - return new ClaudeAiStreamClient(this); - } - - } - - /** - * chat - * - * @param claudeChatMessage - * @param eventSourceListener - */ - public void streamCompletions(ClaudeChatMessage claudeChatMessage, EventSourceListener eventSourceListener) { - if (Objects.isNull(eventSourceListener)) { - log.error("param error:AzureEventSourceListener cannot be empty"); - throw new ParamBusinessException(); - } - log.info("Claude AI, prompt:{}", claudeChatMessage.getText()); - try { - claudeChatMessage.setOrganization_uuid(this.orgId); - claudeChatMessage.setConversation_uuid(this.userId); - EventSource.Factory factory = EventSources.createFactory(this.okHttpClient); - ObjectMapper mapper = new ObjectMapper(); - mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - String requestBody = mapper.writeValueAsString(claudeChatMessage); - - Request request = new Request.Builder() - .url(this.apiHost) - .post(RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), requestBody)) - .build(); - //创建事件 - EventSource eventSource = factory.newEventSource(request, eventSourceListener); - log.info("finish invoking claude ai"); - } catch (Exception e) { - log.error("claude ai error", e); - eventSourceListener.onFailure(null, e, null); - throw new ParamBusinessException(); - } - } - -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/interceptor/ClaudeHeaderAuthorizationInterceptor.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/interceptor/ClaudeHeaderAuthorizationInterceptor.java deleted file mode 100644 index 351bb6c54..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/interceptor/ClaudeHeaderAuthorizationInterceptor.java +++ /dev/null @@ -1,40 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.claude.interceptor; - -import cn.hutool.http.ContentType; -import cn.hutool.http.Header; -import lombok.Getter; -import okhttp3.Interceptor; -import okhttp3.Request; -import okhttp3.Response; - -import java.io.IOException; - -/** - * 描述:请求增加header apikey - * - * @author grt - * @since 2023-03-23 - */ -@Getter -public class ClaudeHeaderAuthorizationInterceptor implements Interceptor { - - private String sessionKey; - - private String orgId; - - public ClaudeHeaderAuthorizationInterceptor(String sessionKey, String orgId) { - this.orgId = orgId; - this.sessionKey = sessionKey; - } - - @Override - public Response intercept(Chain chain) throws IOException { - Request original = chain.request(); - Request request = original.newBuilder() - .header("Cookie", "sessionKey=" + sessionKey) - .header(Header.CONTENT_TYPE.getValue(), ContentType.JSON.getValue()) - .method(original.method(), original.body()) - .build(); - return chain.proceed(request); - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/listener/ClaudeAIEventSourceListener.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/listener/ClaudeAIEventSourceListener.java deleted file mode 100644 index f3570fa6c..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/listener/ClaudeAIEventSourceListener.java +++ /dev/null @@ -1,112 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.claude.listener; - -import ai.chat2db.server.web.api.controller.ai.claude.model.ClaudeCompletionResponse; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.unfbx.chatgpt.entity.chat.Message; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import okhttp3.Response; -import okhttp3.ResponseBody; -import okhttp3.sse.EventSource; -import okhttp3.sse.EventSourceListener; -import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; - -import java.util.Objects; - -/** - * ClaudeAIEventSourceListener - */ -@Slf4j -public class ClaudeAIEventSourceListener extends EventSourceListener { - - private SseEmitter sseEmitter; - - private ObjectMapper mapper = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); - - public ClaudeAIEventSourceListener(SseEmitter sseEmitter) { - this.sseEmitter = sseEmitter; - } - - /** - * {@inheritDoc} - */ - @Override - public void onOpen(EventSource eventSource, Response response) { - log.info("ClaudeAIEventSourceListener..."); - } - - /** - * {@inheritDoc} - */ - @SneakyThrows - @Override - public void onEvent(EventSource eventSource, String id, String type, String data) { - log.info("Claude AI data:{}", data); - if (data.equals("[DONE]")) { - log.info("Claude AI end"); - sseEmitter.send(SseEmitter.event() - .id("[DONE]") - .data("[DONE]") - .reconnectTime(3000)); - sseEmitter.complete(); - return; - } - // 读取Json - ClaudeCompletionResponse completionResponse = mapper.readValue(data, ClaudeCompletionResponse.class); - String text = completionResponse.getCompletion(); - Message message = new Message(); - if (text != null) { - message.setContent(text); - sseEmitter.send(SseEmitter.event() - .id(null) - .data(message) - .reconnectTime(3000)); - } - } - - @Override - public void onClosed(EventSource eventSource) { - sseEmitter.complete(); - log.info("Claude AI closed..."); - } - - @Override - public void onFailure(EventSource eventSource, Throwable t, Response response) { - try { - if (Objects.isNull(response)) { - String message = t.getMessage(); - Message sseMessage = new Message(); - sseMessage.setContent(message); - sseEmitter.send(SseEmitter.event() - .id("[ERROR]") - .data(sseMessage)); - sseEmitter.send(SseEmitter.event() - .id("[DONE]") - .data("[DONE]")); - sseEmitter.complete(); - return; - } - ResponseBody body = response.body(); - String bodyString = null; - if (Objects.nonNull(body)) { - bodyString = body.string(); - log.error("Claude sse error:{}", bodyString, t); - } else { - log.error("Claude sse body error:{}", response, t); - } - eventSource.cancel(); - Message message = new Message(); - message.setContent("Claude sse error:" + bodyString); - sseEmitter.send(SseEmitter.event() - .id("[ERROR]") - .data(message)); - sseEmitter.send(SseEmitter.event() - .id("[DONE]") - .data("[DONE]")); - sseEmitter.complete(); - } catch (Exception exception) { - log.error("发送数据异常:", exception); - } - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/model/ClaudeChatCompletionsOptions.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/model/ClaudeChatCompletionsOptions.java deleted file mode 100644 index cc7961247..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/model/ClaudeChatCompletionsOptions.java +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -// Code generated by Microsoft (R) AutoRest Code Generator. -package ai.chat2db.server.web.api.controller.ai.claude.model; - -import lombok.Data; - -@Data -public final class ClaudeChatCompletionsOptions { - - private Boolean incremental = true; - - private String model = "claude-2"; - - private String prompt; - - private String timezone = "Asia/Shanghai"; - - private Boolean stream = true; - - public Boolean isStream() { - return this.stream; - } - - public ClaudeChatCompletionsOptions setStream(Boolean stream) { - this.stream = stream; - return this; - } - - public String getModel() { - return this.model; - } - - public ClaudeChatCompletionsOptions setModel(String model) { - this.model = model; - return this; - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/model/ClaudeChatMessage.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/model/ClaudeChatMessage.java deleted file mode 100644 index 5b9fe07bf..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/model/ClaudeChatMessage.java +++ /dev/null @@ -1,15 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.claude.model; - -import lombok.Data; - -@Data -public class ClaudeChatMessage { - - private String conversation_uuid; - - private String organization_uuid; - - private String text; - - private ClaudeChatCompletionsOptions completion; -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/model/ClaudeCompletionResponse.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/model/ClaudeCompletionResponse.java deleted file mode 100644 index 627078c9b..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/model/ClaudeCompletionResponse.java +++ /dev/null @@ -1,25 +0,0 @@ - -package ai.chat2db.server.web.api.controller.ai.claude.model; - -import com.unfbx.chatgpt.entity.common.Usage; -import lombok.Data; - -import java.io.Serial; -import java.io.Serializable; - -/** - * @author moji - * @version : ClaudeCompletionResponse.java - */ -@Data -public class ClaudeCompletionResponse implements Serializable { - @Serial - private static final long serialVersionUID = 4968922211204353592L; - private String log_id; - private String stop_reason; - private String stop; - private String model; - private String completion; - private Usage usage; - private ClaudeMessageLimit messageLimit; -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/model/ClaudeMessageLimit.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/model/ClaudeMessageLimit.java deleted file mode 100644 index f6b0c4e8a..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/model/ClaudeMessageLimit.java +++ /dev/null @@ -1,9 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.claude.model; - -import lombok.Data; - -@Data -public class ClaudeMessageLimit { - - private String type; -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/config/LocalCache.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/config/LocalCache.java deleted file mode 100644 index 37d3ff3d3..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/config/LocalCache.java +++ /dev/null @@ -1,31 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.config; - -import cn.hutool.cache.CacheUtil; -import cn.hutool.cache.impl.TimedCache; -import cn.hutool.core.date.DateUnit; - -/** - * 描述: - * - * @author https:www.unfbx.com - * @date 2023-03-10 - */ -public class LocalCache { - /** - * 缓存时长 - */ - public static final long TIMEOUT = 5 * DateUnit.MINUTE.getMillis(); - /** - * 清理间隔 - */ - private static final long CLEAN_TIMEOUT = 5 * DateUnit.MINUTE.getMillis(); - /** - * 缓存对象 - */ - public static final TimedCache CACHE = CacheUtil.newTimedCache(TIMEOUT); - - static { - //启动定时任务 - CACHE.schedulePrune(CLEAN_TIMEOUT); - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/conversation/AiConversationController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/conversation/AiConversationController.java new file mode 100644 index 000000000..b6e423a16 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/conversation/AiConversationController.java @@ -0,0 +1,94 @@ +package ai.chat2db.server.web.api.controller.ai.conversation; + +import java.util.List; + +import ai.chat2db.server.domain.api.model.AiConversation; +import ai.chat2db.server.domain.api.model.AiConversationDetail; +import ai.chat2db.server.domain.api.param.ai.AiConversationCreateParam; +import ai.chat2db.server.domain.api.param.ai.AiConversationQueryParam; +import ai.chat2db.server.domain.api.service.AiConversationService; +import ai.chat2db.server.tools.base.wrapper.ServicePage; +import ai.chat2db.server.tools.base.wrapper.result.ActionResult; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; +import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; +import ai.chat2db.server.tools.common.util.ContextUtils; +import ai.chat2db.server.web.api.controller.ai.conversation.converter.AiConversationWebConverter; +import ai.chat2db.server.web.api.controller.ai.conversation.request.AiConversationCreateRequest; +import ai.chat2db.server.web.api.controller.ai.conversation.request.AiConversationQueryRequest; +import ai.chat2db.server.web.api.controller.ai.conversation.request.AiConversationRenameRequest; +import ai.chat2db.server.web.api.controller.ai.conversation.vo.AiConversationDetailVO; +import ai.chat2db.server.web.api.controller.ai.conversation.vo.AiConversationVO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RequestMapping("/api/ai/conversation") +@RestController +public class AiConversationController { + + @Autowired + private AiConversationService aiConversationService; + + @Autowired + private AiConversationWebConverter webConverter; + + @PostMapping("/create") + public DataResult create(@RequestBody AiConversationCreateRequest request) { + AiConversationCreateParam param = webConverter.req2param(request); + param.setUserId(ContextUtils.getUserId()); + String conversationId = aiConversationService.create(param); + AiConversation conversation = aiConversationService.findByConversationId(conversationId); + return DataResult.of(webConverter.dto2vo(conversation)); + } + + @GetMapping("/list") + public WebPageResult list(AiConversationQueryRequest request) { + return queryPage(request); + } + + @GetMapping("/page") + public WebPageResult page(AiConversationQueryRequest request) { + return queryPage(request); + } + + private WebPageResult queryPage(AiConversationQueryRequest request) { + AiConversationQueryParam param = webConverter.queryReq2param(request); + param.setUserId(ContextUtils.getUserId()); + if (param.getPageNo() == null) { + param.setPageNo(request.getPageNo()); + } + if (param.getPageSize() == null) { + param.setPageSize(request.getPageSize()); + } + ServicePage page = aiConversationService.queryPage(param); + List vos = webConverter.dto2vo(page.getData()); + return WebPageResult.of(vos, page.getTotal(), request.getPageNo(), request.getPageSize()); + } + + @GetMapping("/{conversationId}") + public DataResult get(@PathVariable("conversationId") String conversationId) { + AiConversationDetail detail = aiConversationService.getDetail(conversationId, ContextUtils.getUserId()); + if (detail == null) { + return null; + } + return DataResult.of(webConverter.detail2voWithMessages(detail)); + } + + @PostMapping("/{conversationId}/rename") + public ActionResult rename(@PathVariable("conversationId") String conversationId, + @RequestBody AiConversationRenameRequest request) { + aiConversationService.updateTitle(conversationId, request.getTitle()); + return ActionResult.isSuccess(); + } + + @DeleteMapping("/{conversationId}") + public ActionResult delete(@PathVariable("conversationId") String conversationId) { + aiConversationService.deleteWithPermission(conversationId, ContextUtils.getUserId()); + return ActionResult.isSuccess(); + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/conversation/converter/AiConversationWebConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/conversation/converter/AiConversationWebConverter.java new file mode 100644 index 000000000..890aae334 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/conversation/converter/AiConversationWebConverter.java @@ -0,0 +1,51 @@ +package ai.chat2db.server.web.api.controller.ai.conversation.converter; + +import java.util.List; + +import ai.chat2db.server.domain.api.model.AiConversation; +import ai.chat2db.server.domain.api.model.AiConversationDetail; +import ai.chat2db.server.domain.api.model.AiMessage; +import ai.chat2db.server.domain.api.param.ai.AiConversationCreateParam; +import ai.chat2db.server.domain.api.param.ai.AiConversationQueryParam; +import ai.chat2db.server.web.api.controller.ai.conversation.request.AiConversationCreateRequest; +import ai.chat2db.server.web.api.controller.ai.conversation.request.AiConversationQueryRequest; +import ai.chat2db.server.web.api.controller.ai.conversation.vo.AiConversationDetailVO; +import ai.chat2db.server.web.api.controller.ai.conversation.vo.AiConversationVO; +import ai.chat2db.server.web.api.controller.ai.conversation.vo.AiMessageVO; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.factory.Mappers; + +@Mapper(componentModel = "spring") +public abstract class AiConversationWebConverter { + + public abstract AiConversationCreateParam req2param(AiConversationCreateRequest request); + + @Mappings({ + @Mapping(target = "userId", ignore = true), + @Mapping(target = "status", ignore = true) + }) + public abstract AiConversationQueryParam queryReq2param(AiConversationQueryRequest request); + + public abstract AiConversationVO dto2vo(AiConversation conversation); + + public abstract List dto2vo(List conversations); + + public abstract AiMessageVO dto2MessageVo(AiMessage message); + + public abstract List dto2MessageVo(List messages); + + public abstract AiConversationDetailVO detail2vo(AiConversationDetail detail); + + public AiConversationDetailVO detail2voWithMessages(AiConversationDetail detail) { + if (detail == null) { + return null; + } + AiConversationDetailVO vo = detail2vo(detail); + if (vo != null && detail.getMessages() != null) { + vo.setMessages(dto2MessageVo(detail.getMessages())); + } + return vo; + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/conversation/request/AiConversationCreateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/conversation/request/AiConversationCreateRequest.java new file mode 100644 index 000000000..0feae11eb --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/conversation/request/AiConversationCreateRequest.java @@ -0,0 +1,13 @@ +package ai.chat2db.server.web.api.controller.ai.conversation.request; + +import lombok.Data; + +@Data +public class AiConversationCreateRequest { + + private String conversationId; + private Long dataSourceId; + private String databaseName; + private String schemaName; + private String title; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/conversation/request/AiConversationQueryRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/conversation/request/AiConversationQueryRequest.java new file mode 100644 index 000000000..d9f3d5d51 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/conversation/request/AiConversationQueryRequest.java @@ -0,0 +1,12 @@ +package ai.chat2db.server.web.api.controller.ai.conversation.request; + +import ai.chat2db.server.tools.base.wrapper.request.PageQueryRequest; +import lombok.Data; + +@Data +public class AiConversationQueryRequest extends PageQueryRequest { + + private String searchKey; + private Long dataSourceId; + private String status; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/conversation/request/AiConversationRenameRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/conversation/request/AiConversationRenameRequest.java new file mode 100644 index 000000000..5849a1d5f --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/conversation/request/AiConversationRenameRequest.java @@ -0,0 +1,11 @@ +package ai.chat2db.server.web.api.controller.ai.conversation.request; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class AiConversationRenameRequest { + + @NotBlank + private String title; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/conversation/vo/AiConversationDetailVO.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/conversation/vo/AiConversationDetailVO.java new file mode 100644 index 000000000..5f3aa7ea0 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/conversation/vo/AiConversationDetailVO.java @@ -0,0 +1,12 @@ +package ai.chat2db.server.web.api.controller.ai.conversation.vo; + +import lombok.Data; + +import java.util.List; + +@Data +public class AiConversationDetailVO { + + private AiConversationVO conversation; + private List messages; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/conversation/vo/AiConversationVO.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/conversation/vo/AiConversationVO.java new file mode 100644 index 000000000..52419061f --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/conversation/vo/AiConversationVO.java @@ -0,0 +1,22 @@ +package ai.chat2db.server.web.api.controller.ai.conversation.vo; + +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class AiConversationVO { + + private Long id; + private String conversationId; + private String title; + private Long dataSourceId; + private String dataSourceName; + private String databaseName; + private String schemaName; + private Integer messageCount; + private String lastMessagePreview; + private String status; + private LocalDateTime gmtCreate; + private LocalDateTime gmtModified; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/conversation/vo/AiMessageVO.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/conversation/vo/AiMessageVO.java new file mode 100644 index 000000000..c1890460a --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/conversation/vo/AiMessageVO.java @@ -0,0 +1,17 @@ +package ai.chat2db.server.web.api.controller.ai.conversation.vo; + +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class AiMessageVO { + + private String messageId; + private String role; + private String content; + private String thinking; + private String promptType; + private Integer sequenceNo; + private LocalDateTime gmtCreate; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/converter/ChatConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/converter/ChatConverter.java index 1124b1a58..e08057351 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/converter/ChatConverter.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/converter/ChatConverter.java @@ -1,9 +1,6 @@ package ai.chat2db.server.web.api.controller.ai.converter; import ai.chat2db.server.domain.api.param.TableQueryParam; -import ai.chat2db.server.web.api.controller.ai.fastchat.embeddings.FastChatEmbeddingResponse; -import ai.chat2db.server.web.api.controller.ai.fastchat.embeddings.FastChatItem; -import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatCompletionsUsage; import ai.chat2db.server.web.api.controller.ai.request.ChatQueryRequest; import com.unfbx.chatgpt.entity.common.Usage; @@ -27,27 +24,4 @@ public abstract class ChatConverter { */ public abstract TableQueryParam chat2tableQuery(ChatQueryRequest request); - /** - * chat convert - * - * @param item - * @return - */ - public abstract FastChatItem item2ChatItem(Item item); - - /** - * usage convert - * - * @param usage - * @return - */ - public abstract FastChatCompletionsUsage usage2usage(Usage usage); - - /** - * response convert - * - * @param embeddingResponse - * @return - */ - public abstract FastChatEmbeddingResponse response2response(EmbeddingResponse embeddingResponse); } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/enums/PromptType.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/enums/PromptType.java index 9e9745c75..c1a1edd97 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/enums/PromptType.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/enums/PromptType.java @@ -17,7 +17,7 @@ public enum PromptType implements BaseEnum { /** * 自然语言转换成SQL */ - NL_2_SQL("将自然语言转换成SQL查询"), + NL_2_SQL("将自然语言转换成SQL"), /** * 解释SQL @@ -38,10 +38,64 @@ public enum PromptType implements BaseEnum { * text generation */ TEXT_GENERATION("文本生成"), + + /** + * 生成标题 + */ + TITLE_GENERATION("生成标题"), + + /** + * 选择需要查询的表 + */ + SELECT_TABLES("选择需要查询的表"), + + /** + * 自然语言转换成注释 + */ + NL_2_COMMENT("猜测表和字段注释"), + + NL_2_COMMENT_BATCH("批量猜测表注释"), + + /** + * 智能字段映射推荐 + */ + NL_2_FIELD_MAPPING("智能字段映射推荐"), + + /** + * 智能数据生成表达式推荐 + */ + NL_2_DATA_EXPRESSION("智能数据生成表达式推荐"), + + /** + * SQL错误修复 + */ + SQL_FIX("SQL错误修复"), + + /** + * SQL 补全 + */ + SQL_COMPLETION("SQL补全"), + + /** + * 自然语言转换成 Redis 命令 + */ + REDIS_NL_2_COMMAND("将自然语言转换成Redis命令"), ; final String description; + /** + * 判断是否为简单任务类型(可以使用快速模型) + * 简单任务通常不需要复杂的推理,如选表、生成标题等 + * + * @return true 如果是简单任务 + */ + public boolean isSimpleTask() { + return this == SELECT_TABLES || this == TITLE_GENERATION || this == NL_2_COMMENT_BATCH + || this == NL_2_COMMENT || this == NL_2_FIELD_MAPPING || this == NL_2_DATA_EXPRESSION + || this == SQL_COMPLETION; + } + PromptType(String description) { this.description = description; } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/client/FastChatAIClient.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/client/FastChatAIClient.java deleted file mode 100644 index 4a642c390..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/client/FastChatAIClient.java +++ /dev/null @@ -1,80 +0,0 @@ - -package ai.chat2db.server.web.api.controller.ai.fastchat.client; - -import ai.chat2db.server.domain.api.model.Config; -import ai.chat2db.server.domain.api.service.ConfigService; -import ai.chat2db.server.web.api.util.ApplicationContextUtil; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; - -/** - * @author moji - * @date 23/09/26 - */ -@Slf4j -public class FastChatAIClient { - - /** - * FASTCHAT OPENAI KEY - */ - public static final String FASTCHAT_API_KEY = "fastchat.chatgpt.apiKey"; - - /** - * FASTCHAT OPENAI HOST - */ - public static final String FASTCHAT_HOST = "fastchat.host"; - - /** - * FASTCHAT OPENAI model - */ - public static final String FASTCHAT_MODEL= "fastchat.model"; - - /** - * FASTCHAT OPENAI embedding model - */ - public static final String FASTCHAT_EMBEDDING_MODEL = "fastchat.embedding.model"; - - private static FastChatAIStreamClient FASTCHAT_AI_CLIENT; - - - public static FastChatAIStreamClient getInstance() { - if (FASTCHAT_AI_CLIENT != null) { - return FASTCHAT_AI_CLIENT; - } else { - return singleton(); - } - } - - private static FastChatAIStreamClient singleton() { - if (FASTCHAT_AI_CLIENT == null) { - synchronized (FastChatAIClient.class) { - if (FASTCHAT_AI_CLIENT == null) { - refresh(); - } - } - } - return FASTCHAT_AI_CLIENT; - } - - public static void refresh() { - String apiKey = ""; - String apiHost = ""; - String model = ""; - ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); - Config apiHostConfig = configService.find(FASTCHAT_HOST).getData(); - if (apiHostConfig != null && StringUtils.isNotBlank(apiHostConfig.getContent())) { - apiHost = apiHostConfig.getContent(); - } - Config config = configService.find(FASTCHAT_API_KEY).getData(); - if (config != null && StringUtils.isNotBlank(config.getContent())) { - apiKey = config.getContent(); - } - Config deployConfig = configService.find(FASTCHAT_MODEL).getData(); - if (deployConfig != null && StringUtils.isNotBlank(deployConfig.getContent())) { - model = deployConfig.getContent(); - } - FASTCHAT_AI_CLIENT = FastChatAIStreamClient.builder().apiKey(apiKey).apiHost(apiHost).model(model) - .build(); - } - -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/client/FastChatAIStreamClient.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/client/FastChatAIStreamClient.java deleted file mode 100644 index d3dcaadf2..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/client/FastChatAIStreamClient.java +++ /dev/null @@ -1,243 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.fastchat.client; - -import ai.chat2db.server.tools.common.exception.ParamBusinessException; -import ai.chat2db.server.web.api.controller.ai.fastchat.embeddings.FastChatEmbedding; -import ai.chat2db.server.web.api.controller.ai.fastchat.embeddings.FastChatEmbeddingResponse; -import ai.chat2db.server.web.api.controller.ai.fastchat.interceptor.FastChatHeaderAuthorizationInterceptor; -import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatCompletionsOptions; -import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatMessage; -import cn.hutool.http.ContentType; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import io.reactivex.Single; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import okhttp3.MediaType; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.sse.EventSource; -import okhttp3.sse.EventSourceListener; -import okhttp3.sse.EventSources; -import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang3.StringUtils; -import org.jetbrains.annotations.NotNull; -import retrofit2.Retrofit; -import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; -import retrofit2.converter.jackson.JacksonConverterFactory; - -import java.util.List; -import java.util.Objects; -import java.util.concurrent.TimeUnit; - -/** - * Fast Chat Aligned Client - * - * @author moji - */ -@Slf4j -public class FastChatAIStreamClient { - - /** - * apikey - */ - @Getter - @NotNull - private String apiKey; - - /** - * apiHost - */ - @Getter - @NotNull - private String apiHost; - - /** - * model - */ - @Getter - private String model; - - /** - * embeddingModel - */ - @Getter - private String embeddingModel; - - /** - * okHttpClient - */ - @Getter - private OkHttpClient okHttpClient; - - @Getter - private FastChatOpenAiApi fastChatOpenAiApi; - - - /** - * @param builder - */ - private FastChatAIStreamClient(Builder builder) { - this.apiKey = builder.apiKey; - this.apiHost = builder.apiHost; - this.model = builder.model; - this.embeddingModel = builder.embeddingModel; - if (Objects.isNull(builder.okHttpClient)) { - builder.okHttpClient = this.okHttpClient(); - } - okHttpClient = builder.okHttpClient; - this.fastChatOpenAiApi = new Retrofit.Builder() - .baseUrl(apiHost) - .client(okHttpClient) - .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) - .addConverterFactory(JacksonConverterFactory.create()) - .build().create(FastChatOpenAiApi.class); - } - - /** - * okhttpclient - */ - private OkHttpClient okHttpClient() { - OkHttpClient okHttpClient = new OkHttpClient - .Builder() - .addInterceptor(new FastChatHeaderAuthorizationInterceptor(this.apiKey)) - .connectTimeout(10, TimeUnit.SECONDS) - .writeTimeout(50, TimeUnit.SECONDS) - .readTimeout(50, TimeUnit.SECONDS) - .build(); - return okHttpClient; - } - - /** - * 构造 - * - * @return - */ - public static FastChatAIStreamClient.Builder builder() { - return new FastChatAIStreamClient.Builder(); - } - - /** - * builder - */ - public static final class Builder { - private String apiKey; - - private String apiHost; - - private String model; - - private String embeddingModel; - - /** - * OkhttpClient - */ - private OkHttpClient okHttpClient; - - public Builder() { - } - - public FastChatAIStreamClient.Builder apiKey(String apiKeyValue) { - this.apiKey = apiKeyValue; - return this; - } - - /** - * @param apiHostValue - * @return - */ - public FastChatAIStreamClient.Builder apiHost(String apiHostValue) { - this.apiHost = apiHostValue; - return this; - } - - /** - * @param modelValue - * @return - */ - public FastChatAIStreamClient.Builder model(String modelValue) { - this.model = modelValue; - return this; - } - - public FastChatAIStreamClient.Builder embeddingModel(String embeddingModelValue) { - this.embeddingModel = embeddingModelValue; - return this; - } - - public FastChatAIStreamClient.Builder okHttpClient(OkHttpClient val) { - this.okHttpClient = val; - return this; - } - - public FastChatAIStreamClient build() { - return new FastChatAIStreamClient(this); - } - - } - - /** - * 问答接口 stream 形式 - * - * @param chatMessages - * @param eventSourceListener - */ - public void streamCompletions(List chatMessages, EventSourceListener eventSourceListener) { - if (CollectionUtils.isEmpty(chatMessages)) { - log.error("param error:Fast Chat Prompt cannot be empty"); - throw new ParamBusinessException("prompt"); - } - if (Objects.isNull(eventSourceListener)) { - log.error("param error:FastChatEventSourceListener cannot be empty"); - throw new ParamBusinessException(); - } - log.info("Fast Chat AI, prompt:{}", chatMessages.get(chatMessages.size() - 1).getContent()); - try { - - FastChatCompletionsOptions chatCompletionsOptions = new FastChatCompletionsOptions(chatMessages); - chatCompletionsOptions.setStream(true); - chatCompletionsOptions.setModel(this.model); - - EventSource.Factory factory = EventSources.createFactory(this.okHttpClient); - ObjectMapper mapper = new ObjectMapper(); - mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - String requestBody = mapper.writeValueAsString(chatCompletionsOptions); - Request request = new Request.Builder() - .url(apiHost) - .post(RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), requestBody)) - .build(); - //创建事件 - EventSource eventSource = factory.newEventSource(request, eventSourceListener); - log.info("finish invoking fast chat ai"); - } catch (Exception e) { - log.error("fast chat ai error", e); - eventSourceListener.onFailure(null, e, null); - throw new ParamBusinessException(); - } - } - - /** - * Creates an embedding vector representing the input text. - * - * @param input - * @return EmbeddingResponse - */ - public FastChatEmbeddingResponse embeddings(String input) { - FastChatEmbedding embedding = FastChatEmbedding.builder().input(input).build(); - if (StringUtils.isNotBlank(this.embeddingModel)) { - embedding.setModel(this.embeddingModel); - } - return this.embeddings(embedding); - } - - /** - * Creates an embedding vector representing the input text. - * - * @param embedding - * @return EmbeddingResponse - */ - public FastChatEmbeddingResponse embeddings(FastChatEmbedding embedding) { - Single embeddings = this.fastChatOpenAiApi.embeddings(embedding); - return embeddings.blockingGet(); - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/client/FastChatOpenAiApi.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/client/FastChatOpenAiApi.java deleted file mode 100644 index ed12c5f54..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/client/FastChatOpenAiApi.java +++ /dev/null @@ -1,54 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.fastchat.client; - -import ai.chat2db.server.web.api.controller.ai.fastchat.embeddings.FastChatEmbedding; -import ai.chat2db.server.web.api.controller.ai.fastchat.embeddings.FastChatEmbeddingResponse; -import com.unfbx.chatgpt.entity.billing.CreditGrantsResponse; -import com.unfbx.chatgpt.entity.chat.ChatCompletion; -import com.unfbx.chatgpt.entity.chat.ChatCompletionResponse; -import com.unfbx.chatgpt.entity.common.DeleteResponse; -import com.unfbx.chatgpt.entity.common.OpenAiResponse; -import com.unfbx.chatgpt.entity.completions.Completion; -import com.unfbx.chatgpt.entity.completions.CompletionResponse; -import com.unfbx.chatgpt.entity.edits.Edit; -import com.unfbx.chatgpt.entity.edits.EditResponse; -import com.unfbx.chatgpt.entity.embeddings.Embedding; -import com.unfbx.chatgpt.entity.embeddings.EmbeddingResponse; -import com.unfbx.chatgpt.entity.engines.Engine; -import com.unfbx.chatgpt.entity.files.File; -import com.unfbx.chatgpt.entity.files.UploadFileResponse; -import com.unfbx.chatgpt.entity.fineTune.Event; -import com.unfbx.chatgpt.entity.fineTune.FineTune; -import com.unfbx.chatgpt.entity.fineTune.FineTuneResponse; -import com.unfbx.chatgpt.entity.images.Image; -import com.unfbx.chatgpt.entity.images.ImageResponse; -import com.unfbx.chatgpt.entity.models.Model; -import com.unfbx.chatgpt.entity.models.ModelResponse; -import com.unfbx.chatgpt.entity.moderations.Moderation; -import com.unfbx.chatgpt.entity.moderations.ModerationResponse; -import com.unfbx.chatgpt.entity.whisper.WhisperResponse; -import io.reactivex.Single; -import okhttp3.MultipartBody; -import okhttp3.RequestBody; -import okhttp3.ResponseBody; -import retrofit2.http.*; - -import java.util.Map; - -/** - * 描述: open ai官方api接口 - * - * @author https:www.unfbx.com - * 2023-02-15 - */ -public interface FastChatOpenAiApi { - - /** - * Creates an embedding vector representing the input text. - * - * @param embedding - * @return Single EmbeddingResponse - */ - @POST("v1/embeddings") - Single embeddings(@Body FastChatEmbedding embedding); - -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/embeddings/FastChatEmbedding.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/embeddings/FastChatEmbedding.java deleted file mode 100644 index 54d147196..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/embeddings/FastChatEmbedding.java +++ /dev/null @@ -1,73 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.fastchat.embeddings; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.unfbx.chatgpt.exception.BaseException; -import com.unfbx.chatgpt.exception.CommonError; -import lombok.*; -import lombok.extern.slf4j.Slf4j; - -import java.io.Serializable; -import java.util.Objects; - -/** - * 描述: - * - * @author https:www.unfbx.com - * 2023-02-15 - */ -@Getter -@Slf4j -@Builder -@JsonInclude(JsonInclude.Include.NON_NULL) -@NoArgsConstructor -@AllArgsConstructor -public class FastChatEmbedding implements Serializable { - @NonNull - @Builder.Default - private String model = Model.TEXT_EMBEDDING_ADA_002.getName(); - /** - * 必选项:长度不能超过:8192 - */ - @NonNull - private String input; - - private String user; - - public void setModel(Model model) { - if (Objects.isNull(model)) { - model = Model.TEXT_EMBEDDING_ADA_002; - } - this.model = model.getName(); - } - - public void setModel(String model) { - if (Objects.isNull(model)) { - model = Model.TEXT_EMBEDDING_ADA_002.getName(); - } - this.model = model; - } - - public void setInput(String input) { - if (input == null || "".equals(input)) { - log.error("input不能为空"); - throw new BaseException(CommonError.PARAM_ERROR); - } - if (input.length() > 8192) { - log.error("input超长"); - throw new BaseException(CommonError.PARAM_ERROR); - } - this.input = input; - } - - public void setUser(String user) { - this.user = user; - } - - @Getter - @AllArgsConstructor - public enum Model { - TEXT_EMBEDDING_ADA_002("text-embedding-ada-002"), - ; - private String name; - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/embeddings/FastChatEmbeddingResponse.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/embeddings/FastChatEmbeddingResponse.java deleted file mode 100644 index a364ffc23..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/embeddings/FastChatEmbeddingResponse.java +++ /dev/null @@ -1,22 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.fastchat.embeddings; - -import com.unfbx.chatgpt.entity.common.Usage; -import lombok.Data; - -import java.io.Serializable; -import java.util.List; - -/** - * 描述: - * - * @author https:www.unfbx.com - * 2023-02-15 - */ -@Data -public class FastChatEmbeddingResponse implements Serializable { - - private String object; - private List data; - private String model; - private Usage usage; -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/embeddings/FastChatItem.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/embeddings/FastChatItem.java deleted file mode 100644 index d9df8df2b..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/embeddings/FastChatItem.java +++ /dev/null @@ -1,14 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.fastchat.embeddings; - -import lombok.Data; - -import java.io.Serializable; -import java.math.BigDecimal; -import java.util.List; - -@Data -public class FastChatItem implements Serializable { - private String object; - private List embedding; - private Integer index; -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/interceptor/FastChatHeaderAuthorizationInterceptor.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/interceptor/FastChatHeaderAuthorizationInterceptor.java deleted file mode 100644 index f91719612..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/interceptor/FastChatHeaderAuthorizationInterceptor.java +++ /dev/null @@ -1,40 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.fastchat.interceptor; - -import cn.hutool.core.util.RandomUtil; -import cn.hutool.http.ContentType; -import cn.hutool.http.Header; -import com.google.common.collect.Lists; -import lombok.Getter; -import okhttp3.Interceptor; -import okhttp3.Request; -import okhttp3.Response; - -import java.io.IOException; - -/** - * header apikey - * - * @author grt - * @since 2023-03-23 - */ -@Getter -public class FastChatHeaderAuthorizationInterceptor implements Interceptor { - - private String apiKey; - - public FastChatHeaderAuthorizationInterceptor(String apiKey) { - this.apiKey = apiKey; - } - - @Override - public Response intercept(Chain chain) throws IOException { - Request original = chain.request(); - Request request = original.newBuilder() - // replace to your corresponding field and value - .header(Header.AUTHORIZATION.getValue(), "Bearer " + apiKey) - .header(Header.CONTENT_TYPE.getValue(), ContentType.JSON.getValue()) - .method(original.method(), original.body()) - .build(); - return chain.proceed(request); - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/listener/FastChatAIEventSourceListener.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/listener/FastChatAIEventSourceListener.java deleted file mode 100644 index 7ad62178f..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/listener/FastChatAIEventSourceListener.java +++ /dev/null @@ -1,148 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.fastchat.listener; - -import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatChoice; -import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatCompletions; -import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatCompletionsUsage; -import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatMessage; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.unfbx.chatgpt.entity.chat.Message; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import okhttp3.Response; -import okhttp3.ResponseBody; -import okhttp3.sse.EventSource; -import okhttp3.sse.EventSourceListener; -import org.apache.commons.lang3.StringUtils; -import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; - -import java.io.IOException; -import java.util.Objects; - -/** - * 描述:OpenAIEventSourceListener - * - * @author https:www.unfbx.com - * @date 2023-02-22 - */ -@Slf4j -public class FastChatAIEventSourceListener extends EventSourceListener { - - private SseEmitter sseEmitter; - - private ObjectMapper mapper = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); - - public FastChatAIEventSourceListener(SseEmitter sseEmitter) { - this.sseEmitter = sseEmitter; - } - - /** - * {@inheritDoc} - */ - @Override - public void onOpen(EventSource eventSource, Response response) { - log.info("Fast Chat Sse connecting..."); - } - - /** - * {@inheritDoc} - */ - @SneakyThrows - @Override - public void onEvent(EventSource eventSource, String id, String type, String data) { - log.info("Fast Chat AI response data:{}", data); - if (data.equals("[DONE]")) { - log.info("Fast Chat AI closed"); - sseEmitter.send(SseEmitter.event() - .id("[DONE]") - .data("[DONE]") - .reconnectTime(3000)); - sseEmitter.complete(); - return; - } - - FastChatCompletions chatCompletions = mapper.readValue(data, FastChatCompletions.class); - String text = ""; - log.info("Model={} is created at {}.", chatCompletions.getId(), - chatCompletions.getCreated()); - for (FastChatChoice choice : chatCompletions.getChoices()) { - FastChatMessage message = choice.getMessage(); - if (message != null) { - log.info("Index: {}, Chat Role: {}", choice.getIndex(), message.getRole()); - if (message.getContent() != null) { - text = message.getContent(); - } - } - } - - FastChatCompletionsUsage usage = chatCompletions.getUsage(); - if (usage != null) { - log.info( - "Usage: number of prompt token is {}, number of completion token is {}, and number of total " - + "tokens in request and response is {}.%n", usage.getPromptTokens(), - usage.getCompletionTokens(), usage.getTotalTokens()); - } - - Message message = new Message(); - message.setContent(text); - sseEmitter.send(SseEmitter.event() - .id(null) - .data(message) - .reconnectTime(3000)); - } - - @Override - public void onClosed(EventSource eventSource) { - try { - sseEmitter.send(SseEmitter.event() - .id("[DONE]") - .data("[DONE]")); - } catch (IOException e) { - throw new RuntimeException(e); - } - sseEmitter.complete(); - log.info("FastChatAI close sse connection..."); - } - - @Override - public void onFailure(EventSource eventSource, Throwable t, Response response) { - try { - if (Objects.isNull(response)) { - String message = t.getMessage(); - Message sseMessage = new Message(); - sseMessage.setContent(message); - sseEmitter.send(SseEmitter.event() - .id("[ERROR]") - .data(sseMessage)); - sseEmitter.send(SseEmitter.event() - .id("[DONE]") - .data("[DONE]")); - sseEmitter.complete(); - return; - } - ResponseBody body = response.body(); - String bodyString = Objects.nonNull(t) ? t.getMessage() : ""; - if (Objects.nonNull(body)) { - bodyString = body.string(); - if (StringUtils.isBlank(bodyString) && Objects.nonNull(t)) { - bodyString = t.getMessage(); - } - log.error("Fast Chat AI sse response:{}", bodyString); - } else { - log.error("Fast Chat AI sse response:{},error:{}", response, t); - } - eventSource.cancel(); - Message message = new Message(); - message.setContent("Fast Chat AI error:" + bodyString); - sseEmitter.send(SseEmitter.event() - .id("[ERROR]") - .data(message)); - sseEmitter.send(SseEmitter.event() - .id("[DONE]") - .data("[DONE]")); - sseEmitter.complete(); - } catch (Exception exception) { - log.error("Fast Chat AI send data error:", exception); - } - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatChoice.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatChoice.java deleted file mode 100644 index 5ac0c364f..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatChoice.java +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -// Code generated by Microsoft (R) AutoRest Code Generator. -package ai.chat2db.server.web.api.controller.ai.fastchat.model; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; - -/** - * The representation of a single prompt completion as part of an overall completions request. Generally, `n` choices - * are generated per provided prompt with a default value of 1. Token limits and other settings may limit the number of - * choices generated. - */ -@Data -public final class FastChatChoice { - - /* - * The generated text for a given completions prompt. - */ - @JsonProperty(value = "text") - private String text; - - /* - * The ordered index associated with this completions choice. - */ - @JsonProperty(value = "index") - private int index; - - /* - * The log probabilities model for tokens associated with this completions choice. - */ - @JsonProperty(value = "message") - private FastChatMessage message; - - /* - * Reason for finishing - */ - @JsonProperty(value = "finish_reason") - private FastChatCompletionsFinishReason finishReason; - - /** - * Creates an instance of Choice class. - * - * @param text the text value to set. - * @param index the index value to set. - * @param message the message value to set - * @param finishReason the finishReason value to set. - */ - @JsonCreator - private FastChatChoice( - @JsonProperty(value = "text") String text, - @JsonProperty(value = "index") int index, - @JsonProperty(value = "message") FastChatMessage message, - @JsonProperty(value = "finish_reason") FastChatCompletionsFinishReason finishReason) { - this.text = text; - this.index = index; - this.message = message; - this.finishReason = finishReason; - } - - /** - * Get the text property: The generated text for a given completions prompt. - * - * @return the text value. - */ - public String getText() { - return this.text; - } - - /** - * Get the index property: The ordered index associated with this completions choice. - * - * @return the index value. - */ - public int getIndex() { - return this.index; - } - - /** - * Get the logprobs property: The log probabilities model for tokens associated with this completions choice. - * - * @return the logprobs value. - */ - public FastChatMessage getMessage() { - return this.message; - } - - /** - * Get the finishReason property: Reason for finishing. - * - * @return the finishReason value. - */ - public FastChatCompletionsFinishReason getFinishReason() { - return this.finishReason; - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatCompletions.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatCompletions.java deleted file mode 100644 index fa63281af..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatCompletions.java +++ /dev/null @@ -1,110 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.fastchat.model; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; - -import java.util.List; - -@Data -public class FastChatCompletions { - - /* - * A unique identifier associated with this chat completions response. - */ - private String id; - - /* - * The first timestamp associated with generation activity for this completions response, - * represented as seconds since the beginning of the Unix epoch of 00:00 on 1 Jan 1970. - */ - private int created; - - /** - * model - */ - private String model; - - /** - * object - */ - private String object; - - /* - * The collection of completions choices associated with this completions response. - * Generally, `n` choices are generated per provided prompt with a default value of 1. - * Token limits and other settings may limit the number of choices generated. - */ - @JsonProperty(value = "choices") - private List choices; - - /* - * Usage information for tokens processed and generated as part of this completions operation. - */ - private FastChatCompletionsUsage usage; - - /** - * Creates an instance of ChatCompletions class. - * - * @param id the id value to set. - * @param created the created value to set. - * @param choices the choices value to set. - * @param usage the usage value to set. - */ - @JsonCreator - private FastChatCompletions( - @JsonProperty(value = "id") String id, - @JsonProperty(value = "created") int created, - @JsonProperty(value = "model") String model, - @JsonProperty(value = "object") String object, - @JsonProperty(value = "choices") List choices, - @JsonProperty(value = "usage") FastChatCompletionsUsage usage) { - this.id = id; - this.created = created; - this.model = model; - this.object = object; - this.choices = choices; - this.usage = usage; - } - - /** - * Get the id property: A unique identifier associated with this chat completions response. - * - * @return the id value. - */ - public String getId() { - return this.id; - } - - /** - * Get the created property: The first timestamp associated with generation activity for this completions response, - * represented as seconds since the beginning of the Unix epoch of 00:00 on 1 Jan 1970. - * - * @return the created value. - */ - public int getCreated() { - return this.created; - } - - /** - * Get the choices property: The collection of completions choices associated with this completions response. - * Generally, `n` choices are generated per provided prompt with a default value of 1. Token limits and other - * settings may limit the number of choices generated. - * - * @return the choices value. - */ - public List getChoices() { - return this.choices; - } - - /** - * Get the usage property: Usage information for tokens processed and generated as part of this completions - * operation. - * - * @return the usage value. - */ - public FastChatCompletionsUsage getUsage() { - return this.usage; - } - -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatCompletionsFinishReason.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatCompletionsFinishReason.java deleted file mode 100644 index b2c61795b..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatCompletionsFinishReason.java +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -// Code generated by Microsoft (R) AutoRest Code Generator. -package ai.chat2db.server.web.api.controller.ai.fastchat.model; - -import com.fasterxml.jackson.annotation.JsonCreator; - -import java.util.Collection; - -/** Representation of the manner in which a completions response concluded. */ -public final class FastChatCompletionsFinishReason extends FastChatExpandableStringEnum { - - /** Completions ended normally and reached its end of token generation. */ - public static final FastChatCompletionsFinishReason STOPPED = fromString("stopped"); - - /** Completions exhausted available token limits before generation could complete. */ - public static final FastChatCompletionsFinishReason TOKEN_LIMIT_REACHED = fromString("tokenLimitReached"); - - /** - * Completions generated a response that was identified as potentially sensitive per content moderation policies. - */ - public static final FastChatCompletionsFinishReason CONTENT_FILTERED = fromString("contentFiltered"); - - /** - * Creates a new instance of CompletionsFinishReason value. - * - * @deprecated Use the {@link #fromString(String)} factory method. - */ - @Deprecated - public FastChatCompletionsFinishReason() {} - - /** - * Creates or finds a CompletionsFinishReason from its string representation. - * - * @param name a name to look for. - * @return the corresponding CompletionsFinishReason. - */ - @JsonCreator - public static FastChatCompletionsFinishReason fromString(String name) { - return fromString(name, FastChatCompletionsFinishReason.class); - } - - /** - * Gets known CompletionsFinishReason values. - * - * @return known CompletionsFinishReason values. - */ - public static Collection values() { - return values(FastChatCompletionsFinishReason.class); - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatCompletionsOptions.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatCompletionsOptions.java deleted file mode 100644 index a4e8a328e..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatCompletionsOptions.java +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -// Code generated by Microsoft (R) AutoRest Code Generator. -package ai.chat2db.server.web.api.controller.ai.fastchat.model; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; - -import java.util.List; - -/** - * The configuration information for a chat completions request. Completions support a wide variety of tasks and - * generate text that continues from or "completes" provided prompt data. - */ -@Data -public final class FastChatCompletionsOptions { - - /* - * The collection of context messages associated with this chat completions request. - * Typical usage begins with a chat message for the System role that provides instructions for - * the behavior of the assistant, followed by alternating messages between the User and - * Assistant roles. - */ - private List messages; - - - /* - * A value indicating whether chat completions should be streamed for this request. - */ - private Boolean stream; - // - /* - * The model name to provide as part of this completions request. - * Not applicable to Fast Chat AI, where deployment information should be included in the Fast Chat - * resource URI that's connected to. - */ - private String model; - - /** - * Creates an instance of ChatCompletionsOptions class. - * - * @param messages the messages value to set. - */ - @JsonCreator - public FastChatCompletionsOptions(@JsonProperty(value = "messages") List messages) { - this.messages = messages; - } - - - /** - * Get the stream property: A value indicating whether chat completions should be streamed for this request. - * - * @return the stream value. - */ - public Boolean isStream() { - return this.stream; - } - - /** - * Set the stream property: A value indicating whether chat completions should be streamed for this request. - * - * @param stream the stream value to set. - * @return the ChatCompletionsOptions object itself. - */ - public FastChatCompletionsOptions setStream(Boolean stream) { - this.stream = stream; - return this; - } - - /** - * Get the model property: The model name to provide as part of this completions request. Not applicable to Fast Chat AI, - * where deployment information should be included in the Fast Chat AI resource URI that's connected to. - * - * @return the model value. - */ - public String getModel() { - return this.model; - } - - /** - * Set the model property: The model name to provide as part of this completions request. Not applicable to Fast Chat AI, - * where deployment information should be included in the Fast Chat AI resource URI that's connected to. - * - * @param model the model value to set. - * @return the ChatCompletionsOptions object itself. - */ - public FastChatCompletionsOptions setModel(String model) { - this.model = model; - return this; - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatCompletionsUsage.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatCompletionsUsage.java deleted file mode 100644 index bb8b265bc..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatCompletionsUsage.java +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -// Code generated by Microsoft (R) AutoRest Code Generator. -package ai.chat2db.server.web.api.controller.ai.fastchat.model; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; -import lombok.NoArgsConstructor; - -/** - * Representation of the token counts processed for a completions request. Counts consider all tokens across prompts, - * choices, choice alternates, best_of generations, and other consumers. - */ -@Data -@NoArgsConstructor -public final class FastChatCompletionsUsage { - - /* - * The number of tokens generated across all completions emissions. - */ - @JsonProperty(value = "completion_tokens") - private int completionTokens; - - /* - * The number of tokens in the provided prompts for the completions request. - */ - @JsonProperty(value = "prompt_tokens") - private int promptTokens; - - /* - * The total number of tokens processed for the completions request and response. - */ - @JsonProperty(value = "total_tokens") - private int totalTokens; - - /** - * Creates an instance of CompletionsUsage class. - * - * @param completionTokens the completionTokens value to set. - * @param promptTokens the promptTokens value to set. - * @param totalTokens the totalTokens value to set. - */ - @JsonCreator - private FastChatCompletionsUsage( - @JsonProperty(value = "completion_tokens") int completionTokens, - @JsonProperty(value = "prompt_tokens") int promptTokens, - @JsonProperty(value = "total_tokens") int totalTokens) { - this.completionTokens = completionTokens; - this.promptTokens = promptTokens; - this.totalTokens = totalTokens; - } - - /** - * Get the completionTokens property: The number of tokens generated across all completions emissions. - * - * @return the completionTokens value. - */ - public int getCompletionTokens() { - return this.completionTokens; - } - - /** - * Get the promptTokens property: The number of tokens in the provided prompts for the completions request. - * - * @return the promptTokens value. - */ - public int getPromptTokens() { - return this.promptTokens; - } - - /** - * Get the totalTokens property: The total number of tokens processed for the completions request and response. - * - * @return the totalTokens value. - */ - public int getTotalTokens() { - return this.totalTokens; - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatExpandableStringEnum.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatExpandableStringEnum.java deleted file mode 100644 index ffde27bb7..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatExpandableStringEnum.java +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package ai.chat2db.server.web.api.controller.ai.fastchat.model; - -import ai.chat2db.server.web.api.controller.ai.azure.util.AzureReflectionUtils; -import com.fasterxml.jackson.annotation.JsonValue; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; - -import static java.lang.invoke.MethodType.methodType; - -/** - * Base implementation for expandable, single string enums. - * - * @param a specific expandable enum type - */ -public abstract class FastChatExpandableStringEnum> { - private static final Map, MethodHandle> CONSTRUCTORS = new ConcurrentHashMap<>(); - private static final Map, ConcurrentHashMap>> VALUES - = new ConcurrentHashMap<>(); - - private static final Logger LOGGER = LoggerFactory.getLogger(FastChatExpandableStringEnum.class); - private String name; - private Class clazz; - - /** - * Creates a new instance of {@link FastChatExpandableStringEnum} without a {@link #toString()} value. - *

- * This constructor shouldn't be called as it will produce a {@link FastChatExpandableStringEnum} which doesn't - * have a String enum value. - * - * @deprecated Use the {@link #fromString(String, Class)} factory method. - */ - @Deprecated - public FastChatExpandableStringEnum() { - } - - /** - * Creates an instance of the specific expandable string enum from a String. - * - * @param name The value to create the instance from. - * @param clazz The class of the expandable string enum. - * @param the class of the expandable string enum. - * @return The expandable string enum instance. - * - * @throws RuntimeException wrapping implementation class constructor exception (if any is thrown). - */ - @SuppressWarnings({"unchecked", "deprecation"}) - protected static > T fromString(String name, Class clazz) { - if (name == null) { - return null; - } - - ConcurrentHashMap clazzValues = VALUES.computeIfAbsent(clazz, key -> new ConcurrentHashMap<>()); - T value = (T) clazzValues.get(name); - - if (value != null) { - return value; - } else { - MethodHandle ctor = CONSTRUCTORS.computeIfAbsent(clazz, FastChatExpandableStringEnum::getDefaultConstructor); - - if (ctor == null) { - // logged in ExpandableStringEnum::getDefaultConstructor - return null; - } - - try { - value = (T) ctor.invoke(); - } catch (Throwable e) { - LOGGER.warn("Failed to create {}, default constructor threw exception", clazz.getName(), e); - return null; - } - - return value.nameAndAddValue(name, value, clazz); - } - } - - private static MethodHandle getDefaultConstructor(Class clazz) { - try { - MethodHandles.Lookup lookup = AzureReflectionUtils.getLookupToUse(clazz); - return lookup.findConstructor(clazz, methodType(void.class)); - } catch (NoSuchMethodException | IllegalAccessException e) { - LOGGER.info("Can't find or access default constructor for {}", clazz.getName(), e); - } catch (Exception e) { - LOGGER.info("Failed to get lookup for {}", clazz.getName(), e); - } - - return null; - } - - @SuppressWarnings("unchecked") - T nameAndAddValue(String name, T value, Class clazz) { - this.name = name; - this.clazz = clazz; - - ((ConcurrentHashMap) VALUES.get(clazz)).put(name, value); - return (T) this; - } - - /** - * Gets a collection of all known values to an expandable string enum type. - * - * @param clazz the class of the expandable string enum. - * @param the class of the expandable string enum. - * @return A collection of all known values for the given {@code clazz}. - */ - @SuppressWarnings("unchecked") - protected static > Collection values(Class clazz) { - return new ArrayList((Collection) VALUES.getOrDefault(clazz, new ConcurrentHashMap<>()).values()); - } - - @Override - @JsonValue - public String toString() { - return this.name; - } - - @Override - public int hashCode() { - return Objects.hash(this.clazz, this.name); - } - - @SuppressWarnings("unchecked") - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } else if (clazz == null || !clazz.isAssignableFrom(obj.getClass())) { - return false; - } else if (obj == this) { - return true; - } else if (this.name == null) { - return ((FastChatExpandableStringEnum) obj).name == null; - } else { - return this.name.equals(((FastChatExpandableStringEnum) obj).name); - } - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatMessage.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatMessage.java deleted file mode 100644 index d74d3c1be..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatMessage.java +++ /dev/null @@ -1,61 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.fastchat.model; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; - -@Data -public class FastChatMessage { - - - /* - * The role associated with this message payload. - */ - @JsonProperty(value = "role") - private FastChatRole role; - - /* - * The text associated with this message payload. - */ - @JsonProperty(value = "content") - private String content; - - /** - * Creates an instance of ChatMessage class. - * - * @param role the role value to set. - */ - @JsonCreator - public FastChatMessage(@JsonProperty(value = "role") FastChatRole role) { - this.role = role; - } - - /** - * Get the role property: The role associated with this message payload. - * - * @return the role value. - */ - public FastChatRole getRole() { - return this.role; - } - - /** - * Get the content property: The text associated with this message payload. - * - * @return the content value. - */ - public String getContent() { - return this.content; - } - - /** - * Set the content property: The text associated with this message payload. - * - * @param content the content value to set. - * @return the ChatMessage object itself. - */ - public FastChatMessage setContent(String content) { - this.content = content; - return this; - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatRole.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatRole.java deleted file mode 100644 index 41069d969..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatRole.java +++ /dev/null @@ -1,46 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.fastchat.model; - -import com.fasterxml.jackson.annotation.JsonCreator; - -import java.util.Collection; - -public class FastChatRole extends FastChatExpandableStringEnum { - - /** The role that instructs or sets the behavior of the assistant. */ - public static final FastChatRole SYSTEM = fromString("system"); - - /** The role that provides responses to system-instructed, user-prompted input. */ - public static final FastChatRole ASSISTANT = fromString("assistant"); - - /** The role that provides input for chat completions. */ - public static final FastChatRole USER = fromString("user"); - - /** - * Creates a new instance of ChatRole value. - * - * @deprecated Use the {@link #fromString(String)} factory method. - */ - @Deprecated - public FastChatRole() {} - - /** - * Creates or finds a ChatRole from its string representation. - * - * @param name a name to look for. - * @return the corresponding ChatRole. - */ - @JsonCreator - public static FastChatRole fromString(String name) { - return fromString(name, FastChatRole.class); - } - - - /** - * Gets known ChatRole values. - * - * @return known ChatRole values. - */ - public static Collection values() { - return values(FastChatRole.class); - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/openai/client/OpenAIClient.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/openai/client/OpenAIClient.java deleted file mode 100644 index 9ebf711c2..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/openai/client/OpenAIClient.java +++ /dev/null @@ -1,117 +0,0 @@ - -package ai.chat2db.server.web.api.controller.ai.openai.client; - -import java.net.InetSocketAddress; -import java.net.Proxy; -import java.util.Objects; - -import ai.chat2db.server.domain.api.model.Config; -import ai.chat2db.server.domain.api.service.ConfigService; - -import ai.chat2db.server.web.api.util.ApplicationContextUtil; -import com.google.common.collect.Lists; -import com.unfbx.chatgpt.OpenAiStreamClient; -import com.unfbx.chatgpt.constant.OpenAIConst; -import lombok.extern.slf4j.Slf4j; -import okhttp3.OkHttpClient; -import org.apache.commons.lang3.StringUtils; - -/** - * @author jipengfei - * @version : OpenAIClient.java - */ -@Slf4j -public class OpenAIClient { - - public static final String OPENAI_KEY = "chatgpt.apiKey"; - - /** - * OPENAI接口域名 - */ - public static final String OPENAI_HOST = "chatgpt.apiHost"; - - /** - * 代理IP - */ - public static final String PROXY_HOST = "chatgpt.proxy.host"; - - /** - * 代理端口 - */ - public static final String PROXY_PORT = "chatgpt.proxy.port"; - - private static OpenAiStreamClient OPEN_AI_STREAM_CLIENT; - private static String apiKey; - - public static OpenAiStreamClient getInstance() { - if (OPEN_AI_STREAM_CLIENT != null) { - return OPEN_AI_STREAM_CLIENT; - } else { - return singleton(); - } - } - - private static OpenAiStreamClient singleton() { - if (OPEN_AI_STREAM_CLIENT == null) { - synchronized (OpenAIClient.class) { - if (OPEN_AI_STREAM_CLIENT == null) { - refresh(); - } - } - } - return OPEN_AI_STREAM_CLIENT; - } - - public static void refresh() { - String apikey; - String apiHost = ApplicationContextUtil.getProperty(OPENAI_HOST); - if (StringUtils.isBlank(apiHost)) { - apiHost = OpenAIConst.OPENAI_HOST; - } - ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); - Config apiHostConfig = configService.find(OPENAI_HOST).getData(); - if (apiHostConfig != null) { - apiHost = apiHostConfig.getContent(); - } - Config config = configService.find(OPENAI_KEY).getData(); - if (config != null) { - apikey = config.getContent(); - } else { - apikey = ApplicationContextUtil.getProperty(OPENAI_KEY); - } - String host = System.getProperty("http.proxyHost"); - Config hostConfig = configService.find(PROXY_HOST).getData(); - if (hostConfig != null) { - host = hostConfig.getContent(); - } - Integer port = Objects.nonNull(System.getProperty("http.proxyPort")) ? Integer.valueOf( - System.getProperty("http.proxyPort")) : null; - Config portConfig = configService.find(PROXY_PORT).getData(); - if (portConfig != null && StringUtils.isNotBlank(portConfig.getContent())) { - port = Integer.valueOf(portConfig.getContent()); - } - log.info("refresh openai apikey:{}", maskApiKey(apikey)); - if (Objects.nonNull(host) && Objects.nonNull(port)) { - Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(host, port)); - OkHttpClient okHttpClient = new OkHttpClient.Builder().proxy(proxy).build(); - OPEN_AI_STREAM_CLIENT = OpenAiStreamClient.builder().apiHost(apiHost).apiKey( - Lists.newArrayList(apikey)).okHttpClient(okHttpClient).build(); - } else { - OPEN_AI_STREAM_CLIENT = OpenAiStreamClient.builder().apiHost(apiHost).apiKey( - Lists.newArrayList(apikey)).build(); - } - apiKey = apikey; - } - - private static String maskApiKey(String input) { - if (input == null) { - return input; - } - - StringBuilder maskedString = new StringBuilder(input); - for (int i = input.length() / 4; i < input.length() / 2; i++) { - maskedString.setCharAt(i, '*'); - } - return maskedString.toString(); - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/openai/listener/OpenAIEventSourceListener.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/openai/listener/OpenAIEventSourceListener.java deleted file mode 100644 index ccadf6d68..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/openai/listener/OpenAIEventSourceListener.java +++ /dev/null @@ -1,121 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.openai.listener; - -import java.util.Objects; - -import ai.chat2db.server.web.api.controller.ai.response.ChatCompletionResponse; - -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.unfbx.chatgpt.entity.chat.Message; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import okhttp3.Response; -import okhttp3.ResponseBody; -import okhttp3.sse.EventSource; -import okhttp3.sse.EventSourceListener; -import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; - -/** - * 描述:OpenAIEventSourceListener - * - * @author https:www.unfbx.com - * @date 2023-02-22 - */ -@Slf4j -public class OpenAIEventSourceListener extends EventSourceListener { - - private SseEmitter sseEmitter; - - public OpenAIEventSourceListener(SseEmitter sseEmitter) { - this.sseEmitter = sseEmitter; - } - - /** - * {@inheritDoc} - */ - @Override - public void onOpen(EventSource eventSource, Response response) { - log.info("OpenAI建立sse连接..."); - } - - /** - * {@inheritDoc} - */ - @SneakyThrows - @Override - public void onEvent(EventSource eventSource, String id, String type, String data) { - log.info("OpenAI返回数据:{}", data); - if (data.equals("[DONE]")) { - log.info("OpenAI返回数据结束了"); - sseEmitter.send(SseEmitter.event() - .id("[DONE]") - .data("[DONE]") - .reconnectTime(3000)); - sseEmitter.complete(); - return; - } - ObjectMapper mapper = new ObjectMapper(); - mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - // 读取Json - ChatCompletionResponse completionResponse = mapper.readValue(data, ChatCompletionResponse.class); - String text = completionResponse.getChoices().get(0).getDelta() == null - ? completionResponse.getChoices().get(0).getText() - : completionResponse.getChoices().get(0).getDelta().getContent(); - Message message = new Message(); - if (text != null) { - message.setContent(text); - sseEmitter.send(SseEmitter.event() - .id(completionResponse.getId()) - .data(message) - .reconnectTime(3000)); - } - } - - @Override - public void onClosed(EventSource eventSource) { - sseEmitter.complete(); - log.info("OpenAI关闭sse连接..."); - } - - @Override - public void onFailure(EventSource eventSource, Throwable t, Response response) { - try { - if (Objects.isNull(response)) { - String message = t.getMessage(); - if ("No route to host".equals(message)) { - message = "网络连接超时,请百度自行解决网络问题"; - } - Message sseMessage = new Message(); - sseMessage.setContent(message); - sseEmitter.send(SseEmitter.event() - .id("[ERROR]") - .data(sseMessage)); - sseEmitter.send(SseEmitter.event() - .id("[DONE]") - .data("[DONE]")); - sseEmitter.complete(); - return; - } - ResponseBody body = response.body(); - String bodyString = null; - if (Objects.nonNull(body)) { - bodyString = body.string(); - log.error("OpenAI sse连接异常data:{}", bodyString, t); - } else { - log.error("OpenAI sse连接异常data:{}", response, t); - } - eventSource.cancel(); - Message message = new Message(); - message.setContent("出现异常,请在帮助中查看详细日志:" + bodyString); - sseEmitter.send(SseEmitter.event() - .id("[ERROR]") - .data(message)); - sseEmitter.send(SseEmitter.event() - .id("[DONE]") - .data("[DONE]")); - sseEmitter.complete(); - } catch (Exception exception) { - log.error("发送数据异常:", exception); - } - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/prompt/PreviousSqlResolver.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/prompt/PreviousSqlResolver.java new file mode 100644 index 000000000..6c8aaa108 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/prompt/PreviousSqlResolver.java @@ -0,0 +1,53 @@ +package ai.chat2db.server.web.api.controller.ai.prompt; + +import java.util.List; + +import ai.chat2db.server.domain.api.model.AiMessage; +import ai.chat2db.server.domain.api.service.AiConversationService; +import ai.chat2db.server.web.api.controller.ai.request.ChatQueryRequest; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * 解析连续对话中的上一版 SQL。 + */ +@Component +@Slf4j +public class PreviousSqlResolver { + + @Autowired + private AiConversationService aiConversationService; + + public String resolve(ChatQueryRequest request) { + if (request == null) { + return null; + } + if (StringUtils.isNotBlank(request.getPreviousSql())) { + return request.getPreviousSql(); + } + return resolveFromDb(request.getConversationId()); + } + + private String resolveFromDb(String conversationId) { + if (StringUtils.isBlank(conversationId)) { + return null; + } + try { + List recent = aiConversationService.listRecentMessages(conversationId, 50); + if (recent == null) { + return null; + } + for (int i = recent.size() - 1; i >= 0; i--) { + AiMessage msg = recent.get(i); + if ("assistant".equals(msg.getRole()) && StringUtils.isNotBlank(msg.getSqlExtracted())) { + return msg.getSqlExtracted(); + } + } + } catch (Exception e) { + log.warn("[PreviousSqlResolver] Failed to load previous sql for {}: {}", conversationId, e.getMessage()); + } + return null; + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/prompt/PromptBuilder.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/prompt/PromptBuilder.java new file mode 100644 index 000000000..6e3dcfb78 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/prompt/PromptBuilder.java @@ -0,0 +1,85 @@ +package ai.chat2db.server.web.api.controller.ai.prompt; + +/** + * 提示词构建器接口 + */ +public interface PromptBuilder { + + /** + * 设置上下文 + * + * @param context 上下文 + * @return 构建器 + */ + PromptBuilder context(PromptContext context); + + /** + * 设置消息 + * + * @param message 消息内容 + * @return 构建器 + */ + PromptBuilder message(String message); + + /** + * 设置扩展信息 + * + * @param ext 扩展信息 + * @return 构建器 + */ + PromptBuilder ext(String ext); + + /** + * 设置 Schema DDL + * + * @param schemaDdl Schema DDL + * @return 构建器 + */ + PromptBuilder schema(String schemaDdl); + + /** + * 设置 EXPLAIN 执行计划 + * + * @param explainPlan 执行计划 + * @return 构建器 + */ + PromptBuilder explainPlan(String explainPlan); + + /** + * 设置数据源类型 + * + * @param dataSourceType 数据源类型 + * @return 构建器 + */ + PromptBuilder dataSourceType(String dataSourceType); + + /** + * 设置目标 SQL 类型 + * + * @param targetSqlType 目标 SQL 类型 + * @return 构建器 + */ + PromptBuilder targetSqlType(String targetSqlType); + + /** + * 设置源文件字段列表(用于字段映射推荐) + * + * @param sourceFields 源文件字段列表(JSON 格式) + * @return 构建器 + */ + PromptBuilder sourceFields(String sourceFields); + + /** + * 构建提示词 + * + * @return 提示词 + */ + String build(); + + /** + * 验证提示词 + * + * @return 是否有效 + */ + boolean validate(); +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/prompt/PromptBuilderImpl.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/prompt/PromptBuilderImpl.java new file mode 100644 index 000000000..c05e79782 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/prompt/PromptBuilderImpl.java @@ -0,0 +1,316 @@ +package ai.chat2db.server.web.api.controller.ai.prompt; + +import ai.chat2db.server.web.api.controller.ai.enums.PromptType; +import ai.chat2db.server.tools.base.enums.DataSourceTypeEnum; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Objects; + +/** + * 提示词构建器实现 + */ +@Component +public class PromptBuilderImpl implements PromptBuilder { + + private static final int MAX_HISTORY_MESSAGES = 6; + private static final int MAX_HISTORY_CONTENT_LENGTH = 1200; + private static final int MAX_PREVIOUS_SQL_LENGTH = 6000; + + private final PromptTemplateRegistry templateRegistry; + private final PromptValidator validator; + + private PromptContext context; + + @Autowired + public PromptBuilderImpl(PromptTemplateRegistry templateRegistry, PromptValidator validator) { + this.templateRegistry = templateRegistry; + this.validator = validator; + } + + @Override + public PromptBuilder context(PromptContext context) { + this.context = context; + return this; + } + + @Override + public PromptBuilder message(String message) { + if (this.context == null) { + this.context = new PromptContext(); + } + this.context.setMessage(message); + return this; + } + + @Override + public PromptBuilder ext(String ext) { + if (this.context == null) { + this.context = new PromptContext(); + } + this.context.setExt(ext); + return this; + } + + @Override + public PromptBuilder schema(String schemaDdl) { + if (this.context == null) { + this.context = new PromptContext(); + } + this.context.setSchemaDdl(schemaDdl); + return this; + } + + @Override + public PromptBuilder explainPlan(String explainPlan) { + if (this.context == null) { + this.context = new PromptContext(); + } + this.context.setExplainPlan(explainPlan); + return this; + } + + @Override + public PromptBuilder dataSourceType(String dataSourceType) { + if (this.context == null) { + this.context = new PromptContext(); + } + this.context.setDataSourceType(dataSourceType); + return this; + } + + @Override + public PromptBuilder targetSqlType(String targetSqlType) { + if (this.context == null) { + this.context = new PromptContext(); + } + this.context.setTargetSqlType(targetSqlType); + return this; + } + + @Override + public PromptBuilder sourceFields(String sourceFields) { + if (this.context == null) { + this.context = new PromptContext(); + } + this.context.setSourceFields(sourceFields); + return this; + } + + @Override + public String build() { + validateContext(); + + PromptType type = context.getPromptType(); + if (type == null) { + type = PromptType.NL_2_SQL; + } + if (PromptType.NL_2_SQL.equals(type) && isRedisDataSource(context)) { + type = PromptType.REDIS_NL_2_COMMAND; + } + + PromptTemplate template = templateRegistry.getTemplate(type); + String builtPrompt = fillTemplate(template, context); + + return validator.cleanPrompt(builtPrompt); + } + + @Override + public boolean validate() { + String builtPrompt = build(); + return validator.isValidLength(builtPrompt); + } + + private void validateContext() { + if (context == null) { + throw new IllegalStateException("PromptContext is null"); + } + if (StringUtils.isBlank(context.getMessage()) + && !Objects.equals(context.getPromptType(), PromptType.NL_2_COMMENT) + && !Objects.equals(context.getPromptType(), PromptType.NL_2_COMMENT_BATCH) + && !Objects.equals(context.getPromptType(), PromptType.NL_2_FIELD_MAPPING) + && !Objects.equals(context.getPromptType(), PromptType.NL_2_DATA_EXPRESSION) + && !Objects.equals(context.getPromptType(), PromptType.SQL_FIX)) { + throw new IllegalArgumentException("Message is required"); + } + } + + private String fillTemplate(PromptTemplate template, PromptContext context) { + String templateStr = template.getTemplate(); + String description = context.getPromptType() != null + ? context.getPromptType().getDescription() + : "将自然语言转换成 SQL 查询"; + + String filledTemplate = templateStr + .replace("{description}", description) + .replace("{ext}", Objects.toString(context.getExt(), "")) + .replace("{db_type}", Objects.toString(context.getDataSourceType(), "MYSQL")) + .replace("{schema}", Objects.toString(context.getSchemaDdl(), "")) + .replace("{explain_plan}", Objects.toString(context.getExplainPlan(), "")) + .replace("{message}", Objects.toString(context.getMessage(), "")) + .replace("{target_sql_type}", Objects.toString(context.getTargetSqlType(), + Objects.toString(context.getDataSourceType(), "MYSQL"))); + + // 处理 source_fields 占位符(用于字段映射) + if (filledTemplate.contains("{source_fields}")) { + String sourceFieldsText = formatSourceFields(context.getSourceFields()); + filledTemplate = filledTemplate.replace("{source_fields}", sourceFieldsText); + } + + // 处理 SQL_FIX 的占位符 + if (filledTemplate.contains("{error_message}") || filledTemplate.contains("{original_sql}")) { + String errorMessage = ""; + String originalSql = ""; + if (StringUtils.isNotBlank(context.getExt())) { + try { + com.alibaba.fastjson2.JSONObject extJson = com.alibaba.fastjson2.JSON.parseObject(context.getExt()); + errorMessage = extJson.getString("errorMessage"); + originalSql = extJson.getString("originalSql"); + } catch (Exception e) { + // 忽略解析错误 + } + } + filledTemplate = filledTemplate.replace("{error_message}", Objects.toString(errorMessage, "")); + filledTemplate = filledTemplate.replace("{original_sql}", Objects.toString(originalSql, "")); + } + + return appendRevisionContextIfNeeded(filledTemplate, context); + } + + private boolean isRedisDataSource(PromptContext context) { + return DataSourceTypeEnum.REDIS.getCode().equalsIgnoreCase(context.getDataSourceType()); + } + + private String appendRevisionContextIfNeeded(String filledTemplate, PromptContext context) { + if (StringUtils.isBlank(context.getPreviousSql())) { + return filledTemplate; + } + + if (context.getPromptType() == PromptType.SELECT_TABLES) { + return appendSelectTablesRevisionContext(filledTemplate, context); + } + + if (context.getPromptType() != PromptType.NL_2_SQL + || !Boolean.TRUE.equals(context.getIsRevision())) { + return filledTemplate; + } + + return appendNl2SqlRevisionContext(filledTemplate, context); + } + + private String appendNl2SqlRevisionContext(String filledTemplate, PromptContext context) { + if (isRedisDataSource(context)) { + return appendRedisCommandRevisionContext(filledTemplate, context); + } + + StringBuilder builder = new StringBuilder(filledTemplate); + builder.append("\n\n") + .append("### 连续对话修正要求\n") + .append("用户正在基于上一版 SQL 提出修正。请结合对话历史、上一版 SQL、当前表结构和当前 SQL input,") + .append("生成一条完整的新 SQL。\n") + .append("- 不要只描述变更点。\n") + .append("- 不要省略未变化的 SELECT、FROM、JOIN、WHERE、GROUP BY、ORDER BY 等部分。\n") + .append("- 优先使用 ```sql 代码块输出最终 SQL。\n"); + + String formattedHistory = formatConversationHistory(context.getHistory()); + if (StringUtils.isNotBlank(formattedHistory)) { + builder.append("\n### 最近对话历史\n") + .append(formattedHistory) + .append("\n"); + } + + builder.append("\n### 上一版 SQL\n") + .append("```sql\n") + .append(truncate(context.getPreviousSql(), MAX_PREVIOUS_SQL_LENGTH)) + .append("\n```\n"); + + return builder.toString(); + } + + private String appendRedisCommandRevisionContext(String filledTemplate, PromptContext context) { + StringBuilder builder = new StringBuilder(filledTemplate); + builder.append("\n\n") + .append("### 连续对话修正要求\n") + .append("用户正在基于上一版 Redis 命令提出修正。请结合对话历史、上一版 Redis 命令和当前 Redis command input,") + .append("生成完整的新 Redis 命令。\n") + .append("- 不要只描述变更点。\n") + .append("- 不要输出 SQL。\n") + .append("- 只输出可执行的 Redis 命令,不要输出 Markdown 或代码块。\n"); + + String formattedHistory = formatConversationHistory(context.getHistory()); + if (StringUtils.isNotBlank(formattedHistory)) { + builder.append("\n### 最近对话历史\n") + .append(formattedHistory) + .append("\n"); + } + + builder.append("\n### 上一版 Redis 命令\n") + .append(truncate(context.getPreviousSql(), MAX_PREVIOUS_SQL_LENGTH)) + .append("\n"); + + return builder.toString(); + } + + private String appendSelectTablesRevisionContext(String filledTemplate, PromptContext context) { + StringBuilder builder = new StringBuilder(filledTemplate); + builder.append("\n\n") + .append("### 上一版 SQL\n") + .append("```sql\n") + .append(truncate(context.getPreviousSql(), MAX_PREVIOUS_SQL_LENGTH)) + .append("\n```\n") + .append("上一版 SQL 仅用于理解用户当前修正意图和选择相关表,不要直接生成 SQL。\n"); + return builder.toString(); + } + + private String formatConversationHistory(String history) { + if (StringUtils.isBlank(history)) { + return ""; + } + try { + JSONArray messages = JSON.parseArray(history); + int start = Math.max(0, messages.size() - MAX_HISTORY_MESSAGES); + StringBuilder builder = new StringBuilder(); + for (int i = start; i < messages.size(); i++) { + JSONObject message = messages.getJSONObject(i); + String role = Objects.toString(message.getString("role"), "unknown"); + String content = truncate( + Objects.toString(message.getString("content"), ""), + MAX_HISTORY_CONTENT_LENGTH); + if (StringUtils.isNotBlank(content)) { + builder.append(role).append(": ").append(content).append("\n"); + } + } + return builder.toString().trim(); + } catch (Exception e) { + return truncate(history, MAX_HISTORY_CONTENT_LENGTH * 2); + } + } + + private String truncate(String text, int maxLength) { + if (StringUtils.isBlank(text) || text.length() <= maxLength) { + return Objects.toString(text, ""); + } + return text.substring(0, maxLength) + "\n...已截断..."; + } + + private String formatSourceFields(String sourceFieldsJson) { + if (StringUtils.isBlank(sourceFieldsJson)) { + return ""; + } + try { + if (sourceFieldsJson.trim().startsWith("[")) { + com.alibaba.fastjson2.JSONArray fields = com.alibaba.fastjson2.JSON.parseArray(sourceFieldsJson); + return fields.stream() + .map(field -> "- " + field.toString()) + .collect(java.util.stream.Collectors.joining("\n")); + } + return sourceFieldsJson; + } catch (Exception e) { + return sourceFieldsJson; + } + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/prompt/PromptContext.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/prompt/PromptContext.java new file mode 100644 index 000000000..e0678858f --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/prompt/PromptContext.java @@ -0,0 +1,77 @@ +package ai.chat2db.server.web.api.controller.ai.prompt; + +import ai.chat2db.server.web.api.controller.ai.enums.PromptType; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.AllArgsConstructor; + +/** + * 提示词构建上下文 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PromptContext { + + /** + * 提示词类型 + */ + private PromptType promptType; + + /** + * 输入消息 + */ + private String message; + + /** + * 扩展信息(额外要求/限制) + */ + private String ext; + + /** + * Schema DDL + */ + private String schemaDdl; + + /** + * SQL执行计划结果 (EXPLAIN) + */ + private String explainPlan; + + /** + * 数据源类型 + */ + private String dataSourceType; + + /** + * 目标 SQL 类型(用于 SQL 转换) + */ + private String targetSqlType; + + /** + * 源文件字段列表(用于字段映射推荐) + */ + private String sourceFields; + + /** + * 前端会话 ID,用于同一 AI 面板内的连续对话 + */ + private String conversationId; + + /** + * 最近几轮对话历史,JSON 字符串 + */ + private String history; + + /** + * 上一次 AI 输出的 SQL + */ + private String previousSql; + + /** + * 是否为基于上一次 SQL 的修正请求 + */ + private Boolean isRevision; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/prompt/PromptTemplate.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/prompt/PromptTemplate.java new file mode 100644 index 000000000..02e3e07ff --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/prompt/PromptTemplate.java @@ -0,0 +1,33 @@ +package ai.chat2db.server.web.api.controller.ai.prompt; + +import ai.chat2db.server.web.api.controller.ai.enums.PromptType; +import lombok.Builder; +import lombok.Data; + +/** + * 提示词模板值对象 + */ +@Data +@Builder +public class PromptTemplate { + + /** + * 模板名称 + */ + private String name; + + /** + * 模板内容 + */ + private String template; + + /** + * 提示词类型 + */ + private PromptType promptType; + + /** + * 模板描述 + */ + private String description; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/prompt/PromptTemplateRegistry.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/prompt/PromptTemplateRegistry.java new file mode 100644 index 000000000..ee1fdeca3 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/prompt/PromptTemplateRegistry.java @@ -0,0 +1,125 @@ +package ai.chat2db.server.web.api.controller.ai.prompt; + +import ai.chat2db.server.web.api.controller.ai.enums.PromptType; +import jakarta.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.io.ClassPathResource; +import org.springframework.stereotype.Component; +import org.yaml.snakeyaml.Yaml; + +import java.io.IOException; +import java.io.InputStream; +import java.util.EnumMap; +import java.util.Map; + +/** + * 提示词模板注册表 + * 从配置文件 prompt-templates.yml 加载模板 + */ +@Slf4j +@Component +public class PromptTemplateRegistry { + + private static final String CONFIG_FILE = "prompt-templates.yml"; + + private final Map templates = new EnumMap<>(PromptType.class); + + @PostConstruct + public void init() { + loadFromYamlFile(); + ensureAllTypesHaveTemplate(); + } + + /** + * 从 YAML 文件加载 + */ + private void loadFromYamlFile() { + log.info("Loading prompt templates from: {}", CONFIG_FILE); + ClassPathResource resource = new ClassPathResource(CONFIG_FILE); + if (!resource.exists()) { + log.warn("Prompt templates config file not found: {}, using defaults", CONFIG_FILE); + return; + } + + Yaml yaml = new Yaml(); + try (InputStream inputStream = resource.getInputStream()) { + Map data = yaml.load(inputStream); + if (data == null || !data.containsKey("prompts")) { + log.warn("No prompts configuration found in YAML file"); + return; + } + + @SuppressWarnings("unchecked") + Map prompts = (Map) data.get("prompts"); + + for (Map.Entry entry : prompts.entrySet()) { + try { + PromptType type = PromptType.valueOf(entry.getKey().toUpperCase()); + @SuppressWarnings("unchecked") + Map templateData = (Map) entry.getValue(); + + PromptTemplate template = PromptTemplate.builder() + .name((String) templateData.get("name")) + .promptType(type) + .description((String) templateData.get("description")) + .template((String) templateData.get("template")) + .build(); + + templates.put(type, template); + log.debug("Loaded template: {}", type.getCode()); + } catch (IllegalArgumentException e) { + log.warn("Unknown prompt type: {}", entry.getKey()); + } + } + + log.info("Loaded {} prompt templates", templates.size()); + } catch (IOException e) { + log.error("Failed to load prompt templates from YAML file", e); + } + } + + /** + * 确保所有类型都有模板 + */ + private void ensureAllTypesHaveTemplate() { + PromptTemplate defaultTemplate = buildDefaultTemplate(); + + for (PromptType type : PromptType.values()) { + if (!templates.containsKey(type)) { + log.warn("Template not found for type: {}, using default", type.getCode()); + templates.put(type, defaultTemplate); + } + } + } + + /** + * 根据类型获取模板 + * + * @param type 提示词类型 + * @return 模板 + */ + public PromptTemplate getTemplate(PromptType type) { + return templates.getOrDefault(type, getDefaultTemplate()); + } + + + private PromptTemplate getDefaultTemplate() { + return templates.getOrDefault(PromptType.NL_2_SQL, buildDefaultTemplate()); + } + + private PromptTemplate buildDefaultTemplate() { + return PromptTemplate.builder() + .name("default") + .promptType(PromptType.NL_2_SQL) + .description("将自然语言转换成SQL查询") + .template("### 请根据以下 table properties 和 SQL input{description}. {ext}\n" + + "#\n" + + "### {db_type} SQL tables, with their properties:\n" + + "#\n" + + "# {schema}\n" + + "#\n" + + "#\n" + + "### SQL input: {message}") + .build(); + } +} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/prompt/PromptValidator.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/prompt/PromptValidator.java new file mode 100644 index 000000000..bc4888f36 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/prompt/PromptValidator.java @@ -0,0 +1,53 @@ +package ai.chat2db.server.web.api.controller.ai.prompt; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; + +/** + * 提示词验证器 + */ +@Component +public class PromptValidator { + + private static final int MAX_PROMPT_LENGTH = 15400; + private static final int TOKEN_CONVERT_CHAR_LENGTH = 4; + + /** + * 验证提示词长度 + * + * @param prompt 提示词内容 + * @return 是否有效 + */ + public boolean isValidLength(String prompt) { + if (StringUtils.isEmpty(prompt)) { + return false; + } + return getTokenCount(prompt) <= MAX_PROMPT_LENGTH; + } + + /** + * 获取 token 数量 + * + * @param prompt 提示词内容 + * @return token 数量 + */ + public int getTokenCount(String prompt) { + if (StringUtils.isEmpty(prompt)) { + return 0; + } + return prompt.length() / TOKEN_CONVERT_CHAR_LENGTH; + } + + /** + * 清理提示词(移除特殊字符) + * + * @param prompt 原始提示词 + * @return 清理后的提示词 + */ + public String cleanPrompt(String prompt) { + if (StringUtils.isEmpty(prompt)) { + return ""; + } + return prompt.replaceAll("[\r\t]", ""); + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/request/ChatQueryRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/request/ChatQueryRequest.java index 70a631e8c..e37699fa2 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/request/ChatQueryRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/request/ChatQueryRequest.java @@ -43,4 +43,34 @@ public class ChatQueryRequest extends DataSourceBaseRequest { * 更多备注信息:如要求或限制条件等 */ private String ext; + + /** + * 前端会话 ID,用于同一 AI 面板内的连续对话 + */ + private String conversationId; + + /** + * 最近几轮对话历史,JSON 字符串 + */ + private String history; + + /** + * 上一次 AI 输出的 SQL + */ + private String previousSql; + + /** + * 是否为基于上一次 SQL 的修正请求 + */ + private Boolean isRevision; + + /** + * 大请求体临时缓存 ID,用于避免 SSE GET URL 过长 + */ + private String payloadId; + + /** + * 用户消息的客户端 UUID(可选,用于落库) + */ + private String userMessageId; } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/rest/client/RestAIClient.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/rest/client/RestAIClient.java deleted file mode 100644 index 8140be3ea..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/rest/client/RestAIClient.java +++ /dev/null @@ -1,71 +0,0 @@ - -package ai.chat2db.server.web.api.controller.ai.rest.client; - -import ai.chat2db.server.domain.api.model.Config; -import ai.chat2db.server.domain.api.service.ConfigService; -import ai.chat2db.server.web.api.util.ApplicationContextUtil; - -import lombok.extern.slf4j.Slf4j; - -/** - * @author moji - * @version : RestAIClient.java - */ -@Slf4j -public class RestAIClient { - - /** - * AI SQL选择的接口来源 - */ - public static final String AI_SQL_SOURCE = "ai.sql.source"; - - /** - * 自定义AI接口地址 - */ - public static final String REST_AI_URL = "rest.ai.url"; - - /** - * 自定义AI接口请求方法 - */ - public static final String REST_AI_STREAM_OUT = "rest.ai.stream"; - - private static RestAiStreamClient REST_AI_STREAM_CLIENT; - - public static RestAiStreamClient getInstance() { - if (REST_AI_STREAM_CLIENT != null) { - return REST_AI_STREAM_CLIENT; - } else { - return singleton(); - } - } - - private static RestAiStreamClient singleton() { - if (REST_AI_STREAM_CLIENT == null) { - synchronized (RestAIClient.class) { - if (REST_AI_STREAM_CLIENT == null) { - refresh(); - } - } - } - return REST_AI_STREAM_CLIENT; - } - - /** - * 刷新客户端 - */ - public static void refresh() { - String apiUrl = ""; - Boolean stream = Boolean.TRUE; - ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); - Config apiHostConfig = configService.find(REST_AI_URL).getData(); - if (apiHostConfig != null) { - apiUrl = apiHostConfig.getContent(); - } - Config config = configService.find(REST_AI_STREAM_OUT).getData(); - if (config != null) { - stream = Boolean.valueOf(config.getContent()); - } - REST_AI_STREAM_CLIENT = new RestAiStreamClient(apiUrl, stream); - } - -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/rest/client/RestAiStreamClient.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/rest/client/RestAiStreamClient.java deleted file mode 100644 index fba46a4e6..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/rest/client/RestAiStreamClient.java +++ /dev/null @@ -1,166 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.rest.client; - -import java.io.IOException; -import java.util.Objects; -import java.util.concurrent.TimeUnit; - -import ai.chat2db.server.tools.common.exception.ParamBusinessException; -import ai.chat2db.server.web.api.controller.ai.rest.model.RestAiCompletion; -import cn.hutool.http.ContentType; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.unfbx.chatgpt.sse.ConsoleEventSourceListener; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import okhttp3.Call; -import okhttp3.Callback; -import okhttp3.MediaType; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; -import okhttp3.ResponseBody; -import okhttp3.sse.EventSource; -import okhttp3.sse.EventSourceListener; -import okhttp3.sse.EventSources; -import org.apache.commons.lang3.StringUtils; - -/** - * 自定义AI接口client - * @author moji - */ -@Slf4j -public class RestAiStreamClient { - /** - * rest api url - */ - @Getter - private String apiUrl; - - /** - * 是否流式接口 - */ - @Getter - private Boolean stream; - /** - * okHttpClient - */ - @Getter - private OkHttpClient okHttpClient; - - /** - * 构造实例对象 - * - * @param url - */ - public RestAiStreamClient(String url, Boolean stream) { - this.apiUrl = url; - this.stream = stream; - this.okHttpClient = new OkHttpClient - .Builder() - .connectTimeout(10, TimeUnit.SECONDS) - .writeTimeout(50, TimeUnit.SECONDS) - .readTimeout(50, TimeUnit.SECONDS) - .build(); - } - - /** - * 请求RESTAI接口 - * - * @param prompt - * @param eventSourceListener - */ - public void restCompletions(String prompt, - EventSourceListener eventSourceListener) { - log.info("开始调用自定义AI, prompt:{}", prompt); - RestAiCompletion completion = new RestAiCompletion(); - completion.setPrompt(prompt); - if (Objects.isNull(stream) || stream) { - streamCompletions(completion, eventSourceListener); - log.info("结束调用流式输出自定义AI"); - return; - } - nonStreamCompletions(completion, eventSourceListener); - log.info("结束调用非流式输出自定义AI"); - } - - /** - * 问答接口 stream 形式 - * - * @param completion open ai 参数 - * @param eventSourceListener sse监听器 - * @see ConsoleEventSourceListener - */ - public void streamCompletions(RestAiCompletion completion, EventSourceListener eventSourceListener) { - if (Objects.isNull(eventSourceListener)) { - log.error("参数异常:EventSourceListener不能为空"); - throw new ParamBusinessException(); - } - if (StringUtils.isBlank(completion.getPrompt())) { - log.error("参数异常:Prompt不能为空"); - throw new ParamBusinessException(); - } - try { - EventSource.Factory factory = EventSources.createFactory(this.okHttpClient); - ObjectMapper mapper = new ObjectMapper(); - mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - String requestBody = mapper.writeValueAsString(completion); - Request request = new Request.Builder() - .url(this.apiUrl) - .post(RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), requestBody)) - .build(); - //创建事件 - EventSource eventSource = factory.newEventSource(request, eventSourceListener); - } catch (Exception e) { - log.error("请求参数解析异常", e); - throw new ParamBusinessException(); - } - } - - /** - * 请求非流式输出接口 - * - * @param completion - * @param eventSourceListener - */ - public void nonStreamCompletions(RestAiCompletion completion, EventSourceListener eventSourceListener) { - if (StringUtils.isBlank(completion.getPrompt())) { - log.error("参数异常:Prompt不能为空"); - throw new ParamBusinessException(); - } - try { - ObjectMapper mapper = new ObjectMapper(); - mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - String requestBody = mapper.writeValueAsString(completion); - Request request = new Request.Builder() - .url(this.apiUrl) - .post(RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), requestBody)) - .build(); - - this.okHttpClient.newCall(request).enqueue(new Callback() { - @Override - public void onFailure(Call call, IOException e) { - eventSourceListener.onFailure(null, e, null); - } - - @Override - public void onResponse(Call call, Response response) throws IOException { - try (ResponseBody responseBody = response.body()) { - if (responseBody != null) { - String content = responseBody.string(); - eventSourceListener.onEvent(null, "[DATA]", null, content); - eventSourceListener.onEvent(null, "[DONE]", null, "[DONE]"); - } - } catch (IOException e) { - eventSourceListener.onFailure(null, e, response); - } - } - }); - - } catch (Exception e) { - log.error("请求参数解析异常", e); - throw new ParamBusinessException(); - } - } - -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/rest/listener/RestAIEventSourceListener.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/rest/listener/RestAIEventSourceListener.java deleted file mode 100644 index bb0e12caf..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/rest/listener/RestAIEventSourceListener.java +++ /dev/null @@ -1,118 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.rest.listener; - -import java.util.Objects; - -import com.unfbx.chatgpt.entity.chat.Message; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import okhttp3.Response; -import okhttp3.ResponseBody; -import okhttp3.sse.EventSource; -import okhttp3.sse.EventSourceListener; -import org.apache.commons.lang3.StringUtils; -import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; - -/** - * 描述:RESTAIEventSourceListener - * - * @author https:www.unfbx.com - * @date 2023-02-22 - */ -@Slf4j -public class RestAIEventSourceListener extends EventSourceListener { - - private SseEmitter sseEmitter; - - public RestAIEventSourceListener(SseEmitter sseEmitter) { - this.sseEmitter = sseEmitter; - } - - /** - * {@inheritDoc} - */ - @Override - public void onOpen(EventSource eventSource, Response response) { - log.info("REST AI建立sse连接..."); - } - - /** - * {@inheritDoc} - */ - @SneakyThrows - @Override - public void onEvent(EventSource eventSource, String id, String type, String data) { - log.info("REST AI返回数据:{}", data); - String end = "[DONE]"; - if (data.equals(end)) { - log.info("REST AI返回数据结束了"); - sseEmitter.send(SseEmitter.event() - .id(end) - .data(end) - .reconnectTime(3000)); - sseEmitter.complete(); - return; - } - Message message = new Message(); - if (StringUtils.isNotBlank(data)) { - data = data.replaceAll("^\"|\"$", ""); - data = data.replaceAll("\\\\n", "\n"); - message.setContent(data); - sseEmitter.send(SseEmitter.event() - .id(id) - .data(message) - .reconnectTime(3000)); - } - } - - @SneakyThrows - @Override - public void onClosed(EventSource eventSource) { - log.info("REST AI关闭sse连接..."); - sseEmitter.send(SseEmitter.event() - .id("[DONE]") - .data("[DONE]") - .reconnectTime(3000)); - sseEmitter.complete(); - } - - @Override - public void onFailure(EventSource eventSource, Throwable t, Response response) { - try { - if (Objects.isNull(response)) { - String message = t.getMessage(); - Message sseMessage = new Message(); - sseMessage.setContent(message); - sseEmitter.send(SseEmitter.event() - .id("[ERROR]") - .data(sseMessage)); - sseEmitter.send(SseEmitter.event() - .id("[DONE]") - .data("[DONE]")); - sseEmitter.complete(); - return; - } - ResponseBody body = response.body(); - String bodyString = null; - if (Objects.nonNull(body)) { - bodyString = body.string(); - log.error("REST AI sse body error:{},exception:{}", bodyString, t); - } else { - log.error("REST AI sse response error:{},exception:{}", response, t); - } - if (Objects.nonNull(eventSource)) { - eventSource.cancel(); - } - Message message = new Message(); - message.setContent("Rest AI Error:" + bodyString); - sseEmitter.send(SseEmitter.event() - .id("[ERROR]") - .data(message)); - sseEmitter.send(SseEmitter.event() - .id("[DONE]") - .data("[DONE]")); - sseEmitter.complete(); - } catch (Exception exception) { - log.error("发送数据异常:", exception); - } - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/rest/model/RestAiCompletion.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/rest/model/RestAiCompletion.java deleted file mode 100644 index 1af3c3cf7..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/rest/model/RestAiCompletion.java +++ /dev/null @@ -1,26 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.rest.model; - -import java.io.Serializable; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * @author moji - * @version RestAiCompletion.java, v 0.1 2023年05月27日 14:00 moji Exp $ - * @date 2023/05/27 - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class RestAiCompletion implements Serializable { - - /** - * 提示语 - */ - private String prompt; - -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/statemachine/ChatContext.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/statemachine/ChatContext.java new file mode 100644 index 000000000..fb5df77d5 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/statemachine/ChatContext.java @@ -0,0 +1,42 @@ +package ai.chat2db.server.web.api.controller.ai.statemachine; + +import java.util.List; + +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import ai.chat2db.server.tools.common.model.LoginUser; +import ai.chat2db.server.web.api.controller.ai.request.ChatQueryRequest; +import ai.chat2db.spi.sql.ConnectInfo; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class ChatContext { + private String uid; + private ChatQueryRequest request; + private SseEmitter sseEmitter; + private ChatClient chatClient; + private String builtPrompt; + private List selectedTables; + private String schemaDdl; + private String explainSql; + private String explainResult; + private volatile boolean cancelled; + private LoginUser loginUser; + private ConnectInfo connectInfo; + + private String currentContent; + private String currentThinking; + + /** + * 用户消息的客户端 UUID(落库用) + */ + private String userMessageId; + + /** + * 标记本轮是否为该会话的首条消息(用于触发标题生成) + */ + private Boolean firstTurnOfConversation; +} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/statemachine/ChatEvent.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/statemachine/ChatEvent.java new file mode 100644 index 000000000..aa38e27a2 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/statemachine/ChatEvent.java @@ -0,0 +1,42 @@ +package ai.chat2db.server.web.api.controller.ai.statemachine; + +/** + * 聊天状态机事件枚举 + * 定义了AI对话过程中可能发生的各种事件 + */ +public enum ChatEvent { + /** 表已提供 */ + TABLES_PROVIDED, + /** 表未提供 */ + TABLES_NOT_PROVIDED, + /** 表无需提供(如文本生成、标题生成) */ + TABLES_NOT_NEEDED, + /** 自动选择完成 */ + AUTO_SELECT_DONE, + /** Schema已获取 */ + SCHEMA_FETCHED, + /** EXPLAIN执行完成 */ + EXPLAIN_EXECUTED, + /** EXPLAIN已提取出表名 */ + EXPLAIN_TABLES_SELECTED, + /** EXPLAIN未提取出表名 */ + EXPLAIN_TABLES_NOT_SELECTED, + /** EXPLAIN执行失败 */ + EXPLAIN_FAILED, + /** 不需要EXPLAIN */ + EXPLAIN_NOT_NEEDED, + /** Prompt已构建 */ + PROMPT_BUILT, + /** 流式响应完成 */ + STREAM_FINISHED, + /** 自动选择失败 */ + AUTO_SELECT_FAILED, + /** 获取Schema失败 */ + FETCH_SCHEMA_FAILED, + /** Prompt构建失败 */ + PROMPT_BUILD_FAILED, + /** AI调用失败 */ + AI_CALL_FAILED, + /** 取消操作 */ + CANCEL +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/statemachine/ChatState.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/statemachine/ChatState.java new file mode 100644 index 000000000..86e498755 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/statemachine/ChatState.java @@ -0,0 +1,30 @@ +package ai.chat2db.server.web.api.controller.ai.statemachine; +/** + * 聊天状态枚举 + * 定义AI聊天交互过程中的各种状态 + */ +public enum ChatState { + /** 空闲状态,初始状态或等待用户输入 */ + IDLE, + + /** 自动选择表状态,系统正在自动选择合适的数据库表 */ + AUTO_SELECTING_TABLES, + + /** 获取表结构状态,正在获取选中表的schema信息 */ + FETCHING_TABLE_SCHEMA, + + /** 执行EXPLAIN状态,正在获取SQL执行计划 */ + EXECUTING_EXPLAIN, + + /** 构建提示词状态,正在构造发送给AI的prompt */ + BUILDING_PROMPT, + + /** 流式输出状态,正在接收并流式返回AI的响应 */ + STREAMING, + + /** 完成状态,聊天交互已成功完成 */ + COMPLETED, + + /** 失败状态,聊天交互过程中发生错误 */ + FAILED +} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/statemachine/ChatStateMachineConfig.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/statemachine/ChatStateMachineConfig.java new file mode 100644 index 000000000..9351813d1 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/statemachine/ChatStateMachineConfig.java @@ -0,0 +1,174 @@ +package ai.chat2db.server.web.api.controller.ai.statemachine; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.statemachine.config.EnableStateMachineFactory; +import org.springframework.statemachine.config.StateMachineConfigurerAdapter; +import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; +import org.springframework.statemachine.listener.StateMachineListenerAdapter; +import org.springframework.statemachine.StateMachine; + +import ai.chat2db.server.web.api.controller.ai.enums.PromptType; +import ai.chat2db.server.web.api.controller.ai.statemachine.actions.SelectTablesAction; +import ai.chat2db.server.web.api.controller.ai.statemachine.actions.BuildPromptAction; +import ai.chat2db.server.web.api.controller.ai.statemachine.actions.FetchSchemaAction; +import ai.chat2db.server.web.api.controller.ai.statemachine.actions.SaveAiCommentAction; +import ai.chat2db.server.web.api.controller.ai.statemachine.actions.StreamAction; +import ai.chat2db.server.web.api.controller.ai.statemachine.actions.ExplainAction; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Configuration +@EnableStateMachineFactory +public class ChatStateMachineConfig extends StateMachineConfigurerAdapter { + + @Autowired + private SelectTablesAction selectTablesAction; + + @Autowired + private FetchSchemaAction fetchSchemaAction; + + @Autowired + private BuildPromptAction buildPromptAction; + + @Autowired + private StreamAction streamAction; + + @Autowired + private ExplainAction explainAction; + + @Autowired + private SaveAiCommentAction saveAiCommentAction; + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial(ChatState.IDLE) + .state(ChatState.AUTO_SELECTING_TABLES, selectTablesAction) + .state(ChatState.FETCHING_TABLE_SCHEMA, fetchSchemaAction) + .state(ChatState.EXECUTING_EXPLAIN, explainAction) + .state(ChatState.BUILDING_PROMPT, buildPromptAction) + .state(ChatState.STREAMING, streamAction) + .end(ChatState.COMPLETED) + .end(ChatState.FAILED); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withExternal() + .source(ChatState.IDLE).target(ChatState.FETCHING_TABLE_SCHEMA) + .event(ChatEvent.TABLES_PROVIDED) + .and() + .withExternal() + .source(ChatState.IDLE).target(ChatState.AUTO_SELECTING_TABLES) + .event(ChatEvent.TABLES_NOT_PROVIDED) + .and() + .withExternal() + .source(ChatState.IDLE).target(ChatState.BUILDING_PROMPT) + .event(ChatEvent.TABLES_NOT_NEEDED) + .and() + .withExternal() + .source(ChatState.IDLE).target(ChatState.EXECUTING_EXPLAIN) + .event(ChatEvent.EXPLAIN_TABLES_NOT_SELECTED) + .and() + .withExternal() + .source(ChatState.AUTO_SELECTING_TABLES).target(ChatState.FETCHING_TABLE_SCHEMA) + .event(ChatEvent.AUTO_SELECT_DONE) + .and() + .withExternal() + .source(ChatState.AUTO_SELECTING_TABLES).target(ChatState.FAILED) + .event(ChatEvent.AUTO_SELECT_FAILED) + .and() + .withExternal() + .source(ChatState.FETCHING_TABLE_SCHEMA).target(ChatState.BUILDING_PROMPT) + .event(ChatEvent.SCHEMA_FETCHED) + .and() + .withExternal() + .source(ChatState.FETCHING_TABLE_SCHEMA).target(ChatState.FAILED) + .event(ChatEvent.FETCH_SCHEMA_FAILED) + .and() + .withExternal() + .source(ChatState.EXECUTING_EXPLAIN).target(ChatState.BUILDING_PROMPT) + .event(ChatEvent.EXPLAIN_EXECUTED) + .and() + .withExternal() + .source(ChatState.EXECUTING_EXPLAIN).target(ChatState.FETCHING_TABLE_SCHEMA) + .event(ChatEvent.EXPLAIN_TABLES_SELECTED) + .and() + .withExternal() + .source(ChatState.EXECUTING_EXPLAIN).target(ChatState.AUTO_SELECTING_TABLES) + .event(ChatEvent.EXPLAIN_TABLES_NOT_SELECTED) + .and() + .withExternal() + .source(ChatState.EXECUTING_EXPLAIN).target(ChatState.BUILDING_PROMPT) + .event(ChatEvent.EXPLAIN_FAILED) + .and() + .withExternal() + .source(ChatState.EXECUTING_EXPLAIN).target(ChatState.BUILDING_PROMPT) + .event(ChatEvent.EXPLAIN_NOT_NEEDED) + .and() + .withExternal() + .source(ChatState.BUILDING_PROMPT).target(ChatState.STREAMING) + .event(ChatEvent.PROMPT_BUILT) + .and() + .withExternal() + .source(ChatState.BUILDING_PROMPT).target(ChatState.FAILED) + .event(ChatEvent.PROMPT_BUILD_FAILED) + .and() + .withExternal() + .source(ChatState.STREAMING).target(ChatState.COMPLETED) + .action(saveAiCommentAction) + .event(ChatEvent.STREAM_FINISHED) + .and() + .withExternal() + .source(ChatState.STREAMING).target(ChatState.FAILED) + .event(ChatEvent.AI_CALL_FAILED) + .and() + .withExternal() + .source(ChatState.STREAMING).target(ChatState.FAILED) + .event(ChatEvent.CANCEL); + } + + @Override + public void configure(org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer config) throws Exception { + config.withConfiguration() + .listener(new StateMachineListenerAdapter() { + @Override + public void stateChanged(org.springframework.statemachine.state.State from, org.springframework.statemachine.state.State to) { + log.info("[StateMachine] State changed: {} -> {}", + from != null ? from.getId() : "null", + to != null ? to.getId() : "null"); + } + + @Override + public void stateMachineStarted(StateMachine sm) { + log.info("[StateMachine] StateMachine started with id: {}, initial state: {}", + sm.getId(), + sm.getState() != null ? sm.getState().getId() : "null"); + } + + @Override + public void stateMachineStopped(StateMachine sm) { + log.info("[StateMachine] StateMachine stopped with id: {}, final state: {}", + sm.getId(), + sm.getState() != null ? sm.getState().getId() : "null"); + } + + @Override + public void stateEntered(org.springframework.statemachine.state.State state) { + log.info("[StateMachine] State entered: {}", state.getId()); + } + + @Override + public void stateExited(org.springframework.statemachine.state.State state) { + log.info("[StateMachine] State exited: {}", state.getId()); + } + }); + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/statemachine/actions/BaseChatAction.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/statemachine/actions/BaseChatAction.java new file mode 100644 index 000000000..266da7837 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/statemachine/actions/BaseChatAction.java @@ -0,0 +1,99 @@ +package ai.chat2db.server.web.api.controller.ai.statemachine.actions; + +import java.io.IOException; +import java.util.List; + +import org.springframework.statemachine.StateContext; +import org.springframework.statemachine.action.Action; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import com.alibaba.fastjson2.JSONObject; + +import ai.chat2db.server.domain.repository.Dbutils; +import ai.chat2db.server.tools.common.model.Context; +import ai.chat2db.server.tools.common.model.LoginUser; +import ai.chat2db.server.tools.common.util.ContextUtils; +import ai.chat2db.server.web.api.controller.ai.statemachine.ChatContext; +import ai.chat2db.server.web.api.controller.ai.statemachine.ChatEvent; +import ai.chat2db.server.web.api.controller.ai.statemachine.ChatState; +import ai.chat2db.spi.sql.Chat2DBContext; +import ai.chat2db.spi.sql.ConnectInfo; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public abstract class BaseChatAction implements Action { + + protected ChatContext getChatContext(StateContext context) { + return (ChatContext) context.getExtendedState().getVariables().get("chatContext"); + } + + protected void buildContext(ChatContext ctx) { + LoginUser loginUser = ctx.getLoginUser(); + ConnectInfo connectInfo = ctx.getConnectInfo(); + if (loginUser != null) { + ContextUtils.setContext(Context.builder() + .loginUser(loginUser) + .build()); + } + if (connectInfo != null) { + Dbutils.setSession(); + Chat2DBContext.putContext(connectInfo); + } + } + + protected void removeContext() { + Dbutils.removeSession(); + ContextUtils.removeContext(); + Chat2DBContext.removeContext(); + } + + protected void sendStateEvent(SseEmitter emitter, ChatState state, String message) { + try { + JSONObject data = new JSONObject(); + data.put("state", state.name()); + data.put("message", message); + emitter.send(SseEmitter.event() + .name("state") + .data(data.toJSONString())); + } catch (IOException e) { + log.error("Failed to send state event", e); + } + } + + protected void sendError(SseEmitter emitter, String errorMessage) { + try { + JSONObject data = new JSONObject(); + data.put("error", errorMessage); + emitter.send(SseEmitter.event() + .name("error") + .data(data.toJSONString())); + } catch (IOException e) { + log.error("Failed to send error event", e); + } + } + + protected void sendTablesSelected(SseEmitter emitter, List tables) { + try { + JSONObject data = new JSONObject(); + data.put("tables", tables); + emitter.send(SseEmitter.event() + .name("tables_selected") + .data(data.toJSONString())); + } catch (IOException e) { + log.error("Failed to send tables_selected event", e); + } + } + + protected void sendSchemaFetched(SseEmitter emitter, String ddl) { + try { + JSONObject data = new JSONObject(); + data.put("ddl", ddl); + emitter.send(SseEmitter.event() + .name("schema_fetched") + .data(data.toJSONString())); + } catch (IOException e) { + log.error("Failed to send schema_fetched event", e); + } + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/statemachine/actions/BuildPromptAction.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/statemachine/actions/BuildPromptAction.java new file mode 100644 index 000000000..db3da78eb --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/statemachine/actions/BuildPromptAction.java @@ -0,0 +1,161 @@ +package ai.chat2db.server.web.api.controller.ai.statemachine.actions; + +import java.util.List; + +import ai.chat2db.server.domain.api.model.AiMessage; +import ai.chat2db.server.domain.api.service.AiConversationService; +import ai.chat2db.server.domain.api.service.DataSourceService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.statemachine.StateContext; +import org.springframework.stereotype.Component; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; + +import ai.chat2db.server.web.api.controller.ai.enums.PromptType; +import ai.chat2db.server.web.api.controller.ai.prompt.PromptBuilder; +import ai.chat2db.server.web.api.controller.ai.prompt.PromptContext; +import ai.chat2db.server.web.api.controller.ai.prompt.PreviousSqlResolver; +import ai.chat2db.server.web.api.controller.ai.request.ChatQueryRequest; +import ai.chat2db.server.web.api.controller.ai.statemachine.ChatContext; +import ai.chat2db.server.web.api.controller.ai.statemachine.ChatEvent; +import ai.chat2db.server.web.api.controller.ai.statemachine.ChatState; +import lombok.extern.slf4j.Slf4j; +import reactor.core.publisher.Mono; + +/** + * 构建提示词动作 + */ +@Component +@Slf4j +public class BuildPromptAction extends BaseChatAction { + + private static final int MAX_HISTORY_MESSAGES = 6; + + @Autowired + private PromptBuilder promptBuilder; + + @Autowired + private DataSourceService dataSourceService; + + @Autowired + private AiConversationService aiConversationService; + + @Autowired + private PreviousSqlResolver previousSqlResolver; + + @Override + public void execute(StateContext context) { + ChatContext chatContext = getChatContext(context); + if (chatContext.isCancelled()) { + log.info("[BuildPromptAction] Skip cancelled request, uid: {}", chatContext.getUid()); + return; + } + + sendStateEvent(chatContext.getSseEmitter(), + ChatState.BUILDING_PROMPT, "正在构建提示..."); + + buildContext(chatContext); + try { + ChatQueryRequest request = chatContext.getRequest(); + String schemaDdl = chatContext.getSchemaDdl(); + + PromptType promptType = determinePromptType(request); + log.info("[BuildPromptAction] Building prompt, uid: {}, promptType: {}, isRevision: {}, messageLength: {}", + chatContext.getUid(), promptType, request.getIsRevision(), + StringUtils.length(request.getMessage())); + + String sourceFields = extractSourceFields(request.getExt()); + String history = request.getHistory(); + String previousSql = request.getPreviousSql(); + if (PromptType.NL_2_SQL == promptType && Boolean.TRUE.equals(request.getIsRevision())) { + if (StringUtils.isBlank(history) && StringUtils.isNotBlank(request.getConversationId())) { + history = loadHistoryFromDb(request.getConversationId()); + } + previousSql = previousSqlResolver.resolve(request); + } + + PromptContext promptContext = PromptContext.builder() + .promptType(promptType) + .message(request.getMessage()) + .ext(request.getExt()) + .schemaDdl(schemaDdl) + .explainPlan(chatContext.getExplainResult()) + .dataSourceType(dataSourceService.queryDatabaseType(request.getDataSourceId())) + .targetSqlType(request.getDestSqlType()) + .sourceFields(sourceFields) + .conversationId(request.getConversationId()) + .history(history) + .previousSql(previousSql) + .isRevision(request.getIsRevision()) + .build(); + + String builtPrompt = promptBuilder.context(promptContext).build(); + chatContext.setBuiltPrompt(builtPrompt); + log.info("[BuildPromptAction] Prompt built, uid: {}, promptLength: {}", + chatContext.getUid(), StringUtils.length(builtPrompt)); + log.info("[BuildPromptAction] Prompt content, uid: {}:\n{}", chatContext.getUid(), builtPrompt); + + context.getStateMachine().sendEvent( + Mono.just(MessageBuilder.withPayload(ChatEvent.PROMPT_BUILT).build()) + ).subscribe(); + } catch (Exception e) { + log.error("[BuildPromptAction] Build prompt failed for uid: {}", chatContext.getUid(), e); + sendError(chatContext.getSseEmitter(), + "构建提示失败:" + e.getMessage()); + context.getStateMachine().sendEvent( + Mono.just(MessageBuilder.withPayload(ChatEvent.PROMPT_BUILD_FAILED).build()) + ).subscribe(); + } finally { + removeContext(); + } + } + + private String loadHistoryFromDb(String conversationId) { + try { + List recent = aiConversationService.listRecentMessages(conversationId, MAX_HISTORY_MESSAGES); + if (recent == null || recent.isEmpty()) { + return null; + } + JSONArray array = new JSONArray(); + for (AiMessage msg : recent) { + if ("assistant".equals(msg.getRole())) { + continue; + } + JSONObject obj = new JSONObject(); + obj.put("role", msg.getRole()); + obj.put("content", msg.getContent()); + array.add(obj); + } + return JSON.toJSONString(array); + } catch (Exception e) { + log.warn("[BuildPromptAction] Failed to load history for {}: {}", conversationId, e.getMessage()); + return null; + } + } + + private PromptType determinePromptType(ChatQueryRequest request) { + String promptType = StringUtils.isBlank(request.getPromptType()) + ? PromptType.NL_2_SQL.getCode() + : request.getPromptType(); + return PromptType.valueOf(promptType); + } + + private String extractSourceFields(String ext) { + if (StringUtils.isBlank(ext)) { + return null; + } + try { + com.alibaba.fastjson2.JSONObject extJson = com.alibaba.fastjson2.JSON.parseObject(ext); + if (extJson.containsKey("sourceFields")) { + return extJson.getString("sourceFields"); + } + } catch (Exception e) { + log.warn("[BuildPromptAction] Failed to parse sourceFields from ext", e); + } + return null; + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/statemachine/actions/ExplainAction.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/statemachine/actions/ExplainAction.java new file mode 100644 index 000000000..f022b7601 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/statemachine/actions/ExplainAction.java @@ -0,0 +1,171 @@ +package ai.chat2db.server.web.api.controller.ai.statemachine.actions; + +import java.io.IOException; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.statemachine.StateContext; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import com.alibaba.fastjson2.JSONObject; + +import ai.chat2db.server.web.api.controller.ai.enums.PromptType; +import ai.chat2db.server.web.api.controller.ai.request.ChatQueryRequest; +import ai.chat2db.server.web.api.controller.ai.statemachine.ChatContext; +import ai.chat2db.server.web.api.controller.ai.statemachine.ChatEvent; +import ai.chat2db.server.web.api.controller.ai.statemachine.ChatState; +import ai.chat2db.server.web.api.controller.ai.statemachine.helper.ExplainResult; +import ai.chat2db.server.web.api.controller.ai.statemachine.helper.ExplainTableNameExtractor; +import ai.chat2db.server.web.api.controller.ai.statemachine.helper.SqlExplainHelper; +import lombok.extern.slf4j.Slf4j; +import reactor.core.publisher.Mono; + +/** + * 执行EXPLAIN动作 + * 仅在SQL_OPTIMIZER类型时执行,失败时静默降级 + */ +@Component +@Slf4j +public class ExplainAction extends BaseChatAction { + + @Autowired + private SqlExplainHelper sqlExplainHelper; + + private static final Pattern SQL_CODE_BLOCK_PATTERN = Pattern.compile("```sql\\s*([\\s\\S]+?)\\s*```", Pattern.CASE_INSENSITIVE); + private static final Pattern SQL_KEYWORD_PATTERN = Pattern.compile("(?i)\\b(SELECT|UPDATE|DELETE|INSERT|MERGE)\\b"); + + @Override + public void execute(StateContext context) { + log.info("[ExplainAction] execute called"); + ChatContext ctx = getChatContext(context); + + if (ctx.isCancelled()) { + log.info("[ExplainAction] cancelled, returning"); + return; + } + + ChatQueryRequest request = ctx.getRequest(); + PromptType promptType = determinePromptType(request); + + // 只对 SQL_OPTIMIZER 执行 EXPLAIN + if (promptType != PromptType.SQL_OPTIMIZER) { + log.info("[ExplainAction] Skip EXPLAIN for promptType: {}", promptType); + triggerEvent(context, ChatEvent.EXPLAIN_NOT_NEEDED); + return; + } + + sendStateEvent(ctx.getSseEmitter(), ChatState.EXECUTING_EXPLAIN, "正在分析执行计划..."); + + boolean explainFirst = CollectionUtils.isEmpty(request.getTableNames()) + && StringUtils.isBlank(ctx.getSchemaDdl()); + buildContext(ctx); + try { + if (StringUtils.isNotBlank(ctx.getExplainResult())) { + log.info("[ExplainAction] EXPLAIN result already exists, skip duplicate execution"); + triggerEvent(context, ChatEvent.EXPLAIN_EXECUTED); + return; + } + + String sql = extractSql(request.getMessage()); + if (sql == null) { + log.warn("[ExplainAction] No SQL found in message"); + triggerEvent(context, explainFirst ? ChatEvent.EXPLAIN_TABLES_NOT_SELECTED : ChatEvent.EXPLAIN_NOT_NEEDED); + return; + } + + log.info("[ExplainAction] Executing EXPLAIN for SQL: {}", sql); + + ExplainResult result = sqlExplainHelper.executeExplain(sql); + + if (result.isSuccess()) { + ctx.setExplainSql(result.getExplainSql()); + ctx.setExplainResult(result.getFormattedPlan()); + + sendExplainToClient(ctx.getSseEmitter(), result); + log.info("[ExplainAction] EXPLAIN executed successfully"); + + if (explainFirst) { + List tableNames = ExplainTableNameExtractor.extractTableNames(sql, result); + if (CollectionUtils.isNotEmpty(tableNames)) { + ctx.getRequest().setTableNames(tableNames); + ctx.setSelectedTables(tableNames); + sendTablesSelected(ctx.getSseEmitter(), tableNames); + log.info("[ExplainAction] Extracted tables from EXPLAIN: {}", tableNames); + triggerEvent(context, ChatEvent.EXPLAIN_TABLES_SELECTED); + } else { + log.info("[ExplainAction] No tables extracted from EXPLAIN, fallback to SelectTablesAction"); + triggerEvent(context, ChatEvent.EXPLAIN_TABLES_NOT_SELECTED); + } + } else { + triggerEvent(context, ChatEvent.EXPLAIN_EXECUTED); + } + } else { + log.warn("[ExplainAction] EXPLAIN execution failed: {}", result.getErrorMessage()); + triggerEvent(context, explainFirst ? ChatEvent.EXPLAIN_TABLES_NOT_SELECTED : ChatEvent.EXPLAIN_FAILED); + } + } catch (Exception e) { + log.warn("[ExplainAction] EXPLAIN failed, will skip silently", e); + triggerEvent(context, explainFirst ? ChatEvent.EXPLAIN_TABLES_NOT_SELECTED : ChatEvent.EXPLAIN_FAILED); + } finally { + removeContext(); + } + } + + private PromptType determinePromptType(ChatQueryRequest request) { + String promptType = StringUtils.isBlank(request.getPromptType()) + ? PromptType.NL_2_SQL.getCode() + : request.getPromptType(); + return PromptType.valueOf(promptType); + } + + private String extractSql(String message) { + if (StringUtils.isBlank(message)) { + return null; + } + + // 尝试从 markdown 代码块中提取 SQL + Matcher matcher = SQL_CODE_BLOCK_PATTERN.matcher(message); + if (matcher.find()) { + String sql = matcher.group(1).trim(); + if (StringUtils.isNotBlank(sql)) { + return sql; + } + } + + // 如果没有代码块,检查是否包含 SQL 关键字 + if (SQL_KEYWORD_PATTERN.matcher(message).find()) { + return message.trim(); + } + + return null; + } + + private void sendExplainToClient(SseEmitter emitter, ExplainResult result) { + try { + JSONObject data = new JSONObject(); + data.put("type", "explain"); + data.put("sql", result.getExplainSql()); + data.put("plan", result.getPlanRows()); + data.put("formatted", result.getFormattedPlan()); + data.put("success", result.isSuccess()); + + emitter.send(SseEmitter.event() + .name("explain") + .data(data.toJSONString())); + } catch (IOException e) { + log.error("[ExplainAction] Failed to send explain result", e); + } + } + + private void triggerEvent(StateContext context, ChatEvent event) { + context.getStateMachine().sendEvent( + Mono.just(MessageBuilder.withPayload(event).build()) + ).subscribe(); + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/statemachine/actions/FetchSchemaAction.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/statemachine/actions/FetchSchemaAction.java new file mode 100644 index 000000000..298f2c11c --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/statemachine/actions/FetchSchemaAction.java @@ -0,0 +1,119 @@ +package ai.chat2db.server.web.api.controller.ai.statemachine.actions; + +import java.util.concurrent.CompletableFuture; + +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.statemachine.StateContext; +import org.springframework.stereotype.Component; + +import ai.chat2db.server.domain.api.service.DatabaseService; +import ai.chat2db.server.domain.api.service.DataSourceService; +import ai.chat2db.server.tools.base.enums.DataSourceTypeEnum; +import ai.chat2db.server.web.api.controller.ai.request.ChatQueryRequest; +import ai.chat2db.server.web.api.controller.ai.statemachine.ChatContext; +import ai.chat2db.server.web.api.controller.ai.statemachine.ChatEvent; +import ai.chat2db.server.web.api.controller.ai.statemachine.ChatState; +import lombok.extern.slf4j.Slf4j; +import reactor.core.publisher.Mono; + +/** + * 获取表结构动作 + */ +@Component +@Slf4j +public class FetchSchemaAction extends BaseChatAction { + + @Autowired + private DatabaseService databaseService; + + @Autowired + private DataSourceService dataSourceService; + + @Override + public void execute(StateContext context) { + log.info("[FetchSchemaAction] execute called"); + ChatContext ctx = getChatContext(context); + log.info("[FetchSchemaAction] uid: {}, cancelled: {}", ctx.getUid(), ctx.isCancelled()); + if (ctx.isCancelled()) { + log.info("[FetchSchemaAction] cancelled, returning"); + return; + } + + sendStateEvent(ctx.getSseEmitter(), + ChatState.FETCHING_TABLE_SCHEMA, "正在获取表结构..."); + + buildContext(ctx); + try { + log.info("[FetchSchemaAction] Starting schema fetch for uid: {}, tableNames: {}", + ctx.getUid(), ctx.getRequest().getTableNames()); + String schemaDdl = fetchSchemaDdl(ctx); + log.info("[FetchSchemaAction] Schema DDL length: {}", schemaDdl != null ? schemaDdl.length() : 0); + ctx.setSchemaDdl(schemaDdl); + + if (CollectionUtils.isNotEmpty(ctx.getRequest().getTableNames())) { + sendSchemaFetched(ctx.getSseEmitter(), schemaDdl); + } + + log.info("[FetchSchemaAction] Sending SCHEMA_FETCHED event for uid: {}", ctx.getUid()); + context.getStateMachine().sendEvent( + Mono.just(MessageBuilder.withPayload(ChatEvent.SCHEMA_FETCHED).build()) + ).subscribe(); + } catch (Exception e) { + log.error("[FetchSchemaAction] Fetch schema failed for uid: {}", ctx.getUid(), e); + sendError(ctx.getSseEmitter(), "获取表结构失败:" + e.getMessage()); + context.getStateMachine().sendEvent( + Mono.just(MessageBuilder.withPayload(ChatEvent.FETCH_SCHEMA_FAILED).build()) + ).subscribe(); + } finally { + removeContext(); + } + } + + private String fetchSchemaDdl(ChatContext ctx) { + ChatQueryRequest request = ctx.getRequest(); + + if (isRedis(request)) { + return ""; + } + + if (hasTableNames(request)) { + return queryTablesWithNames(request); + } else { + return queryAllTables(request); + } + } + + private boolean isRedis(ChatQueryRequest request) { + try { + return DataSourceTypeEnum.REDIS.getCode().equalsIgnoreCase( + dataSourceService.queryDatabaseType(request.getDataSourceId())); + } catch (Exception e) { + log.warn("[FetchSchemaAction] Query database type failed, dataSourceId: {}, error: {}", + request.getDataSourceId(), e.getMessage()); + return false; + } + } + + private boolean hasTableNames(ChatQueryRequest request) { + return CollectionUtils.isNotEmpty(request.getTableNames()); + } + + private String queryTablesWithNames(ChatQueryRequest request) { + return databaseService.buildTableColumn( + request.getDataSourceId(), + request.getDatabaseName(), + request.getSchemaName(), + request.getTableNames() + ); + } + + private String queryAllTables(ChatQueryRequest request) { + return databaseService.queryDatabaseTables( + request.getDataSourceId(), + request.getDatabaseName(), + request.getSchemaName() + ); + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/statemachine/actions/SaveAiCommentAction.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/statemachine/actions/SaveAiCommentAction.java new file mode 100644 index 000000000..a6c0bb1e0 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/statemachine/actions/SaveAiCommentAction.java @@ -0,0 +1,287 @@ +package ai.chat2db.server.web.api.controller.ai.statemachine.actions; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import ai.chat2db.server.web.api.controller.ai.statemachine.ChatEvent; +import ai.chat2db.server.web.api.controller.ai.statemachine.ChatState; +import com.alibaba.fastjson2.annotation.JSONField; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.statemachine.StateContext; +import org.springframework.statemachine.action.Action; +import org.springframework.stereotype.Component; + +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; + +import ai.chat2db.server.domain.core.cache.LuceneIndexManager; +import ai.chat2db.server.domain.core.cache.LuceneIndexManagerFactory; +import ai.chat2db.server.web.api.controller.ai.enums.PromptType; +import ai.chat2db.server.web.api.controller.ai.statemachine.ChatContext; +import ai.chat2db.spi.model.Table; +import ai.chat2db.spi.model.TableColumn; +import lombok.extern.slf4j.Slf4j; + +@Component +@Slf4j +public class SaveAiCommentAction extends BaseChatAction { + + @Autowired + private LuceneIndexManagerFactory managerFactory; + + public void execute(StateContext context) { + ChatContext ctx = getChatContext(context); + log.info("[SaveAiCommentAction] execute called for uid: {}", ctx.getUid()); + + String promptType = ctx.getRequest().getPromptType(); + if (!PromptType.NL_2_COMMENT.getCode().equals(promptType) + && !PromptType.NL_2_COMMENT_BATCH.getCode().equals(promptType)) { + log.debug("[SaveAiCommentAction] Not comment type, skip"); + return; + } + + String content = ctx.getCurrentContent(); + if (StringUtils.isBlank(content)) { + log.warn("[SaveAiCommentAction] No content to save for uid: {}", ctx.getUid()); + return; + } + + Long dataSourceId = ctx.getRequest().getDataSourceId(); + String databaseName = ctx.getRequest().getDatabaseName(); + String schemaName = ctx.getRequest().getSchemaName(); + + try { + if (PromptType.NL_2_COMMENT_BATCH.getCode().equals(promptType)) { + executeBatch(ctx, dataSourceId, databaseName, schemaName, content); + } else { + executeSingle(ctx, dataSourceId, databaseName, schemaName, content); + } + } catch (Exception e) { + log.error("[SaveAiCommentAction] Failed to save aiComment for uid: {}", ctx.getUid(), e); + } + } + + private void executeSingle(ChatContext ctx, Long dataSourceId, String databaseName, + String schemaName, String content) { + AiCommentResult result = parseCommentResult(content); + if (result == null) { + log.warn("[SaveAiCommentAction] Failed to parse comment result for uid: {}", ctx.getUid()); + return; + } + + List tableNames = ctx.getRequest().getTableNames(); + if (CollectionUtils.isEmpty(tableNames)) { + log.warn("[SaveAiCommentAction] No table names for uid: {}", ctx.getUid()); + return; + } + + String tableName = tableNames.get(0); + + if (StringUtils.isNotBlank(result.getTableComment())) { + saveTableAiComment(dataSourceId, databaseName, schemaName, tableName, result.getTableComment()); + } + + if (CollectionUtils.isNotEmpty(result.getColumnComments())) { + saveColumnAiComments(dataSourceId, databaseName, schemaName, tableName, result.getColumnComments()); + } + + log.info("[SaveAiCommentAction] Successfully saved aiComment for uid: {}, table: {}", ctx.getUid(), tableName); + } + + private void executeBatch(ChatContext ctx, Long dataSourceId, String databaseName, + String schemaName, String content) { + List batchResult = parseBatchCommentResult(content); + if (CollectionUtils.isEmpty(batchResult)) { + log.warn("[SaveAiCommentAction] Failed to parse batch comment result for uid: {}", ctx.getUid()); + return; + } + + saveBatchTableAiComments(dataSourceId, databaseName, schemaName, batchResult); + log.info("[SaveAiCommentAction] Successfully saved batch aiComment for uid: {}, count: {}", + ctx.getUid(), batchResult.size()); + } + + private List parseBatchCommentResult(String content) { + String json = extractBatchJson(content); + if (StringUtils.isBlank(json)) { + return null; + } + + try { + JSONObject obj = JSONObject.parseObject(json); + JSONArray tables = obj.getJSONArray("tables"); + if (tables == null || tables.isEmpty()) { + return null; + } + return tables.toJavaList(BatchTableComment.class); + } catch (Exception e) { + log.error("[SaveAiCommentAction] Failed to parse batch JSON: {}", content, e); + return null; + } + } + + private String extractBatchJson(String content) { + if (StringUtils.isBlank(content)) { + return null; + } + + Pattern pattern = Pattern.compile("\\{[\\s\\S]*\"tables\"[\\s\\S]*\\}"); + Matcher matcher = pattern.matcher(content); + if (matcher.find()) { + return matcher.group(); + } + + String trimmed = content.trim(); + if (trimmed.startsWith("{") && trimmed.endsWith("}")) { + return trimmed; + } + + int start = content.indexOf('{'); + int end = content.lastIndexOf('}'); + if (start >= 0 && end > start) { + return content.substring(start, end + 1); + } + + return null; + } + + private void saveBatchTableAiComments(Long dataSourceId, String databaseName, String schemaName, + List batchComments) { + try { + LuceneIndexManager

manager = managerFactory.getManager(dataSourceId); + + List
tables = batchComments.stream() + .filter(btc -> StringUtils.isNotBlank(btc.getTableName()) && StringUtils.isNotBlank(btc.getTableComment())) + .map(btc -> { + Table table = new Table(); + table.setDatabaseName(databaseName); + table.setSchemaName(schemaName); + table.setName(btc.getTableName()); + table.setAiComment(btc.getTableComment()); + return table; + }) + .collect(Collectors.toList()); + + if (CollectionUtils.isNotEmpty(tables)) { + manager.updateDocuments(tables, null, false); + log.info("[SaveAiCommentAction] Saved {} batch table aiComments", tables.size()); + } + } catch (Exception e) { + log.error("[SaveAiCommentAction] Failed to save batch table aiComments", e); + } + } + + private AiCommentResult parseCommentResult(String content) { + String json = extractJson(content); + if (StringUtils.isBlank(json)) { + return null; + } + + try { + return JSONObject.parseObject(json, AiCommentResult.class); + } catch (Exception e) { + log.error("[SaveAiCommentAction] Failed to parse JSON: {}", content, e); + return null; + } + } + + private String extractJson(String content) { + if (StringUtils.isBlank(content)) { + return null; + } + + Pattern pattern = Pattern.compile("\\{[\\s\\S]*\"table_comment\"[\\s\\S]*\"column_comments\"[\\s\\S]*\\}"); + Matcher matcher = pattern.matcher(content); + if (matcher.find()) { + return matcher.group(); + } + + String trimmed = content.trim(); + if (trimmed.startsWith("{") && trimmed.endsWith("}")) { + return trimmed; + } + + int start = content.indexOf('{'); + int end = content.lastIndexOf('}'); + if (start >= 0 && end > start) { + return content.substring(start, end + 1); + } + + return null; + } + + private void saveTableAiComment(Long dataSourceId, String databaseName, String schemaName, + String tableName, String aiComment) { + try { + LuceneIndexManager
manager = managerFactory.getManager(dataSourceId); + + Table table = new Table(); + table.setDatabaseName(databaseName); + table.setSchemaName(schemaName); + table.setName(tableName); + table.setAiComment(aiComment); + + manager.updateDocument(table); + log.info("[SaveAiCommentAction] Saved table aiComment: {}.{}", tableName, aiComment); + } catch (Exception e) { + log.error("[SaveAiCommentAction] Failed to save table aiComment: {}", tableName, e); + } + } + + private void saveColumnAiComments(Long dataSourceId, String databaseName, String schemaName, + String tableName, List columnComments) { + try { + LuceneIndexManager manager = managerFactory.getManager(dataSourceId); + + List columns = columnComments.stream() + .filter(col -> StringUtils.isNotBlank(col.getColumnName()) && StringUtils.isNotBlank(col.getComment())) + .map(col -> { + TableColumn column = new TableColumn(); + column.setDatabaseName(databaseName); + column.setSchemaName(schemaName); + column.setTableName(tableName); + column.setName(col.getColumnName()); + column.setAiComment(col.getComment()); + return column; + }) + .collect(Collectors.toList()); + + if (CollectionUtils.isNotEmpty(columns)) { + manager.updateDocuments(columns, null, false); + log.info("[SaveAiCommentAction] Saved {} column aiComments for table: {}", columns.size(), tableName); + } + } catch (Exception e) { + log.error("[SaveAiCommentAction] Failed to save column aiComments", e); + } + } + + @Data + private static class AiCommentResult { + @JSONField(name = "table_comment") + private String tableComment; + @JSONField(name = "column_comments") + private List columnComments; + } + + @Data + private static class ColumnComment { + @JSONField(name = "column_name") + private String columnName; + @JSONField(name = "comment") + private String comment; + } + + @Data + private static class BatchTableComment { + @JSONField(name = "table_name") + private String tableName; + @JSONField(name = "table_comment") + private String tableComment; + } +} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/statemachine/actions/SelectTablesAction.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/statemachine/actions/SelectTablesAction.java new file mode 100644 index 000000000..b5c05f479 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/statemachine/actions/SelectTablesAction.java @@ -0,0 +1,163 @@ +package ai.chat2db.server.web.api.controller.ai.statemachine.actions; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import ai.chat2db.server.web.api.config.AiChatConfig; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.statemachine.StateContext; +import org.springframework.stereotype.Component; + +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; + +import ai.chat2db.server.domain.api.service.DatabaseService; +import ai.chat2db.server.web.api.controller.ai.enums.PromptType; +import ai.chat2db.server.web.api.controller.ai.prompt.PromptBuilder; +import ai.chat2db.server.web.api.controller.ai.prompt.PromptContext; +import ai.chat2db.server.web.api.controller.ai.prompt.PreviousSqlResolver; +import ai.chat2db.server.web.api.controller.ai.statemachine.ChatContext; +import ai.chat2db.server.web.api.controller.ai.statemachine.ChatEvent; +import ai.chat2db.server.web.api.controller.ai.statemachine.ChatState; +import cn.hutool.core.util.StrUtil; +import lombok.extern.slf4j.Slf4j; +import reactor.core.publisher.Mono; + +/** + * 自动选择表动作 + */ +@Component +@Slf4j +public class SelectTablesAction extends BaseChatAction { + + @Autowired + private PromptBuilder promptBuilder; + + @Autowired + private DatabaseService databaseService; + + @Autowired + private AiChatConfig aiChatConfig; + + @Autowired + private PreviousSqlResolver previousSqlResolver; + + @Override + public void execute(StateContext context) { + log.info("[SelectTablesAction] execute called"); + ChatContext ctx = getChatContext(context); + log.info("[SelectTablesAction] uid: {}, cancelled: {}", ctx.getUid(), ctx.isCancelled()); + if (ctx.isCancelled()) { + log.info("[SelectTablesAction] cancelled, returning"); + return; + } + + sendStateEvent(ctx.getSseEmitter(), + ChatState.AUTO_SELECTING_TABLES, "正在选择相关表..."); + + buildContext(ctx); + try { + log.info("[SelectTablesAction] Starting table selection for uid: {}", ctx.getUid()); + List tableNames = selectTables(ctx); + log.info("[SelectTablesAction] Selected tables: {}", tableNames); + + if (CollectionUtils.isNotEmpty(tableNames)) { + ctx.getRequest().setTableNames(tableNames); + ctx.setSelectedTables(tableNames); + sendTablesSelected(ctx.getSseEmitter(), tableNames); + } + + log.info("[SelectTablesAction] Sending AUTO_SELECT_DONE event for uid: {}", ctx.getUid()); + context.getStateMachine().sendEvent( + Mono.just(MessageBuilder.withPayload(ChatEvent.AUTO_SELECT_DONE).build()) + ).subscribe(); + } catch (Exception e) { + log.error("[SelectTablesAction] Auto select tables failed for uid: {}", ctx.getUid(), e); + sendError(ctx.getSseEmitter(), "选表失败:" + e.getMessage()); + context.getStateMachine().sendEvent( + Mono.just(MessageBuilder.withPayload(ChatEvent.AUTO_SELECT_FAILED).build()) + ).subscribe(); + } finally { + removeContext(); + } + } + + private List selectTables(ChatContext ctx) { + String selectPrompt = buildSelectPrompt(ctx); + log.info("[SelectTablesAction] Select prompt for uid: {}:\n{}", ctx.getUid(), selectPrompt); + ChatResponse chatResponse = aiChatConfig.createFastChatClient() + .prompt(selectPrompt) + .call() + .chatResponse(); + + String content = extractContent(chatResponse); + log.info("[SelectTablesAction] AI response for uid: {}:\n{}", ctx.getUid(), content); + return parseTableNames(content, ctx.getRequest().getTableNames()); + } + + private String buildSelectPrompt(ChatContext ctx) { + String schemaDdl = databaseService.queryDatabaseTables( + ctx.getRequest().getDataSourceId(), + ctx.getRequest().getDatabaseName(), + ctx.getRequest().getSchemaName() + ); + + PromptContext promptContext = PromptContext.builder() + .promptType(PromptType.SELECT_TABLES) + .message(ctx.getRequest().getMessage()) + .schemaDdl(schemaDdl) + .previousSql(previousSqlResolver.resolve(ctx.getRequest())) + .build(); + + return promptBuilder.context(promptContext).build(); + } + + private String extractContent(ChatResponse chatResponse) { + if (chatResponse == null || chatResponse.getResult() == null + || chatResponse.getResult().getOutput() == null) { + return null; + } + return chatResponse.getResult().getOutput().getText(); + } + + private List parseTableNames(String content, List existingTableNames) { + if (StrUtil.isBlank(content)) { + return null; + } + + String json = normalizeJson(content); + + try { + JSONObject obj = JSONObject.parseObject(json); + JSONArray tableNames = obj.getJSONArray("table_names"); + if (tableNames == null || tableNames.isEmpty()) { + return null; + } + return tableNames.toJavaList(String.class); + } catch (Exception ignored) { + } + + return fallbackParseTableNames(content, existingTableNames); + } + + private String normalizeJson(String content) { + String json = content.trim(); + if (!json.startsWith("{") && json.contains("{") && json.contains("}")) { + json = json.substring(json.indexOf('{'), json.lastIndexOf('}') + 1); + } + return json; + } + + private List fallbackParseTableNames(String content, List existingTableNames) { + if (existingTableNames == null || existingTableNames.isEmpty()) { + return null; + } + return existingTableNames.stream() + .filter(name -> StringUtils.containsIgnoreCase(content, name)) + .toList(); + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/statemachine/actions/StreamAction.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/statemachine/actions/StreamAction.java new file mode 100644 index 000000000..6b23b4889 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/statemachine/actions/StreamAction.java @@ -0,0 +1,264 @@ +package ai.chat2db.server.web.api.controller.ai.statemachine.actions; + +import java.io.IOException; +import java.util.Map; +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import ai.chat2db.server.domain.api.service.AiConversationService; +import ai.chat2db.server.tools.common.model.LoginUser; +import org.springframework.ai.chat.metadata.ChatGenerationMetadata; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.model.Generation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.statemachine.StateContext; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import com.alibaba.fastjson2.JSONObject; + +import ai.chat2db.server.web.api.controller.ai.enums.PromptType; +import ai.chat2db.server.web.api.controller.ai.prompt.PromptValidator; +import ai.chat2db.server.web.api.controller.ai.request.ChatQueryRequest; +import ai.chat2db.server.web.api.controller.ai.statemachine.ChatContext; +import ai.chat2db.server.web.api.controller.ai.statemachine.ChatEvent; +import ai.chat2db.server.web.api.controller.ai.statemachine.ChatState; +import lombok.extern.slf4j.Slf4j; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; + +/** + * AI 流式输出动作 + */ +@Component +@Slf4j +public class StreamAction extends BaseChatAction { + + private static final Pattern SQL_CODE_BLOCK = Pattern.compile("```sql\\s*([\\s\\S]*?)```", Pattern.CASE_INSENSITIVE); + private static final Pattern GENERIC_CODE_BLOCK = Pattern.compile("```\\s*([\\s\\S]*?)```"); + private static final Pattern SQL_KEYWORDS = Pattern.compile("\\b(select|insert|update|delete|create|alter|drop)\\b", + Pattern.CASE_INSENSITIVE); + + @Autowired + private PromptValidator promptValidator; + + @Autowired + private AiConversationService aiConversationService; + + @Override + public void execute(StateContext context) { + ChatContext ctx = getChatContext(context); + if (ctx.isCancelled()) { + log.info("[StreamAction] Skip cancelled request, uid: {}", ctx.getUid()); + return; + } + + String prompt = ctx.getBuiltPrompt(); + + if (!promptValidator.isValidLength(prompt)) { + log.warn("[StreamAction] Prompt exceeds max length, uid: {}, promptLength: {}", + ctx.getUid(), prompt == null ? 0 : prompt.length()); + sendError(ctx.getSseEmitter(), "提示语超出最大长度"); + context.getStateMachine().sendEvent( + Mono.just(MessageBuilder.withPayload(ChatEvent.AI_CALL_FAILED).build()) + ).subscribe(); + return; + } + + try { + sendStateEvent(ctx.getSseEmitter(), ChatState.STREAMING, "AI 正在生成..."); + log.info("[StreamAction] Starting AI stream, uid: {}, promptLength: {}", + ctx.getUid(), prompt == null ? 0 : prompt.length()); + + Flux flux = ctx.getChatClient().prompt() + .user(prompt) + .stream() + .chatResponse(); + flux.publishOn(Schedulers.boundedElastic()) + .doOnNext(chatResponse -> processChatResponse(ctx, chatResponse)) + .doOnError(error -> handleStreamError(ctx, error, context)) + .doOnComplete(() -> handleStreamComplete(ctx, context)) + .subscribe(); + + } catch (Exception e) { + log.error("[StreamAction] Start streaming failed for uid: {}", ctx.getUid(), e); + sendError(ctx.getSseEmitter(), "启动 AI 流式调用失败:" + e.getMessage()); + context.getStateMachine().sendEvent( + Mono.just(MessageBuilder.withPayload(ChatEvent.AI_CALL_FAILED).build()) + ).subscribe(); + } + } + + /** + * 处理每个聊天响应 + */ + private void processChatResponse(ChatContext ctx, ChatResponse chatResponse) { + if (ctx.isCancelled()) { + log.info("[StreamAction] Stream cancelled by user for uid: {}", ctx.getUid()); + throw new RuntimeException("Cancelled by user"); + } + + JSONObject data = new JSONObject(); + Generation generation = chatResponse.getResult(); + + String content = generation.getOutput().getText(); + String thinking = extractThinkingContent(generation); + + if (content != null && !content.isEmpty()) { + data.put("content", content); + appendContent(ctx, content); + } + if (thinking != null && !thinking.isEmpty()) { + data.put("thinking", thinking); + appendThinking(ctx, thinking); + log.debug("[StreamAction] Thinking content: {}", thinking); + } + + if (!data.isEmpty()) { + try { + ctx.getSseEmitter().send(SseEmitter.event() + .id(ctx.getUid()) + .name("message") + .data(data.toJSONString()) + .reconnectTime(3000)); + } catch (IOException e) { + log.error("[StreamAction] Failed to send SSE message for uid: {}", ctx.getUid(), e); + throw new RuntimeException(e); + } + } + } + + private synchronized void appendContent(ChatContext ctx, String content) { + String current = ctx.getCurrentContent(); + ctx.setCurrentContent(current == null ? content : current + content); + } + + private synchronized void appendThinking(ChatContext ctx, String thinking) { + String current = ctx.getCurrentThinking(); + ctx.setCurrentThinking(current == null ? thinking : current + thinking); + } + + /** + * 从生成结果中提取思考内容 + */ + private String extractThinkingContent(Generation generation) { + ChatGenerationMetadata metadata = generation.getMetadata(); + if (metadata.containsKey("thinking")) { + Object thinkingObj = metadata.get("thinking"); + if (thinkingObj instanceof String) { + return (String) thinkingObj; + } else if (thinkingObj != null) { + return thinkingObj.toString(); + } + } else { + Map outputMetadata = generation.getOutput().getMetadata(); + if (outputMetadata.containsKey("reasoningContent")) { + return outputMetadata.get("reasoningContent").toString(); + } else if (outputMetadata.containsKey("reasoning_details")) { + return outputMetadata.get("reasoning_details").toString(); + } + } + return null; + } + + /** + * 处理流式错误 + */ + private void handleStreamError(ChatContext ctx, Throwable error, StateContext context) { + log.error("[StreamAction] Stream error for uid: {}", ctx.getUid(), error); + if (!ctx.isCancelled()) { + sendError(ctx.getSseEmitter(), "AI 调用失败:" + error.getMessage()); + } + try { + ctx.getSseEmitter().completeWithError(error); + } catch (Exception ignored) { + // 忽略完成时的异常 + } + context.getStateMachine().sendEvent( + MessageBuilder.withPayload(ChatEvent.AI_CALL_FAILED).build() + ); + } + + /** + * 处理流式完成 + */ + private void handleStreamComplete(ChatContext ctx, StateContext context) { + log.info("[StreamAction] Stream completed for uid: {}", ctx.getUid()); + try { + persistTurn(ctx); + ctx.getSseEmitter().send(SseEmitter.event() + .id("[DONE]") + .data("[DONE]") + .reconnectTime(3000)); + } catch (IOException ignored) { + // 忽略发送完成消息时的异常 + } finally { + ctx.getSseEmitter().complete(); + } + context.getStateMachine().sendEvent( + MessageBuilder.withPayload(ChatEvent.STREAM_FINISHED).build() + ); + } + + private void persistTurn(ChatContext ctx) { + ChatQueryRequest request = ctx.getRequest(); + if (request == null) { + return; + } + String conversationId = request.getConversationId(); + String userContent = request.getMessage(); + String assistantContent = ctx.getCurrentContent(); + String thinking = ctx.getCurrentThinking(); + if (conversationId == null || conversationId.isEmpty() + || userContent == null || userContent.isEmpty() + || assistantContent == null || assistantContent.isEmpty()) { + return; + } + LoginUser loginUser = ctx.getLoginUser(); + Long userId = loginUser != null ? loginUser.getId() : null; + String promptType = request.getPromptType(); + String sqlExtracted = extractSql(assistantContent); + String userMessageId = ctx.getUserMessageId() != null ? ctx.getUserMessageId() : UUID.randomUUID().toString(); + String assistantMessageId = UUID.randomUUID().toString(); + + buildContext(ctx); + try { + aiConversationService.appendMessageTurn( + conversationId, + userId, + userMessageId, + userContent, + assistantMessageId, + assistantContent, + thinking, + promptType, + sqlExtracted + ); + } catch (Exception e) { + log.error("[StreamAction] Failed to persist AI conversation turn for uid: {}", ctx.getUid(), e); + } finally { + removeContext(); + } + } + + private String extractSql(String content) { + if (content == null) { + return null; + } + Matcher sqlMatcher = SQL_CODE_BLOCK.matcher(content); + if (sqlMatcher.find()) { + return sqlMatcher.group(1).trim(); + } + Matcher codeMatcher = GENERIC_CODE_BLOCK.matcher(content); + if (codeMatcher.find()) { + String code = codeMatcher.group(1).trim(); + if (SQL_KEYWORDS.matcher(code).find()) { + return code; + } + } + return null; + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/statemachine/helper/ExplainResult.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/statemachine/helper/ExplainResult.java new file mode 100644 index 000000000..e2f25cc82 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/statemachine/helper/ExplainResult.java @@ -0,0 +1,16 @@ +package ai.chat2db.server.web.api.controller.ai.statemachine.helper; + +import java.util.List; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class ExplainResult { + private boolean success; + private String explainSql; + private String formattedPlan; + private List> planRows; + private String errorMessage; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/statemachine/helper/ExplainTableNameExtractor.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/statemachine/helper/ExplainTableNameExtractor.java new file mode 100644 index 000000000..dda410cff --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/statemachine/helper/ExplainTableNameExtractor.java @@ -0,0 +1,123 @@ +package ai.chat2db.server.web.api.controller.ai.statemachine.helper; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; + +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.Statements; +import net.sf.jsqlparser.util.TablesNamesFinder; + +/** + * Extracts table names from EXPLAIN output before falling back to SQL parsing. + */ +public final class ExplainTableNameExtractor { + + private static final Set TABLE_HEADERS = Set.of("table", "table_name", "tablename", "relation_name"); + private static final Pattern PLAN_ON_TABLE_PATTERN = Pattern.compile( + "(?i)\\bon\\s+((?:`[^`]+`|\"[^\"]+\"|\\[[^]]+]|[\\w$]+)(?:\\s*\\.\\s*(?:`[^`]+`|\"[^\"]+\"|\\[[^]]+]|[\\w$]+))*)"); + + private ExplainTableNameExtractor() { + } + + public static List extractTableNames(String sql, ExplainResult result) { + List tableNames = new ArrayList<>(); + + addFromPlanRows(tableNames, result); + addFromFormattedPlan(tableNames, result); + addFromSql(tableNames, sql); + + return tableNames; + } + + private static void addFromPlanRows(List tableNames, ExplainResult result) { + if (result == null || CollectionUtils.isEmpty(result.getPlanRows()) || result.getPlanRows().size() < 2) { + return; + } + + List headers = result.getPlanRows().get(0); + if (CollectionUtils.isEmpty(headers)) { + return; + } + + for (int i = 0; i < headers.size(); i++) { + if (!TABLE_HEADERS.contains(normalizeHeader(headers.get(i)))) { + continue; + } + for (int rowIndex = 1; rowIndex < result.getPlanRows().size(); rowIndex++) { + List row = result.getPlanRows().get(rowIndex); + if (row != null && row.size() > i) { + addTableName(tableNames, row.get(i)); + } + } + } + } + + private static void addFromFormattedPlan(List tableNames, ExplainResult result) { + if (result == null || StringUtils.isBlank(result.getFormattedPlan())) { + return; + } + + Matcher matcher = PLAN_ON_TABLE_PATTERN.matcher(result.getFormattedPlan()); + while (matcher.find()) { + addTableName(tableNames, matcher.group(1)); + } + } + + private static void addFromSql(List tableNames, String sql) { + if (StringUtils.isBlank(sql)) { + return; + } + + try { + Statements statements = CCJSqlParserUtil.parseStatements(sql); + TablesNamesFinder finder = new TablesNamesFinder(); + for (Statement statement : statements.getStatements()) { + finder.getTableList(statement).forEach(tableName -> addTableName(tableNames, tableName)); + } + } catch (Exception ignored) { + } + } + + private static String normalizeHeader(String header) { + return StringUtils.defaultString(header).trim().toLowerCase(Locale.ROOT).replace(" ", "_"); + } + + private static void addTableName(List tableNames, String rawTableName) { + String tableName = normalizeTableName(rawTableName); + if (StringUtils.isBlank(tableName) || tableNames.contains(tableName)) { + return; + } + tableNames.add(tableName); + } + + private static String normalizeTableName(String rawTableName) { + if (StringUtils.isBlank(rawTableName)) { + return null; + } + + String tableName = rawTableName.trim(); + int whitespaceIndex = StringUtils.indexOfAny(tableName, ' ', '\t', '\r', '\n'); + if (whitespaceIndex > 0) { + tableName = tableName.substring(0, whitespaceIndex); + } + tableName = tableName.replace("`", "").replace("\"", "").replace("[", "").replace("]", ""); + + int lastDotIndex = tableName.lastIndexOf('.'); + if (lastDotIndex >= 0 && lastDotIndex < tableName.length() - 1) { + tableName = tableName.substring(lastDotIndex + 1).trim(); + } + + if (StringUtils.startsWith(tableName, "<") || "null".equalsIgnoreCase(tableName)) { + return null; + } + return tableName; + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/statemachine/helper/SqlExplainHelper.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/statemachine/helper/SqlExplainHelper.java new file mode 100644 index 000000000..258800da5 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/statemachine/helper/SqlExplainHelper.java @@ -0,0 +1,100 @@ +package ai.chat2db.server.web.api.controller.ai.statemachine.helper; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.stereotype.Component; + +import ai.chat2db.spi.CommandExecutor; +import ai.chat2db.spi.MetaData; +import ai.chat2db.spi.SqlBuilder; +import ai.chat2db.spi.model.ExecuteResult; +import ai.chat2db.spi.model.Header; +import ai.chat2db.spi.sql.Chat2DBContext; +import lombok.Builder; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Component +public class SqlExplainHelper { + + public ExplainResult executeExplain(String sql) { + try { + SqlBuilder sqlBuilder = Chat2DBContext.getSqlBuilder(); + String explainSql = sqlBuilder.buildExplainSql(sql); + + if (explainSql == null) { + return ExplainResult.builder() + .success(false) + .errorMessage("EXPLAIN not supported for this database type") + .explainSql(sql) + .build(); + } + + MetaData metaData = Chat2DBContext.getMetaData(); + CommandExecutor executor = metaData.getCommandExecutor(); + java.sql.Connection connection = Chat2DBContext.getConnection(); + + ExecuteResult result = executor.execute(explainSql, connection, false, null, null, metaData.getValueHandler()); + + return ExplainResult.builder() + .success(result.getSuccess()) + .explainSql(explainSql) + .planRows(parsePlanRows(result)) + .formattedPlan(formatExplainResult(result)) + .errorMessage(result.getMessage()) + .build(); + } catch (Exception e) { + log.error("[SqlExplainHelper] Failed to execute EXPLAIN", e); + return ExplainResult.builder() + .success(false) + .errorMessage(e.getMessage()) + .explainSql(sql) + .build(); + } + } + + private List> parsePlanRows(ExecuteResult result) { + List> rows = new ArrayList<>(); + + if (CollectionUtils.isEmpty(result.getDataList())) { + return rows; + } + + // 添加表头 + if (CollectionUtils.isNotEmpty(result.getHeaderList())) { + rows.add(result.getHeaderList().stream() + .map(header -> header == null ? "null" : header.getName()) + .collect(Collectors.toList())); + } + + // 添加数据行 + for (List dataRow : result.getDataList()) { + rows.add(new ArrayList<>(dataRow)); + } + + return rows; + } + + private String formatExplainResult(ExecuteResult result) { + StringBuilder sb = new StringBuilder(); + + if (CollectionUtils.isNotEmpty(result.getHeaderList())) { + sb.append(result.getHeaderList().stream() + .map(Header::getName) + .collect(Collectors.joining(" | "))).append("\n"); + sb.append("-".repeat(80)).append("\n"); + } + + if (CollectionUtils.isNotEmpty(result.getDataList())) { + for (List row : result.getDataList()) { + sb.append(String.join(" | ", row)).append("\n"); + } + } + + return sb.toString(); + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/task/AiConversationTitleTask.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/task/AiConversationTitleTask.java new file mode 100644 index 000000000..7ec87fef2 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/task/AiConversationTitleTask.java @@ -0,0 +1,104 @@ +package ai.chat2db.server.web.api.controller.ai.task; + +import ai.chat2db.server.domain.api.service.AiConversationService; +import ai.chat2db.server.domain.repository.Dbutils; +import ai.chat2db.server.web.api.config.AiChatConfig; +import ai.chat2db.server.web.api.controller.ai.enums.PromptType; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +/** + * AI 会话标题异步生成任务 + *

+ * 流式 SSE 入口检测到该会话"首条消息"时,异步调用 + * {@link PromptType#TITLE_GENERATION} 让模型为会话生成短标题。 + * 失败时降级为截取首条消息前 20 字。 + */ +@Slf4j +@Component +public class AiConversationTitleTask { + + private static final int MAX_TITLE_LENGTH = 50; + private static final int FALLBACK_TITLE_LENGTH = 20; + + @Autowired + private AiChatConfig aiChatConfig; + + @Autowired + private AiConversationService aiConversationService; + + @Async + public void generateTitleAsync(String conversationId, String firstUserMessage) { + if (StringUtils.isBlank(conversationId) || StringUtils.isBlank(firstUserMessage)) { + return; + } + + String title = generateTitle(conversationId, firstUserMessage); + persistTitle(conversationId, title); + } + + /** + * 调用 AI 生成标题,失败或结果为空时降级为截取首条消息 + */ + private String generateTitle(String conversationId, String firstUserMessage) { + try { + String title = aiChatConfig.createChatClient(PromptType.TITLE_GENERATION) + .prompt() + .user(firstUserMessage) + .call() + .content(); + title = normalize(title); + if (StringUtils.isBlank(title)) { + title = fallbackTitle(firstUserMessage); + } + log.info("[AiConversationTitleTask] Generated title for {}: {}", conversationId, title); + return title; + } catch (Exception e) { + log.warn("[AiConversationTitleTask] Failed to generate AI title for {}: {}", + conversationId, e.getMessage()); + return fallbackTitle(firstUserMessage); + } + } + + /** + * 持久化标题到数据库 + */ + private void persistTitle(String conversationId, String title) { + try { + Dbutils.setSession(); + aiConversationService.updateTitle(conversationId, title); + } catch (Exception e) { + log.error("[AiConversationTitleTask] Failed to persist title for {}", conversationId, e); + } finally { + Dbutils.removeSession(); + } + } + + private String normalize(String raw) { + if (raw == null) { + return null; + } + String trimmed = raw.trim(); + if (trimmed.startsWith("\"") && trimmed.endsWith("\"") && trimmed.length() >= 2) { + trimmed = trimmed.substring(1, trimmed.length() - 1); + } + trimmed = trimmed.replaceAll("\\s+", " ").trim(); + if (trimmed.length() > MAX_TITLE_LENGTH) { + trimmed = trimmed.substring(0, MAX_TITLE_LENGTH); + } + return trimmed; + } + + private String fallbackTitle(String message) { + if (message == null) { + return "新对话"; + } + String compact = message.replaceAll("\\s+", " ").trim(); + return compact.length() > FALLBACK_TITLE_LENGTH + ? compact.substring(0, FALLBACK_TITLE_LENGTH) + "..." + : compact; + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/tongyi/client/TongyiChatAIClient.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/tongyi/client/TongyiChatAIClient.java deleted file mode 100644 index 7fe09c0ab..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/tongyi/client/TongyiChatAIClient.java +++ /dev/null @@ -1,80 +0,0 @@ - -package ai.chat2db.server.web.api.controller.ai.tongyi.client; - -import ai.chat2db.server.domain.api.model.Config; -import ai.chat2db.server.domain.api.service.ConfigService; -import ai.chat2db.server.web.api.util.ApplicationContextUtil; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; - -/** - * @author moji - * @date 23/09/26 - */ -@Slf4j -public class TongyiChatAIClient { - - /** - * TONGYI OPENAI KEY - */ - public static final String TONGYI_API_KEY = "tongyi.chatgpt.apiKey"; - - /** - * TONGYI OPENAI HOST - */ - public static final String TONGYI_HOST = "tongyi.host"; - - /** - * TONGYI OPENAI model - */ - public static final String TONGYI_MODEL= "tongyi.model"; - - /** - * TONGYI OPENAI embedding model - */ - public static final String TONGYI_EMBEDDING_MODEL = "tongyi.embedding.model"; - - private static TongyiChatAIStreamClient TONGYI_AI_CLIENT; - - - public static TongyiChatAIStreamClient getInstance() { - if (TONGYI_AI_CLIENT != null) { - return TONGYI_AI_CLIENT; - } else { - return singleton(); - } - } - - private static TongyiChatAIStreamClient singleton() { - if (TONGYI_AI_CLIENT == null) { - synchronized (TongyiChatAIClient.class) { - if (TONGYI_AI_CLIENT == null) { - refresh(); - } - } - } - return TONGYI_AI_CLIENT; - } - - public static void refresh() { - String apiKey = ""; - String apiHost = ""; - String model = ""; - ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); - Config apiHostConfig = configService.find(TONGYI_HOST).getData(); - if (apiHostConfig != null && StringUtils.isNotBlank(apiHostConfig.getContent())) { - apiHost = apiHostConfig.getContent(); - } - Config config = configService.find(TONGYI_API_KEY).getData(); - if (config != null && StringUtils.isNotBlank(config.getContent())) { - apiKey = config.getContent(); - } - Config deployConfig = configService.find(TONGYI_MODEL).getData(); - if (deployConfig != null && StringUtils.isNotBlank(deployConfig.getContent())) { - model = deployConfig.getContent(); - } - TONGYI_AI_CLIENT = TongyiChatAIStreamClient.builder().apiKey(apiKey).apiHost(apiHost).model(model) - .build(); - } - -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/tongyi/client/TongyiChatAIStreamClient.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/tongyi/client/TongyiChatAIStreamClient.java deleted file mode 100644 index 36e71614d..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/tongyi/client/TongyiChatAIStreamClient.java +++ /dev/null @@ -1,212 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.tongyi.client; - -import ai.chat2db.server.tools.common.exception.ParamBusinessException; -import ai.chat2db.server.web.api.controller.ai.fastchat.interceptor.FastChatHeaderAuthorizationInterceptor; -import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatMessage; -import ai.chat2db.server.web.api.controller.ai.tongyi.model.TongyiChatCompletionsOptions; -import ai.chat2db.server.web.api.controller.ai.tongyi.model.TongyiChatMessage; -import cn.hutool.http.ContentType; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import okhttp3.MediaType; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.sse.EventSource; -import okhttp3.sse.EventSourceListener; -import okhttp3.sse.EventSources; -import org.apache.commons.collections4.CollectionUtils; -import org.jetbrains.annotations.NotNull; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.TimeUnit; - -/** - * tongyi Chat Aligned Client - * - * @author moji - */ -@Slf4j -public class TongyiChatAIStreamClient { - - /** - * apikey - */ - @Getter - @NotNull - private String apiKey; - - /** - * apiHost - */ - @Getter - @NotNull - private String apiHost; - - /** - * model - */ - @Getter - private String model; - - /** - * embeddingModel - */ - @Getter - private String embeddingModel; - - /** - * okHttpClient - */ - @Getter - private OkHttpClient okHttpClient; - - - /** - * @param builder - */ - private TongyiChatAIStreamClient(Builder builder) { - this.apiKey = builder.apiKey; - this.apiHost = builder.apiHost; - this.model = builder.model; - this.embeddingModel = builder.embeddingModel; - if (Objects.isNull(builder.okHttpClient)) { - builder.okHttpClient = this.okHttpClient(); - } - okHttpClient = builder.okHttpClient; - } - - /** - * okhttpclient - */ - private OkHttpClient okHttpClient() { - OkHttpClient okHttpClient = new OkHttpClient - .Builder() - .addInterceptor(new FastChatHeaderAuthorizationInterceptor(this.apiKey)) - .connectTimeout(10, TimeUnit.SECONDS) - .writeTimeout(50, TimeUnit.SECONDS) - .readTimeout(50, TimeUnit.SECONDS) - .build(); - return okHttpClient; - } - - /** - * 构造 - * - * @return - */ - public static TongyiChatAIStreamClient.Builder builder() { - return new TongyiChatAIStreamClient.Builder(); - } - - /** - * builder - */ - public static final class Builder { - private String apiKey; - - private String apiHost; - - private String model; - - private String embeddingModel; - - /** - * OkhttpClient - */ - private OkHttpClient okHttpClient; - - public Builder() { - } - - public TongyiChatAIStreamClient.Builder apiKey(String apiKeyValue) { - this.apiKey = apiKeyValue; - return this; - } - - /** - * @param apiHostValue - * @return - */ - public TongyiChatAIStreamClient.Builder apiHost(String apiHostValue) { - this.apiHost = apiHostValue; - return this; - } - - /** - * @param modelValue - * @return - */ - public TongyiChatAIStreamClient.Builder model(String modelValue) { - this.model = modelValue; - return this; - } - - public TongyiChatAIStreamClient.Builder embeddingModel(String embeddingModelValue) { - this.embeddingModel = embeddingModelValue; - return this; - } - - public TongyiChatAIStreamClient.Builder okHttpClient(OkHttpClient val) { - this.okHttpClient = val; - return this; - } - - public TongyiChatAIStreamClient build() { - return new TongyiChatAIStreamClient(this); - } - - } - - /** - * 问答接口 stream 形式 - * - * @param chatMessages - * @param eventSourceListener - */ - public void streamCompletions(List chatMessages, EventSourceListener eventSourceListener) { - if (CollectionUtils.isEmpty(chatMessages)) { - log.error("param error:Tongyi Chat Prompt cannot be empty"); - throw new ParamBusinessException("prompt"); - } - if (Objects.isNull(eventSourceListener)) { - log.error("param error:TongyiChatEventSourceListener cannot be empty"); - throw new ParamBusinessException(); - } - log.info("Tongyi Chat AI, prompt:{}", chatMessages.get(chatMessages.size() - 1).getContent()); - try { - - TongyiChatCompletionsOptions chatCompletionsOptions = new TongyiChatCompletionsOptions(); - chatCompletionsOptions.setStream(true); - chatCompletionsOptions.setModel(this.model); - Map parameters = new HashMap<>(); - parameters.put("result_format", "text"); - chatCompletionsOptions.setParameters(parameters); - TongyiChatMessage tongyiChatMessage = new TongyiChatMessage(); - tongyiChatMessage.setMessages(chatMessages); - chatCompletionsOptions.setInput(tongyiChatMessage); - - EventSource.Factory factory = EventSources.createFactory(this.okHttpClient); - ObjectMapper mapper = new ObjectMapper(); - mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - String requestBody = mapper.writeValueAsString(chatCompletionsOptions); - Request request = new Request.Builder() - .url(apiHost) - .post(RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), requestBody)) - .build(); - //创建事件 - EventSource eventSource = factory.newEventSource(request, eventSourceListener); - log.info("finish invoking tongyi chat ai"); - } catch (Exception e) { - log.error("tongyi chat ai error", e); - eventSourceListener.onFailure(null, e, null); - throw new ParamBusinessException(); - } - } - -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/tongyi/listener/TongyiChatAIEventSourceListener.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/tongyi/listener/TongyiChatAIEventSourceListener.java deleted file mode 100644 index 361c427ea..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/tongyi/listener/TongyiChatAIEventSourceListener.java +++ /dev/null @@ -1,131 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.tongyi.listener; - -import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatChoice; -import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatCompletions; -import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatCompletionsUsage; -import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatMessage; -import ai.chat2db.server.web.api.controller.ai.tongyi.model.TongyiChatCompletions; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.unfbx.chatgpt.entity.chat.Message; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import okhttp3.Response; -import okhttp3.ResponseBody; -import okhttp3.sse.EventSource; -import okhttp3.sse.EventSourceListener; -import org.apache.commons.lang3.StringUtils; -import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; - -import java.io.IOException; -import java.util.Objects; - -/** - * 描述:OpenAIEventSourceListener - * - * @author https:www.unfbx.com - * @date 2023-02-22 - */ -@Slf4j -public class TongyiChatAIEventSourceListener extends EventSourceListener { - - private SseEmitter sseEmitter; - - private ObjectMapper mapper = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); - - public TongyiChatAIEventSourceListener(SseEmitter sseEmitter) { - this.sseEmitter = sseEmitter; - } - - /** - * {@inheritDoc} - */ - @Override - public void onOpen(EventSource eventSource, Response response) { - log.info("Tongyi Chat Sse connecting..."); - } - - /** - * {@inheritDoc} - */ - @SneakyThrows - @Override - public void onEvent(EventSource eventSource, String id, String type, String data) { - log.info("Tongyi Chat AI response data:{}", data); - if (data.equals("[DONE]")) { - log.info("Tongyi Chat AI closed"); - sseEmitter.send(SseEmitter.event() - .id("[DONE]") - .data("[DONE]") - .reconnectTime(3000)); - sseEmitter.complete(); - return; - } - - TongyiChatCompletions chatCompletions = mapper.readValue(data, TongyiChatCompletions.class); - String text = chatCompletions.getOutput().getText(); - log.info("id: {}, text: {}", chatCompletions.getId(), text); - - Message message = new Message(); - message.setContent(text); - sseEmitter.send(SseEmitter.event() - .id(null) - .data(message) - .reconnectTime(3000)); - } - - @Override - public void onClosed(EventSource eventSource) { - try { - sseEmitter.send(SseEmitter.event() - .id("[DONE]") - .data("[DONE]")); - } catch (IOException e) { - throw new RuntimeException(e); - } - sseEmitter.complete(); - log.info("TongyiChatAI close sse connection..."); - } - - @Override - public void onFailure(EventSource eventSource, Throwable t, Response response) { - try { - if (Objects.isNull(response)) { - String message = t.getMessage(); - Message sseMessage = new Message(); - sseMessage.setContent(message); - sseEmitter.send(SseEmitter.event() - .id("[ERROR]") - .data(sseMessage)); - sseEmitter.send(SseEmitter.event() - .id("[DONE]") - .data("[DONE]")); - sseEmitter.complete(); - return; - } - ResponseBody body = response.body(); - String bodyString = Objects.nonNull(t) ? t.getMessage() : ""; - if (Objects.nonNull(body)) { - bodyString = body.string(); - if (StringUtils.isBlank(bodyString) && Objects.nonNull(t)) { - bodyString = t.getMessage(); - } - log.error("Tongyi Chat AI sse response:{}", bodyString); - } else { - log.error("Tongyi Chat AI sse response:{},error:{}", response, t); - } - eventSource.cancel(); - Message message = new Message(); - message.setContent("Tongyi Chat AI error:" + bodyString); - sseEmitter.send(SseEmitter.event() - .id("[ERROR]") - .data(message)); - sseEmitter.send(SseEmitter.event() - .id("[DONE]") - .data("[DONE]")); - sseEmitter.complete(); - } catch (Exception exception) { - log.error("Tongyi Chat AI send data error:", exception); - } - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/tongyi/model/TongyiChatCompletions.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/tongyi/model/TongyiChatCompletions.java deleted file mode 100644 index 84e50ae71..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/tongyi/model/TongyiChatCompletions.java +++ /dev/null @@ -1,46 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.tongyi.model; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; - - -@Data -public class TongyiChatCompletions { - - /* - * A unique identifier associated with this chat completions response. - */ - @JsonProperty(value = "request_id") - private String id; - - /* - * The collection of completions choices associated with this completions response. - * Generally, `n` choices are generated per provided prompt with a default value of 1. - * Token limits and other settings may limit the number of choices generated. - */ - private TongyiChatOutput output; - - /* - * Usage information for tokens processed and generated as part of this completions operation. - */ - private TongyiChatCompletionsUsage usage; - - /** - * Creates an instance of ChatCompletions class. - * - * @param id the id value to set. - * @param choices the choices value to set. - * @param usage the usage value to set. - */ - @JsonCreator - private TongyiChatCompletions( - @JsonProperty(value = "id") String id, - @JsonProperty(value = "output") TongyiChatOutput choices, - @JsonProperty(value = "usage") TongyiChatCompletionsUsage usage) { - this.id = id; - this.output = choices; - this.usage = usage; - } - -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/tongyi/model/TongyiChatCompletionsOptions.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/tongyi/model/TongyiChatCompletionsOptions.java deleted file mode 100644 index ee08a65c4..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/tongyi/model/TongyiChatCompletionsOptions.java +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -// Code generated by Microsoft (R) AutoRest Code Generator. -package ai.chat2db.server.web.api.controller.ai.tongyi.model; - -import lombok.Data; - -import java.util.Map; - -/** - * The configuration information for a chat completions request. Completions support a wide variety of tasks and - * generate text that continues from or "completes" provided prompt data. - */ -@Data -public final class TongyiChatCompletionsOptions { - - /* - * The collection of context messages associated with this chat completions request. - * Typical usage begins with a chat message for the System role that provides instructions for - * the behavior of the assistant, followed by alternating messages between the User and - * Assistant roles. - */ - private TongyiChatMessage input; - - /* - * A value indicating whether chat completions should be streamed for this request. - */ - private Boolean stream; - - /* - * The model name to provide as part of this completions request. - * Not applicable to Fast Chat AI, where deployment information should be included in the Fast Chat - * resource URI that's connected to. - */ - private String model; - - /** - * parameters - */ - private Map parameters; -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/tongyi/model/TongyiChatCompletionsUsage.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/tongyi/model/TongyiChatCompletionsUsage.java deleted file mode 100644 index b167f8e69..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/tongyi/model/TongyiChatCompletionsUsage.java +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -// Code generated by Microsoft (R) AutoRest Code Generator. -package ai.chat2db.server.web.api.controller.ai.tongyi.model; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; -import lombok.NoArgsConstructor; - -/** - * Representation of the token counts processed for a completions request. Counts consider all tokens across prompts, - * choices, choice alternates, best_of generations, and other consumers. - */ -@Data -@NoArgsConstructor -public final class TongyiChatCompletionsUsage { - - /* - * The number of tokens generated across all completions emissions. - */ - @JsonProperty(value = "output_tokens") - private int outputTokens; - - /* - * The number of tokens in the provided prompts for the completions request. - */ - @JsonProperty(value = "input_tokens") - private int inputTokens; - - - /** - * Creates an instance of CompletionsUsage class. - * - * @param completionTokens the completionTokens value to set. - * @param promptTokens the promptTokens value to set. - */ - @JsonCreator - private TongyiChatCompletionsUsage( - @JsonProperty(value = "output_tokens") int completionTokens, - @JsonProperty(value = "input_tokens") int promptTokens) { - this.outputTokens = completionTokens; - this.inputTokens = promptTokens; - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/tongyi/model/TongyiChatMessage.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/tongyi/model/TongyiChatMessage.java deleted file mode 100644 index 1eb48b74d..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/tongyi/model/TongyiChatMessage.java +++ /dev/null @@ -1,13 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.tongyi.model; - - -import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatMessage; -import lombok.Data; - -import java.util.List; - -@Data -public class TongyiChatMessage { - - private List messages; -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/tongyi/model/TongyiChatOutput.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/tongyi/model/TongyiChatOutput.java deleted file mode 100644 index fabeefe52..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/tongyi/model/TongyiChatOutput.java +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -// Code generated by Microsoft (R) AutoRest Code Generator. -package ai.chat2db.server.web.api.controller.ai.tongyi.model; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; - -/** - * The representation of a single prompt completion as part of an overall completions request. Generally, `n` choices - * are generated per provided prompt with a default value of 1. Token limits and other settings may limit the number of - * choices generated. - */ -@Data -public final class TongyiChatOutput { - - /* - * The generated text for a given completions prompt. - */ - @JsonProperty(value = "text") - private String text; - - /* - * Reason for finishing - */ - @JsonProperty(value = "finish_reason") - private String finishReason; - - /** - * Creates an instance of Choice class. - * - * @param text the text value to set. - * @param finishReason the finishReason value to set. - */ - @JsonCreator - private TongyiChatOutput( - @JsonProperty(value = "text") String text, - @JsonProperty(value = "finish_reason") String finishReason) { - this.text = text; - this.finishReason = finishReason; - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/wenxin/client/WenxinAIClient.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/wenxin/client/WenxinAIClient.java deleted file mode 100644 index 9dbb910cd..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/wenxin/client/WenxinAIClient.java +++ /dev/null @@ -1,78 +0,0 @@ - -package ai.chat2db.server.web.api.controller.ai.wenxin.client; - -import ai.chat2db.server.domain.api.model.Config; -import ai.chat2db.server.domain.api.service.ConfigService; -import ai.chat2db.server.web.api.controller.ai.fastchat.client.FastChatAIStreamClient; -import ai.chat2db.server.web.api.util.ApplicationContextUtil; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; - -/** - * @author moji - * @date 23/09/26 - */ -@Slf4j -public class WenxinAIClient { - - /** - * WENXIN_ACCESS_TOKEN - */ - public static final String WENXIN_ACCESS_TOKEN = "wenxin.access.token"; - - /** - * WENXIN_HOST - */ - public static final String WENXIN_HOST = "wenxin.host"; - - /** - * WENXIN_MODEL - */ - public static final String WENXIN_MODEL= "wenxin.model"; - - /** - * Wenxin embedding model - */ - public static final String WENXIN_EMBEDDING_MODEL = "wenxin.embedding.model"; - - private static WenxinAIStreamClient WENXIN_AI_CLIENT; - - - public static WenxinAIStreamClient getInstance() { - if (WENXIN_AI_CLIENT != null) { - return WENXIN_AI_CLIENT; - } else { - return singleton(); - } - } - - private static WenxinAIStreamClient singleton() { - if (WENXIN_AI_CLIENT == null) { - synchronized (WenxinAIClient.class) { - if (WENXIN_AI_CLIENT == null) { - refresh(); - } - } - } - return WENXIN_AI_CLIENT; - } - - public static void refresh() { - String apiHost = "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions_pro"; - String accessToken = ""; - ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); - Config apiHostConfig = configService.find(WENXIN_HOST).getData(); - if (apiHostConfig != null && StringUtils.isNotBlank(apiHostConfig.getContent())) { - apiHost = apiHostConfig.getContent(); - if (apiHost.endsWith("/")) { - apiHost = apiHost.substring(0, apiHost.length() - 1); - } - } - Config config = configService.find(WENXIN_ACCESS_TOKEN).getData(); - if (config != null && StringUtils.isNotBlank(config.getContent())) { - accessToken = config.getContent(); - } - WENXIN_AI_CLIENT = WenxinAIStreamClient.builder().accessToken(accessToken).apiHost(apiHost).build(); - } - -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/wenxin/client/WenxinAIStreamClient.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/wenxin/client/WenxinAIStreamClient.java deleted file mode 100644 index b89744d60..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/wenxin/client/WenxinAIStreamClient.java +++ /dev/null @@ -1,200 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.wenxin.client; - -import ai.chat2db.server.tools.common.exception.ParamBusinessException; -import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatCompletionsOptions; -import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatMessage; -import ai.chat2db.server.web.api.controller.ai.wenxin.interceptor.AccessTokenInterceptor; -import cn.hutool.http.ContentType; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import okhttp3.*; -import okhttp3.sse.EventSource; -import okhttp3.sse.EventSourceListener; -import okhttp3.sse.EventSources; -import org.apache.commons.collections4.CollectionUtils; -import org.jetbrains.annotations.NotNull; - -import java.util.List; -import java.util.Objects; -import java.util.concurrent.TimeUnit; - -/** - * Fast Chat Aligned Client - * - * @author moji - */ -@Slf4j -public class WenxinAIStreamClient { - - /** - * apikey - */ - @Getter - @NotNull - private String accessToken; - - /** - * apiHost - */ - @Getter - @NotNull - private String apiHost; - - /** - * model - */ - @Getter - private String model; - - /** - * embeddingModel - */ - @Getter - private String embeddingModel; - - /** - * okHttpClient - */ - @Getter - private OkHttpClient okHttpClient; - - - /** - * @param builder - */ - private WenxinAIStreamClient(Builder builder) { - this.accessToken = builder.accessToken; - this.apiHost = builder.apiHost; - this.model = builder.model; - this.embeddingModel = builder.embeddingModel; - if (Objects.isNull(builder.okHttpClient)) { - builder.okHttpClient = this.okHttpClient(); - } - okHttpClient = builder.okHttpClient; - } - - /** - * okhttpclient - */ - private OkHttpClient okHttpClient() { - OkHttpClient okHttpClient = new OkHttpClient - .Builder() - .addInterceptor(new AccessTokenInterceptor(this.accessToken)) - .connectTimeout(10, TimeUnit.SECONDS) - .writeTimeout(50, TimeUnit.SECONDS) - .readTimeout(50, TimeUnit.SECONDS) - .build(); - return okHttpClient; - } - - /** - * 构造 - * - * @return - */ - public static WenxinAIStreamClient.Builder builder() { - return new WenxinAIStreamClient.Builder(); - } - - /** - * builder - */ - public static final class Builder { - private String accessToken; - - private String apiHost; - - private String model; - - private String embeddingModel; - - /** - * OkhttpClient - */ - private OkHttpClient okHttpClient; - - public Builder() { - } - - public WenxinAIStreamClient.Builder accessToken(String accessToken) { - this.accessToken = accessToken; - return this; - } - - /** - * @param apiHostValue - * @return - */ - public WenxinAIStreamClient.Builder apiHost(String apiHostValue) { - this.apiHost = apiHostValue; - return this; - } - - /** - * @param modelValue - * @return - */ - public WenxinAIStreamClient.Builder model(String modelValue) { - this.model = modelValue; - return this; - } - - public WenxinAIStreamClient.Builder embeddingModel(String embeddingModelValue) { - this.embeddingModel = embeddingModelValue; - return this; - } - - public WenxinAIStreamClient.Builder okHttpClient(OkHttpClient val) { - this.okHttpClient = val; - return this; - } - - public WenxinAIStreamClient build() { - return new WenxinAIStreamClient(this); - } - - } - - /** - * 问答接口 stream 形式 - * - * @param chatMessages - * @param eventSourceListener - */ - public void streamCompletions(List chatMessages, EventSourceListener eventSourceListener) { - if (CollectionUtils.isEmpty(chatMessages)) { - log.error("param error:Wenxin Prompt cannot be empty"); - throw new ParamBusinessException("prompt"); - } - if (Objects.isNull(eventSourceListener)) { - log.error("param error:WenxinEventSourceListener cannot be empty"); - throw new ParamBusinessException(); - } - log.info("Wenxin Chat AI, prompt:{}", chatMessages.get(chatMessages.size() - 1).getContent()); - try { - - FastChatCompletionsOptions chatCompletionsOptions = new FastChatCompletionsOptions(chatMessages); - chatCompletionsOptions.setStream(true); - - EventSource.Factory factory = EventSources.createFactory(this.okHttpClient); - ObjectMapper mapper = new ObjectMapper(); - mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - String requestBody = mapper.writeValueAsString(chatCompletionsOptions); - Request request = new Request.Builder() - .url(apiHost) - .post(RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), requestBody)) - .build(); - //创建事件 - EventSource eventSource = factory.newEventSource(request, eventSourceListener); - log.info("finish invoking fast chat ai"); - } catch (Exception e) { - log.error("wenxin chat ai error", e); - eventSourceListener.onFailure(null, e, null); - throw new ParamBusinessException(); - } - } - - -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/wenxin/interceptor/AccessTokenInterceptor.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/wenxin/interceptor/AccessTokenInterceptor.java deleted file mode 100644 index 5cca185f2..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/wenxin/interceptor/AccessTokenInterceptor.java +++ /dev/null @@ -1,35 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.wenxin.interceptor; - -import okhttp3.HttpUrl; -import okhttp3.Interceptor; -import okhttp3.Request; -import okhttp3.Response; - -import java.io.IOException; - -public class AccessTokenInterceptor implements Interceptor { - private final String accessToken; - - public AccessTokenInterceptor(String accessToken) { - this.accessToken = accessToken; - } - - @Override - public Response intercept(Chain chain) throws IOException { - Request originalRequest = chain.request(); - HttpUrl originalHttpUrl = originalRequest.url(); - - // 使用 HttpUrl.Builder 来添加查询参数 access_token - HttpUrl urlWithAccessToken = originalHttpUrl.newBuilder() - .addQueryParameter("access_token", accessToken) - .build(); - - // 创建新的请求,将新的 URL 应用到它上面 - Request newRequest = originalRequest.newBuilder() - .url(urlWithAccessToken) - .build(); - - return chain.proceed(newRequest); - } -} - diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/wenxin/listener/WenxinAIEventSourceListener.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/wenxin/listener/WenxinAIEventSourceListener.java deleted file mode 100644 index cee3504b3..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/wenxin/listener/WenxinAIEventSourceListener.java +++ /dev/null @@ -1,128 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.wenxin.listener; - -import ai.chat2db.server.web.api.controller.ai.wenxin.model.WenxinChatCompletions; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.unfbx.chatgpt.entity.chat.Message; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import okhttp3.Response; -import okhttp3.ResponseBody; -import okhttp3.sse.EventSource; -import okhttp3.sse.EventSourceListener; -import org.apache.commons.lang3.StringUtils; -import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; - -import java.io.IOException; -import java.util.Objects; - -/** - * 描述:OpenAIEventSourceListener - * - * @author https:www.unfbx.com - * @date 2023-02-22 - */ -@Slf4j -public class WenxinAIEventSourceListener extends EventSourceListener { - - private SseEmitter sseEmitter; - - private ObjectMapper mapper = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); - - public WenxinAIEventSourceListener(SseEmitter sseEmitter) { - this.sseEmitter = sseEmitter; - } - - /** - * {@inheritDoc} - */ - @Override - public void onOpen(EventSource eventSource, Response response) { - log.info("Wenxin chat Sse connecting..."); - } - - /** - * {@inheritDoc} - */ - @SneakyThrows - @Override - public void onEvent(EventSource eventSource, String id, String type, String data) { - log.info("Wenxin AI response data:{}", data); - if (data.equals("[DONE]")) { - log.info("Wenxin AI closed"); - sseEmitter.send(SseEmitter.event() - .id("[DONE]") - .data("[DONE]") - .reconnectTime(3000)); - sseEmitter.complete(); - return; - } - - WenxinChatCompletions chatCompletions = mapper.readValue(data, WenxinChatCompletions.class); - String text = chatCompletions.getResult(); - log.info("Model={} is created at {}. message:{}", chatCompletions.getObject(), - chatCompletions.getCreated(), text); - - Message message = new Message(); - message.setContent(text); - sseEmitter.send(SseEmitter.event() - .id(null) - .data(message) - .reconnectTime(3000)); - } - - @Override - public void onClosed(EventSource eventSource) { - try { - sseEmitter.send(SseEmitter.event() - .id("[DONE]") - .data("[DONE]")); - } catch (IOException e) { - throw new RuntimeException(e); - } - sseEmitter.complete(); - log.info("WenxinChatAI close sse connection..."); - } - - @Override - public void onFailure(EventSource eventSource, Throwable t, Response response) { - try { - if (Objects.isNull(response)) { - String message = t.getMessage(); - Message sseMessage = new Message(); - sseMessage.setContent(message); - sseEmitter.send(SseEmitter.event() - .id("[ERROR]") - .data(sseMessage)); - sseEmitter.send(SseEmitter.event() - .id("[DONE]") - .data("[DONE]")); - sseEmitter.complete(); - return; - } - ResponseBody body = response.body(); - String bodyString = Objects.nonNull(t) ? t.getMessage() : ""; - if (Objects.nonNull(body)) { - bodyString = body.string(); - if (StringUtils.isBlank(bodyString) && Objects.nonNull(t)) { - bodyString = t.getMessage(); - } - log.error("Wenxin chat AI sse response:{}", bodyString); - } else { - log.error("Wenxin chat AI sse response:{},error:{}", response, t); - } - eventSource.cancel(); - Message message = new Message(); - message.setContent("Wenxin chat AI error:" + bodyString); - sseEmitter.send(SseEmitter.event() - .id("[ERROR]") - .data(message)); - sseEmitter.send(SseEmitter.event() - .id("[DONE]") - .data("[DONE]")); - sseEmitter.complete(); - } catch (Exception exception) { - log.error("Wenxin chat AI send data error:", exception); - } - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/wenxin/model/WenxinChatCompletions.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/wenxin/model/WenxinChatCompletions.java deleted file mode 100644 index 82d81bc1a..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/wenxin/model/WenxinChatCompletions.java +++ /dev/null @@ -1,74 +0,0 @@ - -package ai.chat2db.server.web.api.controller.ai.wenxin.model; - -import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatCompletionsUsage; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; - -@Data -public class WenxinChatCompletions { - - /* - * A unique identifier associated with this chat completions response. - */ - private String id; - - /* - * The first timestamp associated with generation activity for this completions response, - * represented as seconds since the beginning of the Unix epoch of 00:00 on 1 Jan 1970. - */ - private int created; - - /** - * model - */ - @JsonProperty(value = "is_truncated") - private String isTruncated; - - @JsonProperty(value = "need_clear_history") - private String needClearHistory; - - /** - * object - */ - private String object; - - /* - * The collection of completions choices associated with this completions response. - * Generally, `n` choices are generated per provided prompt with a default value of 1. - * Token limits and other settings may limit the number of choices generated. - */ - private String result; - - /* - * Usage information for tokens processed and generated as part of this completions operation. - */ - private FastChatCompletionsUsage usage; - - /** - * Creates an instance of ChatCompletions class. - * - * @param id the id value to set. - * @param created the created value to set. - * @param result the result value to set. - * @param usage the usage value to set. - */ - @JsonCreator - private WenxinChatCompletions( - @JsonProperty(value = "id") String id, - @JsonProperty(value = "created") int created, - @JsonProperty(value = "is_truncated") String isTruncated, - @JsonProperty(value = "need_clear_history") String needClearHistory, - @JsonProperty(value = "object") String object, - @JsonProperty(value = "result") String result, - @JsonProperty(value = "usage") FastChatCompletionsUsage usage) { - this.id = id; - this.created = created; - this.isTruncated = isTruncated; - this.needClearHistory = needClearHistory; - this.object = object; - this.result = result; - this.usage = usage; - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/zhipu/client/ZhipuChatAIClient.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/zhipu/client/ZhipuChatAIClient.java deleted file mode 100644 index f205f17f5..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/zhipu/client/ZhipuChatAIClient.java +++ /dev/null @@ -1,80 +0,0 @@ - -package ai.chat2db.server.web.api.controller.ai.zhipu.client; - -import ai.chat2db.server.domain.api.model.Config; -import ai.chat2db.server.domain.api.service.ConfigService; -import ai.chat2db.server.web.api.util.ApplicationContextUtil; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; - -/** - * @author moji - * @date 23/09/26 - */ -@Slf4j -public class ZhipuChatAIClient { - - /** - * ZHIPU OPENAI KEY - */ - public static final String ZHIPU_API_KEY = "zhipu.chatgpt.apiKey"; - - /** - * ZHIPU OPENAI HOST - */ - public static final String ZHIPU_HOST = "zhipu.host"; - - /** - * ZHIPU OPENAI model - */ - public static final String ZHIPU_MODEL= "zhipu.model"; - - /** - * ZHIPU OPENAI embedding model - */ - public static final String ZHIPU_EMBEDDING_MODEL = "zhipu.embedding.model"; - - private static ZhipuChatAIStreamClient ZHIPU_AI_CLIENT; - - - public static ZhipuChatAIStreamClient getInstance() { - if (ZHIPU_AI_CLIENT != null) { - return ZHIPU_AI_CLIENT; - } else { - return singleton(); - } - } - - private static ZhipuChatAIStreamClient singleton() { - if (ZHIPU_AI_CLIENT == null) { - synchronized (ZhipuChatAIClient.class) { - if (ZHIPU_AI_CLIENT == null) { - refresh(); - } - } - } - return ZHIPU_AI_CLIENT; - } - - public static void refresh() { - String apiKey = ""; - String apiHost = "https://open.bigmodel.cn/api/paas/v3/model-api/"; - String model = "chatglm_turbo"; - ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); - Config apiHostConfig = configService.find(ZHIPU_HOST).getData(); - if (apiHostConfig != null && StringUtils.isNotBlank(apiHostConfig.getContent())) { - apiHost = apiHostConfig.getContent(); - } - Config config = configService.find(ZHIPU_API_KEY).getData(); - if (config != null && StringUtils.isNotBlank(config.getContent())) { - apiKey = config.getContent(); - } - Config deployConfig = configService.find(ZHIPU_MODEL).getData(); - if (deployConfig != null && StringUtils.isNotBlank(deployConfig.getContent())) { - model = deployConfig.getContent(); - } - ZHIPU_AI_CLIENT = ZhipuChatAIStreamClient.builder().apiKey(apiKey).apiHost(apiHost).model(model) - .build(); - } - -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/zhipu/client/ZhipuChatAIStreamClient.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/zhipu/client/ZhipuChatAIStreamClient.java deleted file mode 100644 index 550c929eb..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/zhipu/client/ZhipuChatAIStreamClient.java +++ /dev/null @@ -1,224 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.zhipu.client; - -import ai.chat2db.server.tools.common.exception.ParamBusinessException; -import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatMessage; -import ai.chat2db.server.web.api.controller.ai.zhipu.interceptor.ZhipuChatHeaderAuthorizationInterceptor; -import ai.chat2db.server.web.api.controller.ai.zhipu.model.ZhipuChatCompletionsOptions; -import cn.hutool.http.ContentType; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import okhttp3.MediaType; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.sse.EventSource; -import okhttp3.sse.EventSourceListener; -import okhttp3.sse.EventSources; -import org.apache.commons.collections4.CollectionUtils; -import org.jetbrains.annotations.NotNull; - -import java.util.List; -import java.util.Objects; -import java.util.concurrent.TimeUnit; - -/** - * Zhipu Chat Aligned Client - * - * @author moji - */ -@Slf4j -public class ZhipuChatAIStreamClient { - - /** - * apikey - */ - @Getter - @NotNull - private String apiKey; - - @Getter - private String key; - - @Getter - private String secret; - - /** - * apiHost - */ - @Getter - @NotNull - private String apiHost; - - /** - * model - */ - @Getter - private String model; - - /** - * embeddingModel - */ - @Getter - private String embeddingModel; - - /** - * okHttpClient - */ - @Getter - private OkHttpClient okHttpClient; - - - /** - * @param builder - */ - private ZhipuChatAIStreamClient(Builder builder) { - this.apiKey = builder.apiKey; - this.key = builder.key; - this.secret = builder.secret; - this.apiHost = builder.apiHost; - this.model = builder.model; - this.embeddingModel = builder.embeddingModel; - if (Objects.isNull(builder.okHttpClient)) { - builder.okHttpClient = this.okHttpClient(); - } - okHttpClient = builder.okHttpClient; - } - - /** - * okhttpclient - */ - private OkHttpClient okHttpClient() { - OkHttpClient okHttpClient = new OkHttpClient - .Builder() - .addInterceptor(new ZhipuChatHeaderAuthorizationInterceptor(this.key, this.secret)) - .connectTimeout(10, TimeUnit.SECONDS) - .writeTimeout(50, TimeUnit.SECONDS) - .readTimeout(50, TimeUnit.SECONDS) - .build(); - return okHttpClient; - } - - /** - * 构造 - * - * @return - */ - public static ZhipuChatAIStreamClient.Builder builder() { - return new ZhipuChatAIStreamClient.Builder(); - } - - /** - * builder - */ - public static final class Builder { - private String apiKey; - - private String key; - - private String secret; - - private String apiHost; - - private String model; - - private String embeddingModel; - - /** - * OkhttpClient - */ - private OkHttpClient okHttpClient; - - public Builder() { - } - - public ZhipuChatAIStreamClient.Builder apiKey(String apiKeyValue) { - this.apiKey = apiKeyValue; - String[] arrStr = apiKey.split("\\."); - if (arrStr.length != 2) { - throw new RuntimeException("invalid apiSecretKey"); - } - this.key = arrStr[0]; - this.secret = arrStr[1]; - return this; - } - - /** - * @param apiHostValue - * @return - */ - public ZhipuChatAIStreamClient.Builder apiHost(String apiHostValue) { - this.apiHost = apiHostValue; - return this; - } - - /** - * @param modelValue - * @return - */ - public ZhipuChatAIStreamClient.Builder model(String modelValue) { - this.model = modelValue; - return this; - } - - public ZhipuChatAIStreamClient.Builder embeddingModel(String embeddingModelValue) { - this.embeddingModel = embeddingModelValue; - return this; - } - - public ZhipuChatAIStreamClient.Builder okHttpClient(OkHttpClient val) { - this.okHttpClient = val; - return this; - } - - public ZhipuChatAIStreamClient build() { - return new ZhipuChatAIStreamClient(this); - } - - } - - /** - * 问答接口 stream 形式 - * - * @param chatMessages - * @param eventSourceListener - */ - public void streamCompletions(List chatMessages, EventSourceListener eventSourceListener) { - if (CollectionUtils.isEmpty(chatMessages)) { - log.error("param error:Zhipu Chat Prompt cannot be empty"); - throw new ParamBusinessException("prompt"); - } - if (Objects.isNull(eventSourceListener)) { - log.error("param error:Zhipu ChatEventSourceListener cannot be empty"); - throw new ParamBusinessException(); - } - log.info("Zhipu Chat AI, prompt:{}", chatMessages.get(chatMessages.size() - 1).getContent()); - try { - // 建议直接查看demo包代码,这里更新可能不及时 - ZhipuChatCompletionsOptions completionsOptions = new ZhipuChatCompletionsOptions(); - completionsOptions.setPrompt(chatMessages); - completionsOptions.setModel(this.model); - String requestId = String.valueOf(System.currentTimeMillis()); - completionsOptions.setRequestId(requestId); - ObjectMapper mapper = new ObjectMapper(); - mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - String requestBody = mapper.writeValueAsString(completionsOptions); - - String url = this.apiHost + "/" + this.model + "/" + "sse-invoke"; - EventSource.Factory factory = EventSources.createFactory(this.okHttpClient); - Request request = new Request.Builder() - .url(url) - .post(RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), requestBody)) - .build(); - //创建事件 - EventSource eventSource = factory.newEventSource(request, eventSourceListener); - log.info("finish invoking zhipu chat ai"); - } catch (Exception e) { - log.error("fast chat ai error", e); - eventSourceListener.onFailure(null, e, null); - throw new ParamBusinessException(); - } - } - -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/zhipu/interceptor/ZhipuChatHeaderAuthorizationInterceptor.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/zhipu/interceptor/ZhipuChatHeaderAuthorizationInterceptor.java deleted file mode 100644 index 4854a196f..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/zhipu/interceptor/ZhipuChatHeaderAuthorizationInterceptor.java +++ /dev/null @@ -1,43 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.zhipu.interceptor; - -import ai.chat2db.server.web.api.controller.ai.zhipu.util.ZhipuUtils; -import cn.hutool.http.ContentType; -import cn.hutool.http.Header; -import lombok.Getter; -import okhttp3.Interceptor; -import okhttp3.Request; -import okhttp3.Response; - -import java.io.IOException; - -/** - * header apikey - * - * @author grt - * @since 2023-03-23 - */ -@Getter -public class ZhipuChatHeaderAuthorizationInterceptor implements Interceptor { - - private String key; - - private String secret; - - public ZhipuChatHeaderAuthorizationInterceptor(String key, String secret) { - this.key = key; - this.secret = secret; - } - - @Override - public Response intercept(Chain chain) throws IOException { - Request original = chain.request(); - String token = ZhipuUtils.getToken(key, secret); - Request request = original.newBuilder() - // replace to your corresponding field and value - .header(Header.AUTHORIZATION.getValue(), token) - .header(Header.CONTENT_TYPE.getValue(), ContentType.JSON.getValue()) - .method(original.method(), original.body()) - .build(); - return chain.proceed(request); - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/zhipu/listener/ZhipuChatAIEventSourceListener.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/zhipu/listener/ZhipuChatAIEventSourceListener.java deleted file mode 100644 index a8b1ae016..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/zhipu/listener/ZhipuChatAIEventSourceListener.java +++ /dev/null @@ -1,134 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.zhipu.listener; - -import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatMessage; -import ai.chat2db.server.web.api.controller.ai.zhipu.model.ZhipuChatCompletions; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.unfbx.chatgpt.entity.chat.Message; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import okhttp3.Response; -import okhttp3.ResponseBody; -import okhttp3.sse.EventSource; -import okhttp3.sse.EventSourceListener; -import org.apache.commons.lang3.StringUtils; -import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; - -import java.io.IOException; -import java.util.Objects; - -/** - * 描述:OpenAIEventSourceListener - * - * @author https:www.unfbx.com - * @date 2023-02-22 - */ -@Slf4j -public class ZhipuChatAIEventSourceListener extends EventSourceListener { - - private SseEmitter sseEmitter; - - private ObjectMapper mapper = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); - - public ZhipuChatAIEventSourceListener(SseEmitter sseEmitter) { - this.sseEmitter = sseEmitter; - } - - /** - * {@inheritDoc} - */ - @Override - public void onOpen(EventSource eventSource, Response response) { - log.info("Zhipu Chat Sse connecting..."); - } - - /** - * {@inheritDoc} - */ - @SneakyThrows - @Override - public void onEvent(EventSource eventSource, String id, String type, String data) { - log.info("Zhipu Chat AI response data:{}", data); - if (data.equals("[DONE]")) { - log.info("Zhipu Chat AI closed"); - sseEmitter.send(SseEmitter.event() - .id("[DONE]") - .data("[DONE]") - .reconnectTime(3000)); - sseEmitter.complete(); - return; - } - - ZhipuChatCompletions chatCompletions = mapper.readValue(data, ZhipuChatCompletions.class); - String text = chatCompletions.getData(); - if (Objects.isNull(text)) { - for (FastChatMessage message : chatCompletions.getBody().getChoices()) { - if (message != null && message.getContent() != null) { - text = message.getContent(); - } - } - } - - Message message = new Message(); - message.setContent(text); - sseEmitter.send(SseEmitter.event() - .id(null) - .data(message) - .reconnectTime(3000)); - } - - @Override - public void onClosed(EventSource eventSource) { - try { - sseEmitter.send(SseEmitter.event() - .id("[DONE]") - .data("[DONE]")); - } catch (IOException e) { - throw new RuntimeException(e); - } - sseEmitter.complete(); - log.info("ZhipuChatAI close sse connection..."); - } - - @Override - public void onFailure(EventSource eventSource, Throwable t, Response response) { - try { - if (Objects.isNull(response)) { - String message = t.getMessage(); - Message sseMessage = new Message(); - sseMessage.setContent(message); - sseEmitter.send(SseEmitter.event() - .id("[ERROR]") - .data(sseMessage)); - sseEmitter.send(SseEmitter.event() - .id("[DONE]") - .data("[DONE]")); - sseEmitter.complete(); - return; - } - ResponseBody body = response.body(); - String bodyString = Objects.nonNull(t) ? t.getMessage() : ""; - if (Objects.nonNull(body)) { - bodyString = body.string(); - if (StringUtils.isBlank(bodyString) && Objects.nonNull(t)) { - bodyString = t.getMessage(); - } - log.error("Zhipu Chat AI sse response:{}", bodyString); - } else { - log.error("Zhipu Chat AI sse response:{},error:{}", response, t); - } - eventSource.cancel(); - Message message = new Message(); - message.setContent("Zhipu Chat AI error:" + bodyString); - sseEmitter.send(SseEmitter.event() - .id("[ERROR]") - .data(message)); - sseEmitter.send(SseEmitter.event() - .id("[DONE]") - .data("[DONE]")); - sseEmitter.complete(); - } catch (Exception exception) { - log.error("Zhipu Chat AI send data error:", exception); - } - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/zhipu/model/ZhipuChatBody.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/zhipu/model/ZhipuChatBody.java deleted file mode 100644 index e84c81ed0..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/zhipu/model/ZhipuChatBody.java +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -// Code generated by Microsoft (R) AutoRest Code Generator. -package ai.chat2db.server.web.api.controller.ai.zhipu.model; - -import ai.chat2db.server.web.api.controller.ai.baichuan.model.BaichuanChatMessage; -import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatCompletionsUsage; -import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatMessage; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; - -import java.util.List; - -/** - * The representation of a single prompt completion as part of an overall completions request. Generally, `n` choices - * are generated per provided prompt with a default value of 1. Token limits and other settings may limit the number of - * choices generated. - */ -@Data -public final class ZhipuChatBody { - - /* - * The log probabilities model for tokens associated with this completions choice. - */ - @JsonProperty(value = "choices") - private List choices; - - @JsonProperty(value = "usage") - private FastChatCompletionsUsage usage; - -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/zhipu/model/ZhipuChatCompletions.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/zhipu/model/ZhipuChatCompletions.java deleted file mode 100644 index 323b63fb0..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/zhipu/model/ZhipuChatCompletions.java +++ /dev/null @@ -1,44 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.zhipu.model; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; - -@Data -public class ZhipuChatCompletions { - - /* - * A unique identifier associated with this chat completions response. - */ - private String msg; - - private int statusCode; - - private String data; - - /* - * The collection of completions choices associated with this completions response. - * Generally, `n` choices are generated per provided prompt with a default value of 1. - * Token limits and other settings may limit the number of choices generated. - */ - @JsonProperty(value = "body") - private ZhipuChatBody body; - - /** - * Creates an instance of ChatCompletions class. - * - * @param msg the id value to set. - * @param code the created value to set. - * @param body the body value to set. - */ - @JsonCreator - private ZhipuChatCompletions( - @JsonProperty(value = "msg") String msg, - @JsonProperty(value = "code") int code, - @JsonProperty(value = "body") ZhipuChatBody body) { - this.msg = msg; - this.statusCode = code; - this.body = body; - } - -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/zhipu/model/ZhipuChatCompletionsOptions.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/zhipu/model/ZhipuChatCompletionsOptions.java deleted file mode 100644 index 4b6359cc2..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/zhipu/model/ZhipuChatCompletionsOptions.java +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -// Code generated by Microsoft (R) AutoRest Code Generator. -package ai.chat2db.server.web.api.controller.ai.zhipu.model; - -import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatMessage; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; - -import java.util.List; - -/** - * The configuration information for a chat completions request. Completions support a wide variety of tasks and - * generate text that continues from or "completes" provided prompt data. - */ -@Data -public final class ZhipuChatCompletionsOptions { - - @JsonProperty(value = "request_id") - private String requestId; - - // sse-params - @JsonProperty(value = "incremental") - private Boolean stream = true; - - @JsonProperty(value = "sseFormat") - private String sseFormat = "data"; - - - /* - * The collection of context messages associated with this chat completions request. - * Typical usage begins with a chat message for the System role that provides instructions for - * the behavior of the assistant, followed by alternating messages between the User and - * Assistant roles. - */ - @JsonProperty(value = "prompt") - private List prompt; - - - // - /* - * The model name to provide as part of this completions request. - * Not applicable to Fast Chat AI, where deployment information should be included in the Fast Chat - * resource URI that's connected to. - */ - @JsonProperty(value = "model") - private String model; -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/zhipu/util/ZhipuUtils.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/zhipu/util/ZhipuUtils.java deleted file mode 100644 index f32235a08..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/zhipu/util/ZhipuUtils.java +++ /dev/null @@ -1,41 +0,0 @@ -package ai.chat2db.server.web.api.controller.ai.zhipu.util; - -import com.auth0.jwt.JWT; -import com.auth0.jwt.algorithms.Algorithm; -import lombok.extern.slf4j.Slf4j; - -import java.util.Calendar; -import java.util.HashMap; -import java.util.Map; - -@Slf4j -public class ZhipuUtils { - - private static final String tokenV3KeyPrefix = "zhipu_oapi_token_v3"; - - - public static String getToken(String key, String secret) { - String newToken = createJwt(key, secret); - return newToken; - } - - private static String createJwt(String key, String secret) { - Algorithm alg; - try { - alg = Algorithm.HMAC256(secret.getBytes("utf-8")); - } catch (Exception e) { - log.info("create jwt error", e); - return null; - } - - Map payload = new HashMap<>(); - payload.put("api_key", key); - payload.put("exp", System.currentTimeMillis() + 30 * 60 * 1000); - payload.put("timestamp", Calendar.getInstance().getTimeInMillis()); - Map headerClaims = new HashMap<>(); - headerClaims.put("alg", "HS256"); - headerClaims.put("sign_type", "SIGN"); - String token = JWT.create().withPayload(payload).withHeader(headerClaims).sign(alg); - return token; - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/ConfigController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/ConfigController.java index fd07c3ec3..3036e89a9 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/ConfigController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/ConfigController.java @@ -1,10 +1,11 @@ - package ai.chat2db.server.web.api.controller.config; -import java.util.Objects; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; -import ai.chat2db.server.domain.api.enums.AiSqlSourceEnum; -import ai.chat2db.server.domain.api.model.AIConfig; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.TypeReference; import ai.chat2db.server.domain.api.model.Config; import ai.chat2db.server.domain.api.param.SystemConfigParam; import ai.chat2db.server.domain.api.service.ConfigService; @@ -12,17 +13,15 @@ import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; -import ai.chat2db.server.web.api.controller.ai.azure.client.AzureOpenAIClient; -import ai.chat2db.server.web.api.controller.ai.baichuan.client.BaichuanAIClient; -import ai.chat2db.server.web.api.controller.ai.chat2db.client.Chat2dbAIClient; -import ai.chat2db.server.web.api.controller.ai.fastchat.client.FastChatAIClient; -import ai.chat2db.server.web.api.controller.ai.rest.client.RestAIClient; -import ai.chat2db.server.web.api.controller.ai.tongyi.client.TongyiChatAIClient; -import ai.chat2db.server.web.api.controller.ai.wenxin.client.WenxinAIClient; -import ai.chat2db.server.web.api.controller.ai.zhipu.client.ZhipuChatAIClient; -import ai.chat2db.server.web.api.controller.config.request.AIConfigCreateRequest; +import ai.chat2db.server.web.api.config.AiChatConfig; +import ai.chat2db.server.web.api.controller.config.request.DefaultModelConfigRequest; +import ai.chat2db.server.web.api.controller.config.request.ModelItemRequest; +import ai.chat2db.server.web.api.controller.config.request.ModelServiceDeleteRequest; +import ai.chat2db.server.web.api.controller.config.request.ModelServiceTestRequest; +import ai.chat2db.server.web.api.controller.config.request.ModelServiceUpsertRequest; import ai.chat2db.server.web.api.controller.config.request.SystemConfigRequest; -import ai.chat2db.server.web.api.controller.ai.openai.client.OpenAIClient; +import ai.chat2db.server.web.api.controller.config.response.DefaultModelConfigResponse; +import ai.chat2db.server.web.api.controller.config.response.ModelServiceResponse; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; @@ -32,355 +31,160 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -/** - * @author jipengfei - * @version : ConfigController.java - */ @ConnectionInfoAspect @RequestMapping("/api/config") @RestController public class ConfigController { + private static final String MODEL_SERVICE_CONFIG_CODE = "ai.model.services"; + private static final String MODEL_DEFAULT_CONFIG_CODE = "ai.model.default"; @Autowired private ConfigService configService; + @Autowired + private AiChatConfig aiChatConfig; @PostMapping("/system_config") public ActionResult systemConfig(@RequestBody SystemConfigRequest request) { - SystemConfigParam param = SystemConfigParam.builder().code(request.getCode()).content(request.getContent()) - .build(); + SystemConfigParam param = SystemConfigParam.builder() + .code(request.getCode()) + .content(request.getContent()) + .build(); configService.createOrUpdate(param); - if (OpenAIClient.OPENAI_KEY.equals(request.getCode())) { - OpenAIClient.refresh(); - } return ActionResult.isSuccess(); } - /** - * 保存ChatGPT相关配置 - * - * @param request - * @return - */ - @PostMapping("/system_config/ai") - public ActionResult addChatGptSystemConfig(@RequestBody AIConfigCreateRequest request) { - PermissionUtils.checkDeskTopOrAdmin(); - - String sqlSource = request.getAiSqlSource(); - AiSqlSourceEnum aiSqlSourceEnum = AiSqlSourceEnum.getByName(sqlSource); - if (Objects.isNull(aiSqlSourceEnum)) { - sqlSource = AiSqlSourceEnum.CHAT2DBAI.getCode(); - aiSqlSourceEnum = AiSqlSourceEnum.CHAT2DBAI; - } - SystemConfigParam param = SystemConfigParam.builder().code(RestAIClient.AI_SQL_SOURCE).content(sqlSource) - .build(); - configService.createOrUpdate(param); + @GetMapping("/system_config/{code}") + public DataResult getSystemConfig(@PathVariable("code") String code) { + Config result = configService.find(code); + return DataResult.of(result); + } - switch (Objects.requireNonNull(aiSqlSourceEnum)) { - case OPENAI: - saveOpenAIConfig(request); - break; - case CHAT2DBAI: - saveChat2dbAIConfig(request); - break; - case RESTAI: - saveRestAIConfig(request); - break; - case AZUREAI: - saveAzureAIConfig(request); - break; - case FASTCHATAI: - saveFastChatAIConfig(request); - break; - case TONGYIQIANWENAI: - saveTongyiChatAIConfig(request); - break; - case WENXINAI: - saveWenxinAIConfig(request); - break; - case BAICHUANAI: - saveBaichuanAIConfig(request); - break; - case ZHIPUAI: - saveZhipuChatAIConfig(request); - break; + private String getConfigValue(String code) { + Config result = configService.find(code); + if (result != null && StringUtils.isNotBlank(result.getContent())) { + return result.getContent(); } - return ActionResult.isSuccess(); + return ""; } - /** - * save chat2db ai config - * - * @param request - */ - private void saveChat2dbAIConfig(AIConfigCreateRequest request) { - SystemConfigParam param = SystemConfigParam.builder().code(Chat2dbAIClient.CHAT2DB_OPENAI_KEY).content( - request.getApiKey()).build(); - configService.createOrUpdate(param); - SystemConfigParam hostParam = SystemConfigParam.builder().code(Chat2dbAIClient.CHAT2DB_OPENAI_HOST).content( - request.getApiHost()).build(); - configService.createOrUpdate(hostParam); - SystemConfigParam modelParam = SystemConfigParam.builder().code(Chat2dbAIClient.CHAT2DB_OPENAI_MODEL).content( - request.getModel()).build(); - configService.createOrUpdate(modelParam); - Chat2dbAIClient.refresh(); + @GetMapping("/model_service/list") + public DataResult> getModelServiceList() { + return DataResult.of(readModelServices()); } - /** - * save open ai config - * - * @param request - */ - private void saveOpenAIConfig(AIConfigCreateRequest request) { - SystemConfigParam param = SystemConfigParam.builder().code(OpenAIClient.OPENAI_KEY).content( - request.getApiKey()).build(); - configService.createOrUpdate(param); - SystemConfigParam hostParam = SystemConfigParam.builder().code(OpenAIClient.OPENAI_HOST).content( - request.getApiHost()).build(); - configService.createOrUpdate(hostParam); - SystemConfigParam httpProxyHostParam = SystemConfigParam.builder().code(OpenAIClient.PROXY_HOST).content( - request.getHttpProxyHost()).build(); - configService.createOrUpdate(httpProxyHostParam); - SystemConfigParam httpProxyPortParam = SystemConfigParam.builder().code(OpenAIClient.PROXY_PORT).content( - request.getHttpProxyPort()).build(); - configService.createOrUpdate(httpProxyPortParam); - OpenAIClient.refresh(); - } + @PostMapping("/model_service/upsert") + public ActionResult upsertModelService(@RequestBody ModelServiceUpsertRequest request) { + PermissionUtils.checkDeskTopOrAdmin(); + if (StringUtils.isBlank(request.getProvider())) { + return ActionResult.fail("INVALID_PARAM", "provider is required", "provider is required"); + } - /** - * save rest ai config - * - * @param request - */ - private void saveRestAIConfig(AIConfigCreateRequest request) { - SystemConfigParam restParam = SystemConfigParam.builder().code(RestAIClient.REST_AI_URL).content( - request.getApiHost()).build(); - configService.createOrUpdate(restParam); - SystemConfigParam methodParam = SystemConfigParam.builder().code(RestAIClient.REST_AI_STREAM_OUT).content( - request.getStream().toString()).build(); - configService.createOrUpdate(methodParam); - RestAIClient.refresh(); - } + List services = readModelServices(); + ModelServiceResponse target = null; + if (StringUtils.isNotBlank(request.getId())) { + for (ModelServiceResponse service : services) { + if (StringUtils.equals(service.getId(), request.getId())) { + target = service; + break; + } + } + } - /** - * save azure config - * - * @param request - */ - private void saveAzureAIConfig(AIConfigCreateRequest request) { - SystemConfigParam apikeyParam = SystemConfigParam.builder().code(AzureOpenAIClient.AZURE_CHATGPT_API_KEY) - .content( - request.getApiKey()).build(); - configService.createOrUpdate(apikeyParam); - SystemConfigParam endpointParam = SystemConfigParam.builder().code(AzureOpenAIClient.AZURE_CHATGPT_ENDPOINT) - .content( - request.getApiHost()).build(); - configService.createOrUpdate(endpointParam); - SystemConfigParam modelParam = SystemConfigParam.builder().code(AzureOpenAIClient.AZURE_CHATGPT_DEPLOYMENT_ID) - .content( - request.getModel()).build(); - configService.createOrUpdate(modelParam); - AzureOpenAIClient.refresh(); - } + if (target == null) { + target = new ModelServiceResponse(); + target.setId(UUID.randomUUID().toString()); + services.add(target); + } - /** - * save common fast chat ai config - * - * @param request - */ - private void saveFastChatAIConfig(AIConfigCreateRequest request) { - SystemConfigParam apikeyParam = SystemConfigParam.builder().code(FastChatAIClient.FASTCHAT_API_KEY) - .content(request.getApiKey()).build(); - configService.createOrUpdate(apikeyParam); - SystemConfigParam apiHostParam = SystemConfigParam.builder().code(FastChatAIClient.FASTCHAT_HOST) - .content(request.getApiHost()).build(); - configService.createOrUpdate(apiHostParam); - SystemConfigParam modelParam = SystemConfigParam.builder().code(FastChatAIClient.FASTCHAT_MODEL) - .content(request.getModel()).build(); - configService.createOrUpdate(modelParam); - FastChatAIClient.refresh(); - } + target.setName(StringUtils.defaultIfBlank(request.getName(), request.getProvider() + " Service")); + target.setProvider(request.getProvider()); + target.setApiKey(StringUtils.defaultString(request.getApiKey())); + target.setApiHost(StringUtils.defaultString(request.getApiHost())); + target.setHttpProxyHost(StringUtils.defaultString(request.getHttpProxyHost())); + target.setHttpProxyPort(StringUtils.defaultString(request.getHttpProxyPort())); + target.setOrganizationId(StringUtils.defaultString(request.getOrganizationId())); + target.setProjectId(StringUtils.defaultString(request.getProjectId())); + List modelList = request.getModelList() == null ? new ArrayList<>() : request.getModelList(); + for (ModelItemRequest modelItem : modelList) { + if (StringUtils.isBlank(modelItem.getId())) { + modelItem.setId(UUID.randomUUID().toString()); + } + } + target.setModelList(modelList); - /** - * save common zhipu chat ai config - * - * @param request - */ - private void saveZhipuChatAIConfig(AIConfigCreateRequest request) { - SystemConfigParam apikeyParam = SystemConfigParam.builder().code(ZhipuChatAIClient.ZHIPU_API_KEY) - .content(request.getApiKey()).build(); - configService.createOrUpdate(apikeyParam); - SystemConfigParam apiHostParam = SystemConfigParam.builder().code(ZhipuChatAIClient.ZHIPU_HOST) - .content(request.getApiHost()).build(); - configService.createOrUpdate(apiHostParam); - SystemConfigParam modelParam = SystemConfigParam.builder().code(ZhipuChatAIClient.ZHIPU_MODEL) - .content(request.getModel()).build(); - configService.createOrUpdate(modelParam); - ZhipuChatAIClient.refresh(); + saveSystemConfig(MODEL_SERVICE_CONFIG_CODE, JSON.toJSONString(services)); + return ActionResult.isSuccess(); } - /** - * save common tongyi chat ai config - * - * @param request - */ - private void saveTongyiChatAIConfig(AIConfigCreateRequest request) { - SystemConfigParam apikeyParam = SystemConfigParam.builder().code(TongyiChatAIClient.TONGYI_API_KEY) - .content(request.getApiKey()).build(); - configService.createOrUpdate(apikeyParam); - SystemConfigParam apiHostParam = SystemConfigParam.builder().code(TongyiChatAIClient.TONGYI_HOST) - .content(request.getApiHost()).build(); - configService.createOrUpdate(apiHostParam); - SystemConfigParam modelParam = SystemConfigParam.builder().code(TongyiChatAIClient.TONGYI_MODEL) - .content(request.getModel()).build(); - configService.createOrUpdate(modelParam); - TongyiChatAIClient.refresh(); + @PostMapping("/model_service/delete") + public ActionResult deleteModelService(@RequestBody ModelServiceDeleteRequest request) { + PermissionUtils.checkDeskTopOrAdmin(); + if (StringUtils.isBlank(request.getId())) { + return ActionResult.fail("INVALID_PARAM", "id is required", "id is required"); + } + List services = readModelServices(); + services.removeIf(service -> StringUtils.equals(service.getId(), request.getId())); + saveSystemConfig(MODEL_SERVICE_CONFIG_CODE, JSON.toJSONString(services)); + return ActionResult.isSuccess(); } - /** - * save common wenxin chat ai config - * - * @param request - */ - private void saveWenxinAIConfig(AIConfigCreateRequest request) { - SystemConfigParam apikeyParam = SystemConfigParam.builder().code(WenxinAIClient.WENXIN_ACCESS_TOKEN) - .content(request.getApiKey()).build(); - configService.createOrUpdate(apikeyParam); - SystemConfigParam apiHostParam = SystemConfigParam.builder().code(WenxinAIClient.WENXIN_HOST) - .content(request.getApiHost()).build(); - configService.createOrUpdate(apiHostParam); - WenxinAIClient.refresh(); + @PostMapping("/model_service/test") + public ActionResult testModelService(@RequestBody ModelServiceTestRequest request) { + PermissionUtils.checkDeskTopOrAdmin(); + if (StringUtils.isBlank(request.getProvider()) || StringUtils.isBlank(request.getApiHost()) + || StringUtils.isBlank(request.getApiKey())) { + return ActionResult.fail("INVALID_PARAM", "provider/apiHost/apiKey is required", + "provider/apiHost/apiKey is required"); + } + if (request.getModelList() == null || request.getModelList().isEmpty() + || StringUtils.isBlank(request.getModelList().get(0).getModel())) { + return ActionResult.fail("INVALID_PARAM", "at least one model is required", "at least one model is required"); + } + String provider = request.getProvider().toUpperCase(); + String model = request.getModelList().get(0).getModel(); + try { + aiChatConfig.testModelService(provider, request.getApiHost(), request.getApiKey(), model); + return ActionResult.isSuccess(); + } catch (Exception e) { + return ActionResult.fail("MODEL_SERVICE_TEST_FAILED", e.getMessage(), e.getMessage()); + } } - /** - * save common fast chat ai config - * - * @param request - */ - private void saveBaichuanAIConfig(AIConfigCreateRequest request) { - SystemConfigParam apikeyParam = SystemConfigParam.builder().code(BaichuanAIClient.BAICHUAN_API_KEY) - .content(request.getApiKey()).build(); - configService.createOrUpdate(apikeyParam); - SystemConfigParam secretKeyParam = SystemConfigParam.builder().code(BaichuanAIClient.BAICHUAN_SECRET_KEY) - .content(request.getSecretKey()).build(); - configService.createOrUpdate(secretKeyParam); - SystemConfigParam apiHostParam = SystemConfigParam.builder().code(BaichuanAIClient.BAICHUAN_HOST) - .content(request.getApiHost()).build(); - configService.createOrUpdate(apiHostParam); - SystemConfigParam modelParam = SystemConfigParam.builder().code(BaichuanAIClient.BAICHUAN_MODEL) - .content(request.getModel()).build(); - configService.createOrUpdate(modelParam); - BaichuanAIClient.refresh(); + @GetMapping("/model/default") + public DataResult getDefaultModelConfig() { + String content = getConfigValue(MODEL_DEFAULT_CONFIG_CODE); + if (StringUtils.isBlank(content)) { + return DataResult.of(new DefaultModelConfigResponse()); + } + return DataResult.of(JSON.parseObject(content, DefaultModelConfigResponse.class)); } - @GetMapping("/system_config/{code}") - public DataResult getSystemConfig(@PathVariable("code") String code) { - DataResult result = configService.find(code); - return DataResult.of(result.getData()); + @PostMapping("/model/default") + public ActionResult setDefaultModelConfig(@RequestBody DefaultModelConfigRequest request) { + PermissionUtils.checkDeskTopOrAdmin(); + if (StringUtils.isBlank(request.getDefaultModelId())) { + return ActionResult.fail("INVALID_PARAM", "defaultModelId is required", "defaultModelId is required"); + } + DefaultModelConfigResponse response = new DefaultModelConfigResponse(); + response.setDefaultModelId(request.getDefaultModelId()); + response.setFastModelId(StringUtils.defaultString(request.getFastModelId())); + saveSystemConfig(MODEL_DEFAULT_CONFIG_CODE, JSON.toJSONString(response)); + return ActionResult.isSuccess(); } - /** - * ai config info - * - * @return - */ - @GetMapping("/system_config/ai") - public DataResult getChatAiSystemConfig(String aiSqlSource) { - DataResult dbSqlSource = configService.find(RestAIClient.AI_SQL_SOURCE); - if (StringUtils.isBlank(aiSqlSource)) { - if (Objects.nonNull(dbSqlSource.getData())) { - aiSqlSource = dbSqlSource.getData().getContent(); - } - } - AIConfig config = new AIConfig(); - AiSqlSourceEnum aiSqlSourceEnum = AiSqlSourceEnum.getByName(aiSqlSource); - if (Objects.isNull(aiSqlSourceEnum)) { - aiSqlSource = AiSqlSourceEnum.CHAT2DBAI.getCode(); - config.setAiSqlSource(aiSqlSource); - return DataResult.of(config); - } - config.setAiSqlSource(aiSqlSource); - switch (Objects.requireNonNull(aiSqlSourceEnum)) { - case OPENAI: - DataResult apiKey = configService.find(OpenAIClient.OPENAI_KEY); - DataResult apiHost = configService.find(OpenAIClient.OPENAI_HOST); - DataResult httpProxyHost = configService.find(OpenAIClient.PROXY_HOST); - DataResult httpProxyPort = configService.find(OpenAIClient.PROXY_PORT); - config.setApiKey(Objects.nonNull(apiKey.getData()) ? apiKey.getData().getContent() : ""); - config.setApiHost(Objects.nonNull(apiHost.getData()) ? apiHost.getData().getContent() : ""); - config.setHttpProxyHost( - Objects.nonNull(httpProxyHost.getData()) ? httpProxyHost.getData().getContent() : ""); - config.setHttpProxyPort( - Objects.nonNull(httpProxyPort.getData()) ? httpProxyPort.getData().getContent() : ""); - break; - case CHAT2DBAI: - DataResult chat2dbApiKey = configService.find(Chat2dbAIClient.CHAT2DB_OPENAI_KEY); - DataResult chat2dbApiHost = configService.find(Chat2dbAIClient.CHAT2DB_OPENAI_HOST); - DataResult chat2dbModel = configService.find(Chat2dbAIClient.CHAT2DB_OPENAI_MODEL); - config.setApiKey(Objects.nonNull(chat2dbApiKey.getData()) ? chat2dbApiKey.getData().getContent() : ""); - config.setApiHost( - Objects.nonNull(chat2dbApiHost.getData()) ? chat2dbApiHost.getData().getContent() : ""); - config.setModel(Objects.nonNull(chat2dbModel.getData()) ? chat2dbModel.getData().getContent() : ""); - break; - case AZUREAI: - DataResult azureApiKey = configService.find(AzureOpenAIClient.AZURE_CHATGPT_API_KEY); - DataResult azureEndpoint = configService.find(AzureOpenAIClient.AZURE_CHATGPT_ENDPOINT); - DataResult azureDeployId = configService.find(AzureOpenAIClient.AZURE_CHATGPT_DEPLOYMENT_ID); - config.setApiKey(Objects.nonNull(azureApiKey.getData()) ? azureApiKey.getData().getContent() : ""); - config.setApiHost(Objects.nonNull(azureEndpoint.getData()) ? azureEndpoint.getData().getContent() : ""); - config.setModel(Objects.nonNull(azureDeployId.getData()) ? azureDeployId.getData().getContent() : ""); - break; - case RESTAI: - DataResult restAiUrl = configService.find(RestAIClient.REST_AI_URL); - DataResult restAiHttpMethod = configService.find(RestAIClient.REST_AI_STREAM_OUT); - config.setApiHost(Objects.nonNull(restAiUrl.getData()) ? restAiUrl.getData().getContent() : ""); - config.setStream(Objects.nonNull(restAiHttpMethod.getData()) ? Boolean.valueOf( - restAiHttpMethod.getData().getContent()) : Boolean.TRUE); - break; - case FASTCHATAI: - DataResult fastChatApiKey = configService.find(FastChatAIClient.FASTCHAT_API_KEY); - DataResult fastChatApiHost = configService.find(FastChatAIClient.FASTCHAT_HOST); - DataResult fastChatModel = configService.find(FastChatAIClient.FASTCHAT_MODEL); - config.setApiKey(Objects.nonNull(fastChatApiKey.getData()) ? fastChatApiKey.getData().getContent() : ""); - config.setApiHost(Objects.nonNull(fastChatApiHost.getData()) ? fastChatApiHost.getData().getContent() : ""); - config.setModel(Objects.nonNull(fastChatModel.getData()) ? fastChatModel.getData().getContent() : ""); - break; - case WENXINAI: - DataResult wenxinAccessToken = configService.find(WenxinAIClient.WENXIN_ACCESS_TOKEN); - DataResult wenxinApiHost = configService.find(WenxinAIClient.WENXIN_HOST); - config.setApiKey(Objects.nonNull(wenxinAccessToken.getData()) ? wenxinAccessToken.getData().getContent() : ""); - config.setApiHost(Objects.nonNull(wenxinApiHost.getData()) ? wenxinApiHost.getData().getContent() : ""); - break; - case BAICHUANAI: - DataResult baichuanApiKey = configService.find(BaichuanAIClient.BAICHUAN_API_KEY); - DataResult baichuanSecretKey = configService.find(BaichuanAIClient.BAICHUAN_SECRET_KEY); - DataResult baichuanApiHost = configService.find(BaichuanAIClient.BAICHUAN_HOST); - DataResult baichuanModel = configService.find(BaichuanAIClient.BAICHUAN_MODEL); - config.setApiKey(Objects.nonNull(baichuanApiKey.getData()) ? baichuanApiKey.getData().getContent() : ""); - config.setSecretKey(Objects.nonNull(baichuanSecretKey.getData()) ? baichuanSecretKey.getData().getContent() : ""); - config.setApiHost(Objects.nonNull(baichuanApiHost.getData()) ? baichuanApiHost.getData().getContent() : ""); - config.setModel(Objects.nonNull(baichuanModel.getData()) ? baichuanModel.getData().getContent() : ""); - break; - case TONGYIQIANWENAI: - DataResult tongyiApiKey = configService.find(TongyiChatAIClient.TONGYI_API_KEY); - DataResult tongyiApiHost = configService.find(TongyiChatAIClient.TONGYI_HOST); - DataResult tongyiModel = configService.find(TongyiChatAIClient.TONGYI_MODEL); - config.setApiKey(Objects.nonNull(tongyiApiKey.getData()) ? tongyiApiKey.getData().getContent() : ""); - config.setApiHost(Objects.nonNull(tongyiApiHost.getData()) ? tongyiApiHost.getData().getContent() : ""); - config.setModel(Objects.nonNull(tongyiModel.getData()) ? tongyiModel.getData().getContent() : ""); - break; - case ZHIPUAI: - DataResult zhipuApiKey = configService.find(ZhipuChatAIClient.ZHIPU_API_KEY); - DataResult zhipuApiHost = configService.find(ZhipuChatAIClient.ZHIPU_HOST); - DataResult zhipuModel = configService.find(ZhipuChatAIClient.ZHIPU_MODEL); - config.setApiKey(Objects.nonNull(zhipuApiKey.getData()) ? zhipuApiKey.getData().getContent() : ""); - config.setApiHost(Objects.nonNull(zhipuApiHost.getData()) ? zhipuApiHost.getData().getContent() : ""); - config.setModel(Objects.nonNull(zhipuModel.getData()) ? zhipuModel.getData().getContent() : ""); - break; - default: - break; + private List readModelServices() { + String content = getConfigValue(MODEL_SERVICE_CONFIG_CODE); + if (StringUtils.isBlank(content)) { + return new ArrayList<>(); } + List services = JSON.parseObject(content, new TypeReference>() { + }); + return services == null ? new ArrayList<>() : services; + } - return DataResult.of(config); + private void saveSystemConfig(String code, String content) { + SystemConfigParam param = SystemConfigParam.builder().code(code).content(content).build(); + configService.createOrUpdate(param); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/request/AIConfigCreateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/request/AIConfigCreateRequest.java index 188a9b434..ed996df49 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/request/AIConfigCreateRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/request/AIConfigCreateRequest.java @@ -5,52 +5,53 @@ import jakarta.validation.constraints.NotNull; import lombok.Data; -/** - * @author jipengfei - * @version : SystemConfigRequest.java - */ @Data public class AIConfigCreateRequest { - /** - * APIKEY - */ private String apiKey; - /** - * SECRETKEY - */ private String secretKey; - /** - * APIHOST - */ private String apiHost; - /** - * api http proxy host - */ private String httpProxyHost; - /** - * api http proxy port - */ private String httpProxyPort; - /** - * @see AiSqlSourceEnum - */ @NotNull private String aiSqlSource; - /** - * return data stream - * 非必填,默认值为TRUE - */ private Boolean stream = Boolean.TRUE; - /** - * deployed model, default gpt-3.5-turbo - */ private String model; + + private String embeddingModel; + + private String temperature; + + private String maxTokens; + + private String topP; + + private String topK; + + private String stopSequences; + + private String betaVersion; + + private String n; + + private String stop; + + private String presencePenalty; + + private String frequencyPenalty; + + private String logitBias; + + private String user; + + private String organizationId; + + private String projectId; } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/request/DefaultModelConfigRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/request/DefaultModelConfigRequest.java new file mode 100644 index 000000000..bfe5ac4d2 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/request/DefaultModelConfigRequest.java @@ -0,0 +1,9 @@ +package ai.chat2db.server.web.api.controller.config.request; + +import lombok.Data; + +@Data +public class DefaultModelConfigRequest { + private String defaultModelId; + private String fastModelId; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/request/ModelItemRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/request/ModelItemRequest.java new file mode 100644 index 000000000..fc05f21dc --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/request/ModelItemRequest.java @@ -0,0 +1,10 @@ +package ai.chat2db.server.web.api.controller.config.request; + +import lombok.Data; + +@Data +public class ModelItemRequest { + private String id; + private String name; + private String model; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/request/ModelServiceDeleteRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/request/ModelServiceDeleteRequest.java new file mode 100644 index 000000000..67752a555 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/request/ModelServiceDeleteRequest.java @@ -0,0 +1,8 @@ +package ai.chat2db.server.web.api.controller.config.request; + +import lombok.Data; + +@Data +public class ModelServiceDeleteRequest { + private String id; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/request/ModelServiceTestRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/request/ModelServiceTestRequest.java new file mode 100644 index 000000000..157950401 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/request/ModelServiceTestRequest.java @@ -0,0 +1,14 @@ +package ai.chat2db.server.web.api.controller.config.request; + +import java.util.ArrayList; +import java.util.List; + +import lombok.Data; + +@Data +public class ModelServiceTestRequest { + private String provider; + private String apiKey; + private String apiHost; + private List modelList = new ArrayList<>(); +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/request/ModelServiceUpsertRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/request/ModelServiceUpsertRequest.java new file mode 100644 index 000000000..a79a892f1 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/request/ModelServiceUpsertRequest.java @@ -0,0 +1,20 @@ +package ai.chat2db.server.web.api.controller.config.request; + +import java.util.ArrayList; +import java.util.List; + +import lombok.Data; + +@Data +public class ModelServiceUpsertRequest { + private String id; + private String name; + private String provider; + private String apiKey; + private String apiHost; + private String httpProxyHost; + private String httpProxyPort; + private String organizationId; + private String projectId; + private List modelList = new ArrayList<>(); +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/response/DefaultModelConfigResponse.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/response/DefaultModelConfigResponse.java new file mode 100644 index 000000000..43d5a9170 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/response/DefaultModelConfigResponse.java @@ -0,0 +1,9 @@ +package ai.chat2db.server.web.api.controller.config.response; + +import lombok.Data; + +@Data +public class DefaultModelConfigResponse { + private String defaultModelId = ""; + private String fastModelId = ""; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/response/ModelServiceResponse.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/response/ModelServiceResponse.java new file mode 100644 index 000000000..692e4594b --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/response/ModelServiceResponse.java @@ -0,0 +1,21 @@ +package ai.chat2db.server.web.api.controller.config.response; + +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.server.web.api.controller.config.request.ModelItemRequest; +import lombok.Data; + +@Data +public class ModelServiceResponse { + private String id; + private String name; + private String provider; + private String apiKey; + private String apiHost; + private String httpProxyHost; + private String httpProxyPort; + private String organizationId; + private String projectId; + private List modelList = new ArrayList<>(); +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/ChartController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/ChartController.java index 1660b8139..1e910d5a3 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/ChartController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/ChartController.java @@ -4,6 +4,7 @@ import ai.chat2db.server.domain.api.chart.ChartListQueryParam; import ai.chat2db.server.domain.api.chart.ChartQueryParam; import ai.chat2db.server.domain.api.chart.ChartUpdateParam; +import ai.chat2db.server.domain.api.model.Chart; import ai.chat2db.server.domain.api.service.ChartService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; @@ -25,6 +26,8 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; +import java.util.List; + /** * 保存图表类 * @@ -53,8 +56,11 @@ public DataResult get(@PathVariable("id") Long id) { ChartQueryParam param = new ChartQueryParam(); param.setId(id); param.setUserId(ContextUtils.getUserId()); - return chartService.queryExistent(param) - .map(chartWebConverter::model2vo); + Chart result = chartService.queryExistent(param); + if (result == null) { + return null; + } + return DataResult.of(chartWebConverter.model2vo(result)); } /** @@ -68,8 +74,11 @@ public ListResult list(ChartQueryRequest request) { ChartListQueryParam param = new ChartListQueryParam(); param.setIdList(request.getIds()); param.setUserId(ContextUtils.getUserId()); - return chartService.listQuery(param) - .map(chartWebConverter::model2vo); + List result = chartService.listQuery(param); + if (result == null) { + return null; + } + return ListResult.of(chartWebConverter.model2vo(result)); } /** @@ -81,7 +90,7 @@ public ListResult list(ChartQueryRequest request) { @PostMapping("/create") public DataResult create(@Valid @RequestBody ChartCreateRequest request) { ChartCreateParam chartCreateParam = chartWebConverter.req2param(request); - return chartService.createWithPermission(chartCreateParam); + return DataResult.of(chartService.createWithPermission(chartCreateParam)); } /** @@ -93,7 +102,8 @@ public DataResult create(@Valid @RequestBody ChartCreateRequest request) { @RequestMapping(value = "/update", method = {RequestMethod.POST, RequestMethod.PUT}) public ActionResult update(@RequestBody ChartUpdateRequest request) { ChartUpdateParam param = chartWebConverter.req2updateParam(request); - return chartService.updateWithPermission(param); + chartService.updateWithPermission(param); + return ActionResult.isSuccess(); } /** @@ -104,7 +114,8 @@ public ActionResult update(@RequestBody ChartUpdateRequest request) { */ @DeleteMapping("/{id}") public ActionResult delete(@PathVariable("id") Long id) { - return chartService.deleteWithPermission(id); + chartService.deleteWithPermission(id); + return ActionResult.isSuccess(); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/DashboardController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/DashboardController.java index ecf48b9ac..3637adba6 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/DashboardController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/DashboardController.java @@ -8,9 +8,9 @@ import ai.chat2db.server.domain.api.param.dashboard.DashboardQueryParam; import ai.chat2db.server.domain.api.param.dashboard.DashboardUpdateParam; import ai.chat2db.server.domain.api.service.DashboardService; +import ai.chat2db.server.tools.base.wrapper.ServicePage; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; -import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.server.web.api.controller.dashboard.converter.DashboardWebConverter; @@ -54,7 +54,7 @@ public class DashboardController { @GetMapping("/list") public WebPageResult list(DashboardPageQueryParam request) { request.setUserId(ContextUtils.getUserId()); - PageResult result = dashboardService.queryPage(request); + ServicePage result = dashboardService.queryPage(request); List dashboardVOS = dashboardWebConverter.model2vo(result.getData()); return WebPageResult.of(dashboardVOS, result.getTotal(), result.getPageNo(), result.getPageSize()); } @@ -70,8 +70,11 @@ public DataResult get(@PathVariable("id") Long id) { DashboardQueryParam param = new DashboardQueryParam(); param.setId(id); param.setUserId(ContextUtils.getUserId()); - return dashboardService.queryExistent(param) - .map(dashboardWebConverter::model2vo); + Dashboard result = dashboardService.queryExistent(param); + if (result == null) { + return null; + } + return DataResult.of(dashboardWebConverter.model2vo(result)); } /** @@ -83,7 +86,7 @@ public DataResult get(@PathVariable("id") Long id) { @PostMapping("/create") public DataResult create(@RequestBody DashboardCreateRequest request) { DashboardCreateParam param = dashboardWebConverter.req2param(request); - return dashboardService.createWithPermission(param); + return DataResult.of(dashboardService.createWithPermission(param)); } /** @@ -95,7 +98,8 @@ public DataResult create(@RequestBody DashboardCreateRequest request) { @RequestMapping(value = "/update", method = {RequestMethod.POST, RequestMethod.PUT}) public ActionResult update(@RequestBody DashboardUpdateRequest request) { DashboardUpdateParam param = dashboardWebConverter.req2updateParam(request); - return dashboardService.updateWithPermission(param); + dashboardService.updateWithPermission(param); + return ActionResult.isSuccess(); } /** @@ -106,6 +110,7 @@ public ActionResult update(@RequestBody DashboardUpdateRequest request) { */ @DeleteMapping("/{id}") public ActionResult delete(@PathVariable("id") Long id) { - return dashboardService.deleteWithPermission(id); + dashboardService.deleteWithPermission(id); + return ActionResult.isSuccess(); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/DataSourceController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/DataSourceController.java index 37c8e4c9d..a74022ab7 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/DataSourceController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/DataSourceController.java @@ -9,16 +9,17 @@ import ai.chat2db.server.domain.api.param.datasource.DataSourcePageQueryParam; import ai.chat2db.server.domain.api.param.datasource.DataSourcePreConnectParam; import ai.chat2db.server.domain.api.param.datasource.DataSourceSelector; +import ai.chat2db.server.domain.api.param.datasource.DataSourceSortUpdateParam; import ai.chat2db.server.domain.api.param.datasource.DataSourceUpdateParam; import ai.chat2db.server.domain.api.service.ConsoleService; import ai.chat2db.server.domain.api.service.DataSourceService; import ai.chat2db.server.tools.common.exception.ConnectionException; import ai.chat2db.spi.model.Database; import ai.chat2db.spi.ssh.SSHManager; +import ai.chat2db.server.tools.base.wrapper.ServicePage; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; -import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; import ai.chat2db.server.web.api.controller.data.source.converter.DataSourceWebConverter; @@ -30,6 +31,7 @@ import ai.chat2db.server.web.api.controller.data.source.request.DataSourceCloseRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceCreateRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceQueryRequest; +import ai.chat2db.server.web.api.controller.data.source.request.DataSourceSortUpdateRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceTestRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceUpdateRequest; import ai.chat2db.server.web.api.controller.data.source.request.SSHTestRequest; @@ -88,7 +90,8 @@ public class DataSourceController { @RequestMapping("/datasource/pre_connect") public ActionResult preConnect(@RequestBody DataSourceTestRequest request) { DataSourcePreConnectParam param = dataSourceWebConverter.testRequest2param(request); - return dataSourceService.preConnect(param); + dataSourceService.preConnect(param); + return ActionResult.isSuccess(); } /** @@ -121,8 +124,8 @@ public ActionResult sshConnect(@RequestBody SSHTestRequest request) { */ @GetMapping("/datasource/connect") public ListResult attach(@Valid @NotNull DataSourceAttachRequest request) { - ListResult databaseDTOListResult = dataSourceService.connect(request.getId()); - List databaseVOS = dataSourceWebConverter.databaseDto2vo(databaseDTOListResult.getData()); + List databaseList = dataSourceService.connect(request.getId()); + List databaseVOS = dataSourceWebConverter.databaseDto2vo(databaseList); return ListResult.of(databaseVOS); } @@ -134,7 +137,8 @@ public ListResult attach(@Valid @NotNull DataSourceAttachRequest req */ @GetMapping("/datasource/close") public ActionResult close(@Valid @NotNull DataSourceCloseRequest request) { - return dataSourceService.close(request.getId()); + dataSourceService.close(request.getId()); + return ActionResult.isSuccess(); } /** @@ -146,7 +150,8 @@ public ActionResult close(@Valid @NotNull DataSourceCloseRequest request) { @GetMapping("/console/connect") public ActionResult connect(@Valid @NotNull ConsoleConnectRequest request) { ConsoleConnectParam consoleConnectParam = dataSourceWebConverter.request2connectParam(request); - return consoleService.createConsole(consoleConnectParam); + consoleService.createConsole(consoleConnectParam); + return ActionResult.isSuccess(); } /** @@ -158,7 +163,8 @@ public ActionResult connect(@Valid @NotNull ConsoleConnectRequest request) { @GetMapping("/console/close") public ActionResult closeConsole(@Valid @NotNull ConsoleCloseRequest request) { ConsoleCloseParam closeParam = dataSourceWebConverter.request2closeParam(request); - return consoleService.closeConsole(closeParam); + consoleService.closeConsole(closeParam); + return ActionResult.isSuccess(); } /** @@ -171,7 +177,7 @@ public ActionResult closeConsole(@Valid @NotNull ConsoleCloseRequest request) { @GetMapping("/datasource/list") public WebPageResult list(DataSourceQueryRequest request) { DataSourcePageQueryParam param = dataSourceWebConverter.queryReq2param(request); - PageResult result = dataSourceService.queryPageWithPermission(param, DATA_SOURCE_SELECTOR); + ServicePage result = dataSourceService.queryPageWithPermission(param, DATA_SOURCE_SELECTOR); List dataSourceVOS = dataSourceWebConverter.dto2vo(result.getData()); return WebPageResult.of(dataSourceVOS, result.getTotal(), result.getPageNo(), result.getPageSize()); } @@ -184,8 +190,8 @@ public WebPageResult list(DataSourceQueryRequest request) { */ @GetMapping("/datasource/{id}") public DataResult queryById(@PathVariable("id") Long id) { - DataResult dataResult = dataSourceService.queryExistent(id, DATA_SOURCE_SELECTOR); - DataSourceVO dataSourceVO = dataSourceWebConverter.dto2vo(dataResult.getData()); + DataSource dataResult = dataSourceService.queryExistent(id, DATA_SOURCE_SELECTOR); + DataSourceVO dataSourceVO = dataSourceWebConverter.dto2vo(dataResult); if (StringUtils.isNotBlank(dataSourceVO.getUser())) { dataSourceVO.setAuthenticationType("1"); } else { @@ -203,7 +209,7 @@ public DataResult queryById(@PathVariable("id") Long id) { @PostMapping("/datasource/create") public DataResult create(@RequestBody DataSourceCreateRequest request) { DataSourceCreateParam param = dataSourceWebConverter.createReq2param(request); - return dataSourceService.createWithPermission(param); + return DataResult.of(dataSourceService.createWithPermission(param)); } /** @@ -215,7 +221,7 @@ public DataResult create(@RequestBody DataSourceCreateRequest request) { @RequestMapping(value = "/datasource/update", method = {RequestMethod.POST, RequestMethod.PUT}) public DataResult update(@RequestBody DataSourceUpdateRequest request) { DataSourceUpdateParam param = dataSourceWebConverter.updateReq2param(request); - return dataSourceService.updateWithPermission(param); + return DataResult.of(dataSourceService.updateWithPermission(param)); } /** @@ -226,7 +232,20 @@ public DataResult update(@RequestBody DataSourceUpdateRequest request) { */ @PostMapping("/datasource/clone") public DataResult copy(@RequestBody DataSourceCloneRequest request) { - return dataSourceService.copyByIdWithPermission(request.getId()); + return DataResult.of(dataSourceService.copyByIdWithPermission(request.getId())); + } + + /** + * 更新当前用户的数据源连接排序 + * + * @param request + * @return + */ + @PostMapping("/datasource/sort") + public ActionResult updateSort(@RequestBody DataSourceSortUpdateRequest request) { + DataSourceSortUpdateParam param = dataSourceWebConverter.sortUpdateReq2param(request); + dataSourceService.updateSortWithPermission(param); + return ActionResult.isSuccess(); } /** @@ -237,7 +256,8 @@ public DataResult copy(@RequestBody DataSourceCloneRequest request) { */ @DeleteMapping("/datasource/{id}") public ActionResult delete(@PathVariable Long id) { - return dataSourceService.deleteWithPermission(id); + dataSourceService.deleteWithPermission(id); + return ActionResult.isSuccess(); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/converter/DataSourceWebConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/converter/DataSourceWebConverter.java index 26802232e..06fb4f0ec 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/converter/DataSourceWebConverter.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/converter/DataSourceWebConverter.java @@ -9,11 +9,13 @@ import ai.chat2db.server.domain.api.param.datasource.DataSourceCreateParam; import ai.chat2db.server.domain.api.param.datasource.DataSourcePageQueryParam; import ai.chat2db.server.domain.api.param.datasource.DataSourcePreConnectParam; +import ai.chat2db.server.domain.api.param.datasource.DataSourceSortUpdateParam; import ai.chat2db.server.domain.api.param.datasource.DataSourceUpdateParam; import ai.chat2db.server.web.api.controller.data.source.request.ConsoleCloseRequest; import ai.chat2db.server.web.api.controller.data.source.request.ConsoleConnectRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceCreateRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceQueryRequest; +import ai.chat2db.server.web.api.controller.data.source.request.DataSourceSortUpdateRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceTestRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceUpdateRequest; import ai.chat2db.server.web.api.controller.data.source.vo.DataSourceVO; @@ -62,6 +64,14 @@ public abstract class DataSourceWebConverter { */ public abstract DataSourcePageQueryParam queryReq2param(DataSourceQueryRequest request); + /** + * 参数转换 + * + * @param request + * @return + */ + public abstract DataSourceSortUpdateParam sortUpdateReq2param(DataSourceSortUpdateRequest request); + /** * 模型转换 * diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/DataSourceSortUpdateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/DataSourceSortUpdateRequest.java new file mode 100644 index 000000000..ceee395c8 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/DataSourceSortUpdateRequest.java @@ -0,0 +1,18 @@ +package ai.chat2db.server.web.api.controller.data.source.request; + +import java.util.List; +import lombok.Data; + +/** + * 数据源连接排序更新请求 + * + * @author chat2db + */ +@Data +public class DataSourceSortUpdateRequest { + + /** + * 按目标顺序排列的数据源 ID + */ + private List idList; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/driver/JdbcDriverController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/driver/JdbcDriverController.java index 523dafcf8..982ca938e 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/driver/JdbcDriverController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/driver/JdbcDriverController.java @@ -44,7 +44,7 @@ public class JdbcDriverController { */ @GetMapping("/list") public DataResult list(@RequestParam String dbType) { - return jdbcDriverService.getDrivers(dbType); + return DataResult.of(jdbcDriverService.getDrivers(dbType)); } /** @@ -56,7 +56,8 @@ public DataResult list(@RequestParam String dbType) { @GetMapping("/download") public ActionResult download(@RequestParam String dbType) { - return jdbcDriverService.download(dbType); + jdbcDriverService.download(dbType); + return ActionResult.isSuccess(); } /** @@ -92,8 +93,9 @@ public ListResult upload(@RequestParam MultipartFile[] multipartFiles) { @PostMapping("/save") public ActionResult save(@RequestBody JdbcDriverRequest request) { - return jdbcDriverService.upload(request.getDbType(), request.getJdbcDriverClass(), + jdbcDriverService.upload(request.getDbType(), request.getJdbcDriverClass(), String.join(",", request.getJdbcDriver())); + return ActionResult.isSuccess(); } ///** diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java index e372baf30..d3b10a6f4 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java @@ -3,7 +3,6 @@ import ai.chat2db.server.domain.core.util.DesUtil; import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.domain.repository.entity.DataSourceDO; -import ai.chat2db.server.domain.repository.mapper.ChartMapper; import ai.chat2db.server.domain.repository.mapper.DataSourceMapper; import ai.chat2db.server.tools.common.util.ConfigUtils; import ai.chat2db.server.tools.common.util.ContextUtils; @@ -23,7 +22,6 @@ import com.alibaba.fastjson2.JSONObject; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.w3c.dom.Document; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/log/OperationLogController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/log/OperationLogController.java index 3ec810b08..c15ff046e 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/log/OperationLogController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/log/OperationLogController.java @@ -6,8 +6,8 @@ import ai.chat2db.server.domain.api.param.operation.OperationLogCreateParam; import ai.chat2db.server.domain.api.param.operation.OperationLogPageQueryParam; import ai.chat2db.server.domain.api.service.OperationLogService; +import ai.chat2db.server.tools.base.wrapper.ServicePage; import ai.chat2db.server.tools.base.wrapper.result.DataResult; -import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.server.web.api.controller.operation.log.converter.OperationLogWebConverter; @@ -49,7 +49,7 @@ public class OperationLogController { public WebPageResult list(OperationLogQueryRequest request) { OperationLogPageQueryParam param = operationLogWebConverter.req2param(request); param.setUserId(ContextUtils.getUserId()); - PageResult result = operationLogService.queryPage(param); + ServicePage result = operationLogService.queryPage(param); List operationLogVOList = operationLogWebConverter.dto2vo(result.getData()); return WebPageResult.of(operationLogVOList, result.getTotal(), result.getPageNo(), result.getPageSize()); } @@ -63,7 +63,7 @@ public WebPageResult list(OperationLogQueryRequest request) { @PostMapping("/create") public DataResult create(@RequestBody OperationLogCreateRequest request) { OperationLogCreateParam param = operationLogWebConverter.createReq2param(request); - return operationLogService.create(param); + return DataResult.of(operationLogService.create(param)); } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/saved/OperationSavedController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/saved/OperationSavedController.java index 42b2adc7d..b9379ce44 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/saved/OperationSavedController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/saved/OperationSavedController.java @@ -8,9 +8,9 @@ import ai.chat2db.server.domain.api.param.operation.OperationSavedParam; import ai.chat2db.server.domain.api.param.operation.OperationUpdateParam; import ai.chat2db.server.domain.api.service.OperationService; +import ai.chat2db.server.tools.base.wrapper.ServicePage; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; -import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.server.web.api.controller.operation.saved.converter.OperationWebConverter; @@ -57,7 +57,7 @@ public class OperationSavedController { public WebPageResult list(OperationQueryRequest request) { OperationPageQueryParam param = operationWebConverter.queryReq2param(request,ContextUtils.getUserId()); param.setUserId(ContextUtils.getUserId()); - PageResult dtoPageResult = operationService.queryPage(param); + ServicePage dtoPageResult = operationService.queryPage(param); List operationVOS = operationWebConverter.dto2vo(dtoPageResult.getData()); return WebPageResult.of(operationVOS, dtoPageResult.getTotal(), request.getPageNo(), request.getPageSize()); } @@ -73,7 +73,11 @@ public DataResult get(@PathVariable("id") Long id) { OperationQueryParam param = new OperationQueryParam(); param.setId(id); param.setUserId(ContextUtils.getUserId()); - return operationService.queryExistent(param).map(operationWebConverter::dto2vo); + Operation result = operationService.queryExistent(param); + if (result == null) { + return null; + } + return DataResult.of(operationWebConverter.dto2vo(result)); } /** @@ -86,7 +90,7 @@ public DataResult get(@PathVariable("id") Long id) { public DataResult create(@RequestBody OperationCreateRequest request) { OperationSavedParam param = operationWebConverter.req2param(request); param.setTabOpened("y"); - return operationService.createWithPermission(param); + return DataResult.of(operationService.createWithPermission(param)); } /** @@ -98,7 +102,8 @@ public DataResult create(@RequestBody OperationCreateRequest request) { @RequestMapping(value = "/update", method = {RequestMethod.POST, RequestMethod.PUT}) public ActionResult update(@RequestBody OperationUpdateRequest request) { OperationUpdateParam param = operationWebConverter.updateReq2param(request); - return operationService.updateWithPermission(param); + operationService.updateWithPermission(param); + return ActionResult.isSuccess(); } /** @@ -129,6 +134,7 @@ public ActionResult batchTabClose(@RequestBody BatchTabCloseRequest request) { */ @DeleteMapping("/{id}") public ActionResult delete(@PathVariable("id") Long id) { - return operationService.deleteWithPermission(id); + operationService.deleteWithPermission(id); + return ActionResult.isSuccess(); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/DataGenerationController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/DataGenerationController.java new file mode 100644 index 000000000..77fa01a32 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/DataGenerationController.java @@ -0,0 +1,89 @@ +package ai.chat2db.server.web.api.controller.rdb; + +import ai.chat2db.server.domain.api.param.DataGenerationRequest; +import ai.chat2db.server.domain.api.service.DataGenerationService; +import ai.chat2db.server.domain.api.service.DataGenerationRuleService; +import ai.chat2db.server.domain.api.vo.DataGenerationPreviewVO; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; +import ai.chat2db.server.tools.base.wrapper.result.ListResult; +import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; +import ai.chat2db.server.web.api.controller.rdb.converter.DataGenerationConverter; +import ai.chat2db.server.web.api.controller.rdb.request.DataGenerationRequestVO; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +@Slf4j +@RestController +@ConnectionInfoAspect +@RequestMapping("/api/rdb/table/generate-data") +public class DataGenerationController { + + @Autowired + private DataGenerationService dataGenerationService; + + @Autowired + private DataGenerationRuleService ruleService; + + @PostMapping("/config") + public ListResult getTableColumns( + @RequestBody DataGenerationRequestVO requestVO) { + try { + DataGenerationRequest request = DataGenerationConverter.voToRequest(requestVO); + return ListResult.of(dataGenerationService.getTableColumns(request)); + } catch (Exception e) { + log.error("Failed to get table columns for data generation", e); + return ListResult.error("获取表列信息失败: " + e.getMessage(), null); + } + } + + @PostMapping("/preview") + public DataResult generatePreview( + @RequestBody DataGenerationRequestVO requestVO) { + try { + DataGenerationRequest request = DataGenerationConverter.voToRequest(requestVO); + request.setPreviewMode(true); + request.setRowCount(10); + return DataResult.of(dataGenerationService.generatePreview(request)); + } catch (Exception e) { + log.error("Failed to generate data preview", e); + return DataResult.error("GENERATE_PREVIEW_ERROR", "生成预览失败: " + e.getMessage()); + } + } + + @PostMapping("/execute") + public DataResult executeDataGeneration( + @RequestBody DataGenerationRequestVO requestVO) { + try { + DataGenerationRequest request = DataGenerationConverter.voToRequest(requestVO); + return DataResult.of(dataGenerationService.executeDataGeneration(request)); + } catch (Exception e) { + log.error("Failed to execute data generation", e); + return DataResult.error("EXECUTE_GENERATION_ERROR", "执行数据生成失败: " + e.getMessage()); + } + } + + @GetMapping("/templates") + public ListResult getAllGeneratorTemplates() { + try { + return ListResult.of(dataGenerationService.getAllGeneratorTemplates()); + } catch (Exception e) { + log.error("Failed to get all generator templates", e); + return ListResult.error("获取生成模板失败: " + e.getMessage(), null); + } + } + + @GetMapping("/generation-rule/list") + public ListResult getColumnConfigs( + @RequestParam Long dataSourceId, + @RequestParam String databaseName, + @RequestParam(required = false) String schemaName, + @RequestParam String tableName) { + try { + return ListResult.of(ruleService.getColumnConfigs(dataSourceId, databaseName, schemaName, tableName)); + } catch (Exception e) { + log.error("Failed to get column configs", e); + return ListResult.error("GET_COLUMN_CONFIGS_ERROR", "获取列配置失败: " + e.getMessage()); + } + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/DatabaseController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/DatabaseController.java index 848ae1722..38a9068d5 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/DatabaseController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/DatabaseController.java @@ -20,7 +20,6 @@ import ai.chat2db.spi.model.Sql; import jakarta.validation.Valid; import org.apache.commons.lang3.StringUtils; -import org.apache.poi.util.StringUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; @@ -28,6 +27,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import java.util.List; + /** * database controller */ @@ -45,28 +46,42 @@ public class DatabaseController { public DatabaseConverter databaseConverter; /** - * 查询数据库里包含的database_schema_list + * 查询数据库和 Schema 列表 + *

+ * 返回当前数据源下所有的 Database 和 Schema 信息 + * 适用于 PostgreSQL 等支持 Schema 概念的数据库,可以同时获取 database 和 schema 列表 + *

* - * @param request - * @return + * @param request 请求参数,包含数据源 ID + * @return MetaSchemaVO,包含 databases(数据库列表)和 schemas(Schema 列表) */ @GetMapping("/database_schema_list") public DataResult databaseSchemaList(@Valid DataSourceBaseRequest request) { MetaDataQueryParam queryParam = MetaDataQueryParam.builder().dataSourceId(request.getDataSourceId()) .refresh( request.isRefresh()).build(); - DataResult result = databaseService.queryDatabaseSchema(queryParam); - MetaSchemaVO schemaDto2vo = rdbWebConverter.metaSchemaDto2vo(result.getData()); + MetaSchema result = databaseService.queryDatabaseSchema(queryParam); + MetaSchemaVO schemaDto2vo = rdbWebConverter.metaSchemaDto2vo(result); return DataResult.of(schemaDto2vo); } + /** + * 查询数据库列表 + *

+ * 返回当前数据源下所有的 Database 信息 + * 仅获取数据库列表,不包含 Schema 信息 + *

+ * + * @param request 请求参数,包含数据源 ID + * @return DatabaseVO 列表,每个 DatabaseVO 包含数据库名称、关联的 schemas、注释等信息 + */ @GetMapping("list") public ListResult databaseList(@Valid DataSourceBaseRequest request) { DatabaseQueryAllParam queryParam = DatabaseQueryAllParam.builder().dataSourceId(request.getDataSourceId()) .refresh( request.isRefresh()).build(); - ListResult result = databaseService.queryAll(queryParam); - return ListResult.of(rdbWebConverter.databaseDto2vo(result.getData())); + List result = databaseService.queryAll(queryParam); + return ListResult.of(rdbWebConverter.databaseDto2vo(result)); } /** @@ -78,7 +93,8 @@ public ListResult databaseList(@Valid DataSourceBaseRequest request) @PostMapping("/delete_database") public ActionResult deleteDatabase(@Valid @RequestBody DataSourceBaseRequest request) { DatabaseCreateParam param = DatabaseCreateParam.builder().name(request.getDatabaseName()).build(); - return databaseService.deleteDatabase(param); + databaseService.deleteDatabase(param); + return ActionResult.isSuccess(); } /** @@ -93,7 +109,7 @@ public DataResult createDatabase(@Valid @RequestBody DatabaseCreateRequest request.setName(request.getDatabaseName()); } Database database = databaseConverter.request2param(request); - return databaseService.createDatabase(database); + return DataResult.of(databaseService.createDatabase(database)); } /** @@ -106,6 +122,7 @@ public DataResult createDatabase(@Valid @RequestBody DatabaseCreateRequest public ActionResult modifyDatabase(@Valid @RequestBody UpdateDatabaseRequest request) { DatabaseCreateParam param = DatabaseCreateParam.builder().name(request.getDatabaseName()) .name(request.getNewDatabaseName()).build(); - return databaseService.modifyDatabase(param); + databaseService.modifyDatabase(param); + return ActionResult.isSuccess(); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/ErDiagramController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/ErDiagramController.java new file mode 100644 index 000000000..60ea3324b --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/ErDiagramController.java @@ -0,0 +1,71 @@ +package ai.chat2db.server.web.api.controller.rdb; + +import ai.chat2db.server.domain.api.param.ErDiagramQueryParam; +import ai.chat2db.server.domain.api.service.ErDiagramService; +import ai.chat2db.server.domain.api.vo.InferVirtualFkResultVO; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; +import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; +import ai.chat2db.server.web.api.controller.rdb.request.ErDiagramQueryRequest; +import ai.chat2db.spi.model.ErDiagram; +import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * ER图控制器 + * 提供ER图数据查询接口,用于展示数据库表之间的关系 + */ +@ConnectionInfoAspect +@RequestMapping("/api/rdb/er") +@RestController +@Slf4j +public class ErDiagramController { + + @Autowired + private ErDiagramService erDiagramService; + + /** + * 查询ER图数据 + * 返回指定数据库中表之间的外键关系,用于前端渲染ER图 + * + * @param request 查询请求,包含数据源、数据库、schema、表名过滤、虚拟外键开关等参数 + * @return ER图数据,包含节点(表)和边(外键关系) + */ + @GetMapping("/diagram") + public DataResult diagram(@Valid ErDiagramQueryRequest request) { + ErDiagramQueryParam param = ErDiagramQueryParam.builder() + .dataSourceId(request.getDataSourceId()) + .databaseName(request.getDatabaseName()) + .schemaName(request.getSchemaName()) + .tableNameFilter(request.getTableNameFilter()) + .includeVirtualFk(request.getIncludeVirtualFk()) + .syncForeignKeys(request.getSyncForeignKeys()) + .onlyRelatedTables(request.getOnlyRelatedTables()) + .build(); + return DataResult.of(erDiagramService.queryErDiagram(param)); + } + + /** + * 推断并添加虚拟外键 + * 根据命名规范(如 user_id -> users.id)自动推断可能的虚拟外键关系 + * + * @param request 查询请求 + * @return 推断结果,包含新增和删除的虚拟外键列表 + */ + @PostMapping("/infer-virtual-fk") + public DataResult inferVirtualForeignKey(@Valid @RequestBody ErDiagramQueryRequest request) { + ErDiagramQueryParam param = ErDiagramQueryParam.builder() + .dataSourceId(request.getDataSourceId()) + .databaseName(request.getDatabaseName()) + .schemaName(request.getSchemaName()) + .tableNameFilter(request.getTableNameFilter()) + .includeVirtualFk(true) + .build(); + return DataResult.of(erDiagramService.inferVirtualForeignKeys(param)); + } +} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/ForeignKeyController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/ForeignKeyController.java new file mode 100644 index 000000000..f225df9b3 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/ForeignKeyController.java @@ -0,0 +1,140 @@ +package ai.chat2db.server.web.api.controller.rdb; + +import ai.chat2db.server.domain.api.param.CreateVirtualFKParam; +import ai.chat2db.server.domain.api.param.UpdateVirtualFKParam; +import ai.chat2db.server.domain.api.service.ForeignKeySyncService; +import ai.chat2db.server.domain.api.service.TableService; +import ai.chat2db.server.tools.base.wrapper.result.ActionResult; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; +import ai.chat2db.server.tools.base.wrapper.result.ListResult; +import ai.chat2db.server.web.api.controller.rdb.request.CreateVirtualFKRequest; +import ai.chat2db.server.web.api.controller.rdb.request.DeleteFKByNameRequest; +import ai.chat2db.server.web.api.controller.rdb.request.DeleteFKRequest; +import ai.chat2db.server.web.api.controller.rdb.request.ForeignKeyListRequest; +import ai.chat2db.server.web.api.controller.rdb.request.ForeignKeySyncRequest; +import ai.chat2db.server.web.api.controller.rdb.request.UpdateVirtualFKRequest; +import ai.chat2db.server.web.api.controller.rdb.vo.DeleteFKResult; +import ai.chat2db.server.web.api.controller.rdb.vo.ForeignKeyVO; +import ai.chat2db.server.web.api.controller.rdb.vo.SyncResult; +import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; +import ai.chat2db.spi.model.ForeignKey; +import ai.chat2db.spi.model.VirtualForeignKey; +import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.stream.Collectors; + +@Slf4j +@ConnectionInfoAspect +@RequestMapping("/api/rdb/fk") +@RestController +public class ForeignKeyController { + + @Autowired + private ForeignKeySyncService foreignKeySyncService; + + + @PostMapping("/sync") + public DataResult sync(@Valid @RequestBody ForeignKeySyncRequest request) { + ForeignKeySyncService.SyncResult result = foreignKeySyncService.syncForeignKeys( + request.getDataSourceId(), + request.getDatabaseName(), + request.getSchemaName(), + request.getTableName() + ); + return DataResult.of(SyncResult.builder() + .added(result.getAdded()) + .deleted(result.getDeleted()) + .unchanged(result.getUnchanged()) + .build()); + } + + @GetMapping("/list") + public ListResult list(@Valid ForeignKeyListRequest request) { + List fks = foreignKeySyncService.listAllForeignKeys( + request.getDataSourceId(), + request.getDatabaseName(), + request.getSchemaName(), + request.getTableName() + ); + List voList = fks.stream() + .map(fk -> ForeignKeyVO.builder() + .id(fk.getId()) + .name(fk.getName()) + .tableName(fk.getTableName()) + .columnName(fk.getColumn()) + .referencedTable(fk.getReferencedTable()) + .referencedColumnName(fk.getReferencedColumn()) + .comment(fk.getComment()) + .updateRule(fk.getUpdateRule()) + .deleteRule(fk.getDeleteRule()) + .sourceType(fk instanceof VirtualForeignKey ? "VIRTUAL" : "REAL") + .editable(fk instanceof VirtualForeignKey) + .build()) + .collect(Collectors.toList()); + return ListResult.of(voList); + } + + @PostMapping("/virtual/create") + public DataResult createVirtual(@Valid @RequestBody CreateVirtualFKRequest request) { + CreateVirtualFKParam param = CreateVirtualFKParam.builder() + .dataSourceId(request.getDataSourceId()) + .databaseName(request.getDatabaseName()) + .schemaName(request.getSchemaName()) + .tableName(request.getTableName()) + .columnName(request.getColumnName()) + .referencedTable(request.getReferencedTable()) + .referencedColumnName(request.getReferencedColumnName()) + .comment(request.getComment()) + .build(); + return DataResult.of(foreignKeySyncService.createVirtualFK(param)); + } + + @PostMapping("/virtual/update") + public DataResult updateVirtual(@Valid @RequestBody UpdateVirtualFKRequest request) { + UpdateVirtualFKParam param = UpdateVirtualFKParam.builder() + .id(request.getId()) + .dataSourceId(request.getDataSourceId()) + .databaseName(request.getDatabaseName()) + .schemaName(request.getSchemaName()) + .comment(request.getComment()) + .tableName(request.getTableName()) + .columnName(request.getColumnName()) + .referencedTable(request.getReferencedTable()) + .referencedColumnName(request.getReferencedColumnName()) + .vkName(request.getVkName()) + .build(); + return DataResult.of(foreignKeySyncService.updateVirtualFK(param)); + } + + @PostMapping("/delete") + public DataResult delete(@Valid @RequestBody DeleteFKRequest request) { + if ("VIRTUAL".equals(request.getSourceType())) { + foreignKeySyncService.deleteVirtualFK(request.getId()); + return DataResult.of(DeleteFKResult.builder().executedDDL(null).build()); + } else { + String ddl = foreignKeySyncService.deleteRealFK(request.getId()); + return DataResult.of(DeleteFKResult.builder().executedDDL(ddl).build()); + } + } + + @PostMapping("/delete_by_name") + public ActionResult deleteByName(@Valid @RequestBody DeleteFKByNameRequest request) { + List virtualFKs = foreignKeySyncService.queryVirtualForeignKeys( + request.getDataSourceId(), + request.getDatabaseName(), + request.getSchemaName(), + request.getTableName() + ); + for (VirtualForeignKey vk : virtualFKs) { + if (vk.getName() != null && vk.getName().equals(request.getKeyName())) { + foreignKeySyncService.deleteVirtualFK(vk.getId()); + return ActionResult.isSuccess(); + } + } + return ActionResult.fail("VIRTUAL_FK_NOT_FOUND", "虚拟外键不存在", ""); + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/FunctionController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/FunctionController.java index 8a43baaa0..dcd4dc11a 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/FunctionController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/FunctionController.java @@ -14,6 +14,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import java.util.List; + @ConnectionInfoAspect @RequestMapping("/api/rdb/function") @RestController @@ -24,14 +26,14 @@ public class FunctionController { @GetMapping("/list") public WebPageResult list(@Valid FunctionPageRequest request) { - ListResult functionListResult = functionService.functions(request.getDatabaseName(), - request.getSchemaName()); - return WebPageResult.of(functionListResult.getData(), Long.valueOf(functionListResult.getData().size()), 1, - functionListResult.getData().size()); + List functionList = functionService.functionsWithCache(request.getDataSourceId(), + request.getDatabaseName(), request.getSchemaName(), request.getSearchKey(), request.isRefresh()); + return WebPageResult.of(functionList, Long.valueOf(functionList.size()), 1, + functionList.size()); } @GetMapping("/detail") public DataResult detail(@Valid FunctionDetailRequest request) { - return functionService.detail(request.getDatabaseName(), request.getSchemaName(), request.getFunctionName()); + return DataResult.of(functionService.detail(request.getDatabaseName(), request.getSchemaName(), request.getFunctionName())); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/ProcedureController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/ProcedureController.java index fc6b86827..cdd5ce7e8 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/ProcedureController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/ProcedureController.java @@ -14,6 +14,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import java.util.List; + @ConnectionInfoAspect @RequestMapping("/api/rdb/procedure") @RestController @@ -24,14 +26,14 @@ public class ProcedureController { @GetMapping("/list") public WebPageResult list(@Valid ProcedurePageRequest request) { - ListResult procedureListResult = procedureService.procedures(request.getDatabaseName(), - request.getSchemaName()); - return WebPageResult.of(procedureListResult.getData(), Long.valueOf(procedureListResult.getData().size()), 1, - procedureListResult.getData().size()); + List procedureList = procedureService.proceduresWithCache(request.getDataSourceId(), + request.getDatabaseName(), request.getSchemaName(), request.getSearchKey(), request.isRefresh()); + return WebPageResult.of(procedureList, Long.valueOf(procedureList.size()), 1, + procedureList.size()); } @GetMapping("/detail") public DataResult detail(@Valid ProcedureDetailRequest request) { - return procedureService.detail(request.getDatabaseName(), request.getSchemaName(), request.getProcedureName()); + return DataResult.of(procedureService.detail(request.getDatabaseName(), request.getSchemaName(), request.getProcedureName())); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDdlController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDdlController.java deleted file mode 100644 index feb7f3f7b..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDdlController.java +++ /dev/null @@ -1,240 +0,0 @@ -package ai.chat2db.server.web.api.controller.rdb; - -import ai.chat2db.server.domain.api.param.*; -import ai.chat2db.server.domain.api.param.datasource.DatabaseCreateParam; -import ai.chat2db.server.domain.api.service.DatabaseService; -import ai.chat2db.server.domain.api.service.DlTemplateService; -import ai.chat2db.server.domain.api.service.TableService; -import ai.chat2db.server.tools.base.wrapper.result.ActionResult; -import ai.chat2db.server.tools.base.wrapper.result.DataResult; -import ai.chat2db.server.tools.base.wrapper.result.ListResult; -import ai.chat2db.server.tools.base.wrapper.result.PageResult; -import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; -import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; -import ai.chat2db.server.web.api.controller.ai.EmbeddingController; -import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; -import ai.chat2db.server.web.api.controller.rdb.converter.RdbWebConverter; -import ai.chat2db.server.web.api.controller.rdb.request.*; -import ai.chat2db.server.web.api.controller.rdb.vo.*; -import ai.chat2db.spi.model.*; -import ai.chat2db.spi.sql.Chat2DBContext; -import ai.chat2db.spi.sql.ConnectInfo; -import com.google.common.collect.Lists; -import jakarta.validation.Valid; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.*; - -import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -/** - * mysql表运维类 - * - * @author moji - * @version MysqlTableManageController.java, v 0.1 2022年09月16日 17:41 moji Exp $ - * @date 2022/09/16 - */ -@ConnectionInfoAspect -@RequestMapping("/api/rdb/ddl") -@RestController -@Slf4j -@Deprecated -public class RdbDdlController extends EmbeddingController { - - @Autowired - private TableService tableService; - - @Autowired - private DlTemplateService dlTemplateService; - - @Autowired - private RdbWebConverter rdbWebConverter; - - @Autowired - private DatabaseService databaseService; - - public static ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); - - /** - * 查询当前DB下的表列表 - * - * @param request - * @return - */ - @GetMapping("/list") - public WebPageResult list(@Valid TableBriefQueryRequest request) { - TablePageQueryParam queryParam = rdbWebConverter.tablePageRequest2param(request); - TableSelector tableSelector = new TableSelector(); - tableSelector.setColumnList(false); - tableSelector.setIndexList(false); - - PageResult
tableDTOPageResult = tableService.pageQuery(queryParam, tableSelector); - List tableVOS = rdbWebConverter.tableDto2vo(tableDTOPageResult.getData()); - -// ConnectInfo connectInfo = Chat2DBContext.getConnectInfo(); -// singleThreadExecutor.submit(() -> { -// try { -// Chat2DBContext.putContext(connectInfo); -// syncTableVector(request); -//// syncTableEs(request); -// } catch (Exception e) { -// log.error("sync table vector error", e); -// } finally { -// Chat2DBContext.removeContext(); -// } -// log.info("sync table vector finish"); -// }); - return WebPageResult.of(tableVOS, tableDTOPageResult.getTotal(), request.getPageNo(), - request.getPageSize()); - } - - /** - * 查询数据库里包含的schema_list - * - * @param request - * @return - */ - @GetMapping("/schema_list") - public ListResult schemaList(@Valid DataSourceBaseRequest request) { - SchemaQueryParam queryParam = SchemaQueryParam.builder().dataBaseName(request.getDatabaseName()).build(); - ListResult tableColumns = databaseService.querySchema(queryParam); - List tableVOS = rdbWebConverter.schemaDto2vo(tableColumns.getData()); - return ListResult.of(tableVOS); - } - - /** - * 查询数据库里包含的database_schema_list - * - * @param request - * @return - */ - @GetMapping("/database_schema_list") - public DataResult databaseSchemaList(@Valid DataSourceBaseRequest request) { - MetaDataQueryParam queryParam = MetaDataQueryParam.builder().dataSourceId(request.getDataSourceId()).refresh( - request.isRefresh()).build(); - DataResult result = databaseService.queryDatabaseSchema(queryParam); - MetaSchemaVO schemaDto2vo = rdbWebConverter.metaSchemaDto2vo(result.getData()); - return DataResult.of(schemaDto2vo); - } - - - /** - * 查询当前DB下的表columns - * d - * - * @param request - * @return - */ - @GetMapping("/column_list") - public ListResult columnList(@Valid TableDetailQueryRequest request) { - TableQueryParam queryParam = rdbWebConverter.tableRequest2param(request); - List tableColumns = tableService.queryColumns(queryParam); - List tableVOS = rdbWebConverter.columnDto2vo(tableColumns); - return ListResult.of(tableVOS); - } - - /** - * 查询当前DB下的表index - * - * @param request - * @return - */ - @GetMapping("/index_list") - public ListResult indexList(@Valid TableDetailQueryRequest request) { - TableQueryParam queryParam = rdbWebConverter.tableRequest2param(request); - List tableIndices = tableService.queryIndexes(queryParam); - List indexVOS = rdbWebConverter.indexDto2vo(tableIndices); - return ListResult.of(indexVOS); - } - - /** - * 查询当前DB下的表key - * - * @param request - * @return - */ - @GetMapping("/key_list") - public ListResult keyList(@Valid TableDetailQueryRequest request) { - // TODO 增加查询key实现 - return ListResult.of(Lists.newArrayList()); - } - - /** - * 导出建表语句 - * - * @param request - * @return - */ - @GetMapping("/export") - public DataResult export(@Valid DdlExportRequest request) { - ShowCreateTableParam param = rdbWebConverter.ddlExport2showCreate(request); - return tableService.showCreateTable(param); - } - - /** - * 建表语句样例 - * - * @param request - * @return - */ - @GetMapping("/create/example") - public DataResult createExample(@Valid TableCreateDdlQueryRequest request) { - return tableService.createTableExample(request.getDbType()); - } - - /** - * 更新表语句样例 - * - * @param request - * @return - */ - @GetMapping("/update/example") - public DataResult updateExample(@Valid TableUpdateDdlQueryRequest request) { - return tableService.alterTableExample(request.getDbType()); - } - - /** - * 获取表下列和索引等信息 - * - * @param request - * @return - */ - @GetMapping("/query") - public DataResult query(@Valid TableDetailQueryRequest request) { - TableQueryParam queryParam = rdbWebConverter.tableRequest2param(request); - TableSelector tableSelector = new TableSelector(); - tableSelector.setColumnList(true); - tableSelector.setIndexList(true); - DataResult
tableDTODataResult = tableService.query(queryParam, tableSelector); - TableVO tableVO = rdbWebConverter.tableDto2vo(tableDTODataResult.getData()); - return DataResult.of(tableVO); - } - - /** - * 获取修改表的sql语句 - * - * @param request - * @return - */ - @GetMapping("/modify/sql") - public ListResult modifySql(@Valid TableModifySqlRequest request) { - return tableService.buildSql( - rdbWebConverter.tableRequest2param(request.getOldTable()), - rdbWebConverter.tableRequest2param(request.getNewTable())) - .map(rdbWebConverter::dto2vo); - } - - /** - * 删除表 - * - * @param request - * @return - */ - @PostMapping("/delete") - public ActionResult delete(@Valid @RequestBody TableDeleteRequest request) { - DropParam dropParam = rdbWebConverter.tableDelete2dropParam(request); - return tableService.drop(dropParam); - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlController.java index 5b3962f3b..fd9189cfd 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlController.java @@ -2,39 +2,37 @@ import java.sql.Connection; import java.util.List; -import java.util.Objects; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import ai.chat2db.server.domain.api.model.Config; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.CollectionUtils; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + import ai.chat2db.server.domain.api.param.DlExecuteParam; import ai.chat2db.server.domain.api.param.OrderByParam; import ai.chat2db.server.domain.api.param.UpdateSelectResultParam; -import ai.chat2db.server.domain.api.service.ConfigService; import ai.chat2db.server.domain.api.service.DlTemplateService; +import ai.chat2db.server.domain.api.service.ForeignKeySyncService; +import ai.chat2db.server.domain.core.service.VirtualFkSuggestionService; import ai.chat2db.server.tools.base.enums.DataSourceTypeEnum; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; -import ai.chat2db.server.tools.common.util.ConfigUtils; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; -import ai.chat2db.server.web.api.controller.ai.chat2db.client.Chat2dbAIClient; import ai.chat2db.server.web.api.controller.rdb.converter.RdbWebConverter; -import ai.chat2db.server.web.api.controller.rdb.request.*; +import ai.chat2db.server.web.api.controller.rdb.request.DdlCountRequest; +import ai.chat2db.server.web.api.controller.rdb.request.DmlRequest; +import ai.chat2db.server.web.api.controller.rdb.request.DmlTableRequest; +import ai.chat2db.server.web.api.controller.rdb.request.OrderByRequest; +import ai.chat2db.server.web.api.controller.rdb.request.SelectResultUpdateRequest; import ai.chat2db.server.web.api.controller.rdb.vo.ExecuteResultVO; -import ai.chat2db.server.web.api.http.GatewayClientService; -import ai.chat2db.server.web.api.http.request.SqlExecuteHistoryCreateRequest; -import ai.chat2db.server.web.api.util.ApplicationContextUtil; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.model.ExecuteResult; +import ai.chat2db.spi.model.Table; +import ai.chat2db.spi.model.VirtualForeignKey; +import ai.chat2db.spi.model.VirtualForeignKeySuggestion; import ai.chat2db.spi.sql.Chat2DBContext; -import com.google.common.collect.Lists; -import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.util.CollectionUtils; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; /** * mysql数据运维类 @@ -55,39 +53,57 @@ public class RdbDmlController { private DlTemplateService dlTemplateService; @Autowired - private GatewayClientService gatewayClientService; + private ForeignKeySyncService foreignKeySyncService; + + @Autowired + private VirtualFkSuggestionService virtualFkSuggestionService; - public static ExecutorService executorService = Executors.newFixedThreadPool(10); /** - * 增删改查等数据运维 + * 执行SQL语句,返回所有执行结果 + * 适用于执行多条SQL语句,需要查看所有执行结果的场景 * - * @param request - * @return + * @param request SQL执行请求 + * @return 所有SQL语句的执行结果列表 */ @RequestMapping(value = "/execute", method = {RequestMethod.POST, RequestMethod.PUT}) public ListResult manage(@RequestBody DmlRequest request) { DlExecuteParam param = rdbWebConverter.request2param(request); - ListResult resultDTOListResult = dlTemplateService.execute(param); - List resultVOS = rdbWebConverter.dto2vo(resultDTOListResult.getData()); + List resultList = dlTemplateService.execute(param); + + // Add Virtual FK suggestions using cached JSqlParser AST + if (!resultList.isEmpty()) { + ExecuteResult firstResult = resultList.get(0); + if (canSuggestVirtualForeignKeys(firstResult)) { + List existingFKs = foreignKeySyncService.listAllForeignKeys( + request.getDataSourceId(), + request.getDatabaseName(), + request.getSchemaName(), + null + ).stream() + .filter(fk -> fk instanceof VirtualForeignKey) + .map(fk -> (VirtualForeignKey) fk) + .toList(); + + List suggestions = virtualFkSuggestionService.suggest(firstResult.getJsqlStatement(), existingFKs); + if (!suggestions.isEmpty()) { + firstResult.setVkSuggestions(suggestions); + } + } + } + List resultVOS = rdbWebConverter.dto2vo(resultList); return ListResult.of(resultVOS); } - - /** - * query chat2db apikey - * - * @return - */ - private String getClientId() { - ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); - Config keyConfig = configService.find(Chat2dbAIClient.CHAT2DB_OPENAI_KEY).getData(); - if (Objects.isNull(keyConfig) || StringUtils.isBlank(keyConfig.getContent())) { - return ConfigUtils.getClientId(); - } - return keyConfig.getContent(); + private boolean canSuggestVirtualForeignKeys(ExecuteResult result) { + return result != null + && Boolean.TRUE.equals(result.getSuccess()) + && result.getJsqlStatement() != null + && !CollectionUtils.isEmpty(result.getDataList()); } + + /** * 查询表结构信息 * @@ -99,15 +115,38 @@ public ListResult executeTable(@RequestBody DmlTableRequest req DlExecuteParam param = rdbWebConverter.request2param(request); // 解析sql String type = Chat2DBContext.getConnectInfo().getDbType(); - if (DataSourceTypeEnum.MONGODB.getCode().equals(type)) { + if (DataSourceTypeEnum.REDIS.getCode().equals(type)) { + MetaData metaData = Chat2DBContext.getMetaData(); + List
tables = metaData.tables(Chat2DBContext.getConnection(), param.getDatabaseName(), param.getSchemaName(), request.getTableName()); + for (Table table : tables) { + if ("string".equals(table.getType())) { + param.setSql("GET " + request.getTableName()); + } else if ("hash".equals(table.getType())) { + param.setSql("HGETALL " + request.getTableName()); + } else if ("list".equals(table.getType())) { + param.setSql("LRANGE " + request.getTableName() + " 0 -1"); + } else if ("set".equals(table.getType())){ + param.setSql("SMEMBERS " + request.getTableName()); + } else if ("zset".equals(table.getType())){ + param.setSql("ZRANGE " + request.getTableName() + " 0 -1"); + } else if ("stream".equals(table.getType())){ + param.setSql("XRANGE " + request.getTableName() + " 0 -1"); + } + } + } else if (DataSourceTypeEnum.MONGODB.getCode().equals(type)) { param.setSql("db." + request.getTableName() + ".find()"); + } else if (DataSourceTypeEnum.PHOENIX.getCode().equals(type)) { + MetaData metaData = Chat2DBContext.getMetaData(); + // 拼接`tableName`,避免关键字被占用问题 + param.setSql("select * from " + metaData.getMetaDataName(request.getSchemaName(),request.getTableName())); } else { MetaData metaData = Chat2DBContext.getMetaData(); // 拼接`tableName`,避免关键字被占用问题 param.setSql("select * from " + metaData.getMetaDataName(request.getTableName())); } - return dlTemplateService.execute(param) - .map(rdbWebConverter::dto2vo); + List resultList = dlTemplateService.execute(param); + List resultVOS = rdbWebConverter.dto2vo(resultList); + return ListResult.of(resultVOS); } /** @@ -119,11 +158,11 @@ public ListResult executeTable(@RequestBody DmlTableRequest req @RequestMapping(value = "/execute_update", method = {RequestMethod.POST, RequestMethod.PUT}) public DataResult executeSelectResultUpdate(@RequestBody DmlRequest request) { DlExecuteParam param = rdbWebConverter.request2param(request); - DataResult result = dlTemplateService.executeUpdate(param); - if (!result.success()) { - return DataResult.error(result.getErrorCode(), result.getErrorMessage()); + ExecuteResult result = dlTemplateService.executeUpdate(param); + if (result == null || Boolean.FALSE.equals(result.getSuccess())) { + return DataResult.error("EXECUTE_ERROR", result != null ? result.getMessage() : "Unknown error"); } - ExecuteResultVO executeResultVO = rdbWebConverter.dto2vo(result.getData()); + ExecuteResultVO executeResultVO = rdbWebConverter.dto2vo(result); return DataResult.of(executeResultVO); } @@ -131,7 +170,7 @@ public DataResult executeSelectResultUpdate(@RequestBody DmlReq @RequestMapping(value = "/get_update_sql", method = {RequestMethod.POST, RequestMethod.PUT}) public DataResult getUpdateSelectResultSql(@RequestBody SelectResultUpdateRequest request) { UpdateSelectResultParam param = rdbWebConverter.request2param(request); - return dlTemplateService.updateSelectResult(param); + return DataResult.of(dlTemplateService.updateSelectResult(param)); } @@ -140,14 +179,16 @@ public DataResult getOrderBySql(@RequestBody OrderByRequest request) { OrderByParam param = rdbWebConverter.request2param(request); - return dlTemplateService.getOrderBySql(param); + return DataResult.of(dlTemplateService.getOrderBySql(param)); } /** - * 增删改查等数据运维 + * 执行SQL语句,返回单个执行结果 + * 成功时返回第一个成功结果,失败时返回第一个失败结果 + * 适用于执行DDL语句或单条SQL语句的场景 * - * @param request - * @return + * @param request SQL执行请求 + * @return 单个执行结果(成功时返回第一个,失败时返回第一个失败) */ @RequestMapping(value = "/execute_ddl", method = {RequestMethod.POST, RequestMethod.PUT}) public DataResult executeDDL(@RequestBody DmlRequest request) { @@ -158,8 +199,8 @@ public DataResult executeDDL(@RequestBody DmlRequest request) { boolean flag = true; ExecuteResultVO executeResult = null; //connection.setAutoCommit(false); - ListResult resultDTOListResult = dlTemplateService.execute(param); - List resultVOS = rdbWebConverter.dto2vo(resultDTOListResult.getData()); + List resultList = dlTemplateService.execute(param); + List resultVOS = rdbWebConverter.dto2vo(resultList); if (!CollectionUtils.isEmpty(resultVOS)) { for (ExecuteResultVO resultVO : resultVOS) { if (!resultVO.getSuccess()) { @@ -194,7 +235,7 @@ public DataResult executeDDL(@RequestBody DmlRequest request) { */ @RequestMapping(value = "/count", method = {RequestMethod.POST, RequestMethod.PUT}) public DataResult count(@RequestBody DdlCountRequest request) { - return dlTemplateService.count(rdbWebConverter.request2param(request)); + return DataResult.of(dlTemplateService.count(rdbWebConverter.request2param(request))); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlExportController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlExportController.java deleted file mode 100644 index 73acdf4a9..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlExportController.java +++ /dev/null @@ -1,191 +0,0 @@ -package ai.chat2db.server.web.api.controller.rdb; - -import java.io.IOException; -import java.io.PrintWriter; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.time.LocalDateTime; -import java.util.List; - -import com.alibaba.druid.DbType; -import com.alibaba.druid.sql.SQLUtils; -import com.alibaba.druid.sql.SQLUtils.FormatOption; -import com.alibaba.druid.sql.ast.SQLStatement; -import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; -import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; -import com.alibaba.druid.sql.ast.statement.SQLInsertStatement; -import com.alibaba.druid.sql.ast.statement.SQLInsertStatement.ValuesClause; -import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; -import com.alibaba.druid.sql.visitor.VisitorFeature; -import com.alibaba.excel.EasyExcel; -import com.alibaba.excel.ExcelWriter; -import com.alibaba.excel.support.ExcelTypeEnum; -import com.alibaba.excel.write.builder.ExcelWriterBuilder; -import com.alibaba.excel.write.metadata.WriteSheet; - -import ai.chat2db.server.domain.api.enums.ExportSizeEnum; -import ai.chat2db.server.domain.api.enums.ExportTypeEnum; -import ai.chat2db.server.tools.base.excption.BusinessException; -import ai.chat2db.server.tools.common.exception.ParamBusinessException; -import ai.chat2db.server.tools.common.util.EasyCollectionUtils; -import ai.chat2db.server.tools.common.util.EasyEnumUtils; -import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; -import ai.chat2db.server.web.api.controller.rdb.request.DataExportRequest; -import ai.chat2db.spi.jdbc.DefaultValueHandler; -import ai.chat2db.spi.sql.Chat2DBContext; -import ai.chat2db.spi.sql.SQLExecutor; -import ai.chat2db.spi.util.JdbcUtils; -import ai.chat2db.spi.util.SqlUtils; -import cn.hutool.core.date.DatePattern; -import com.google.common.collect.Lists; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.validation.Valid; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; - -/** - * Export Database Exclusive - * - * @author Jiaju Zhuang - */ -@ConnectionInfoAspect -@RequestMapping("/api/rdb/dml") -@Controller -@Slf4j -public class RdbDmlExportController { - - /** - * Format insert statement - */ - private static final FormatOption INSERT_FORMAT_OPTION = new FormatOption(true, false); - - static { - INSERT_FORMAT_OPTION.config(VisitorFeature.OutputNameQuote, true); - } - - /** - * export data - * - * @param request - * @return - */ - @PostMapping("/export") - public void export(@Valid @RequestBody DataExportRequest request, HttpServletResponse response) throws IOException { - ExportSizeEnum exportSize = EasyEnumUtils.getEnum(ExportSizeEnum.class, request.getExportSize()); - ExportTypeEnum exportType = EasyEnumUtils.getEnum(ExportTypeEnum.class, request.getExportType()); - String sql; - if (exportSize == ExportSizeEnum.CURRENT_PAGE) { - sql = request.getSql(); - } else { - sql = request.getOriginalSql(); - } - if (StringUtils.isBlank(sql)) { - throw new ParamBusinessException("exportSize"); - } - DbType dbType = JdbcUtils.parse2DruidDbType(Chat2DBContext.getConnectInfo().getDbType()); - String tableName; - if (dbType != null) { - SQLStatement sqlStatement = SQLUtils.parseSingleStatement(sql, dbType); - if (!(sqlStatement instanceof SQLSelectStatement)) { - throw new BusinessException("dataSource.sqlAnalysisError"); - } - tableName = SqlUtils.getTableName(sql, dbType); - } else { - tableName = StringUtils.join(Lists.newArrayList(request.getDatabaseName(), request.getSchemaName()), "_"); - } - - response.setCharacterEncoding("utf-8"); - String fileName = URLEncoder.encode( - tableName + "_" + LocalDateTime.now().format(DatePattern.PURE_DATETIME_FORMATTER), - StandardCharsets.UTF_8) - .replaceAll("\\+", "%20"); - - if (exportType == ExportTypeEnum.CSV) { - doExportCsv(sql, response, fileName); - } else { - doExportInsert(sql, response, fileName, dbType, tableName); - } - } - - private void doExportCsv(String sql, HttpServletResponse response, String fileName) - throws IOException { - response.setContentType("text/csv"); - response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".csv"); - - ExcelWrapper excelWrapper = new ExcelWrapper(); - try { - ExcelWriterBuilder excelWriterBuilder = EasyExcel.write(response.getOutputStream()) - .charset(StandardCharsets.UTF_8) - .excelType(ExcelTypeEnum.CSV); - excelWrapper.setExcelWriterBuilder(excelWriterBuilder); - SQLExecutor.getInstance().execute(Chat2DBContext.getConnection(), sql, headerList -> { - excelWriterBuilder.head( - EasyCollectionUtils.toList(headerList, header -> Lists.newArrayList(header.getName()))); - excelWrapper.setExcelWriter(excelWriterBuilder.build()); - excelWrapper.setWriteSheet(EasyExcel.writerSheet(0).build()); - }, dataList -> { - List> writeDataList = Lists.newArrayList(); - writeDataList.add(dataList); - excelWrapper.getExcelWriter().write(writeDataList, excelWrapper.getWriteSheet()); - }, false, new DefaultValueHandler()); - } finally { - if (excelWrapper.getExcelWriter() != null) { - excelWrapper.getExcelWriter().finish(); - } - } - } - - private void doExportInsert(String sql, HttpServletResponse response, String fileName, DbType dbType, - String tableName) - throws IOException { - response.setContentType("text/sql"); - response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".sql"); - - try (PrintWriter printWriter = response.getWriter()) { - InsertWrapper insertWrapper = new InsertWrapper(); - SQLExecutor.getInstance().execute(Chat2DBContext.getConnection(), sql, - headerList -> insertWrapper.setHeaderList( - EasyCollectionUtils.toList(headerList, header -> new SQLIdentifierExpr(header.getName()))) - , dataList -> { - SQLInsertStatement sqlInsertStatement = new SQLInsertStatement(); - sqlInsertStatement.setDbType(dbType); - sqlInsertStatement.setTableSource(new SQLExprTableSource(tableName)); - sqlInsertStatement.getColumns().addAll(insertWrapper.getHeaderList()); - ValuesClause valuesClause = new ValuesClause(); - for (String s : dataList) { - valuesClause.addValue(s); - } - sqlInsertStatement.setValues(valuesClause); - - printWriter.println(SQLUtils.toSQLString(sqlInsertStatement, dbType, INSERT_FORMAT_OPTION) + ";"); - }, false, new DefaultValueHandler()); - } - } - - @Data - @SuperBuilder - @NoArgsConstructor - @AllArgsConstructor - public static class InsertWrapper { - private List headerList; - } - - @Data - @SuperBuilder - @NoArgsConstructor - @AllArgsConstructor - public static class ExcelWrapper { - private ExcelWriterBuilder excelWriterBuilder; - private ExcelWriter excelWriter; - private WriteSheet writeSheet; - } - -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDocController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDocController.java deleted file mode 100644 index 0ea76219b..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDocController.java +++ /dev/null @@ -1,91 +0,0 @@ -package ai.chat2db.server.web.api.controller.rdb; - -import ai.chat2db.server.domain.api.enums.ExportTypeEnum; -import ai.chat2db.server.domain.api.param.TablePageQueryParam; -import ai.chat2db.server.domain.api.param.TableQueryParam; -import ai.chat2db.server.domain.api.param.TableSelector; -import ai.chat2db.server.domain.api.service.TableService; -import ai.chat2db.server.tools.base.wrapper.result.PageResult; -import ai.chat2db.server.tools.common.util.EasyEnumUtils; -import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; -import ai.chat2db.server.web.api.controller.rdb.converter.RdbWebConverter; -import ai.chat2db.server.web.api.controller.rdb.doc.DatabaseExportService; -import ai.chat2db.server.web.api.controller.rdb.doc.conf.ExportOptions; -import ai.chat2db.server.web.api.controller.rdb.doc.event.TemplateEvent; -import ai.chat2db.server.web.api.controller.rdb.factory.ExportServiceFactory; -import ai.chat2db.server.web.api.controller.rdb.request.DataExportRequest; -import ai.chat2db.server.web.api.controller.rdb.vo.TableVO; -import ai.chat2db.spi.model.Table; -import cn.hutool.core.date.DatePattern; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.validation.Valid; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; - -import java.lang.reflect.Constructor; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.time.LocalDateTime; -import java.util.List; - -/** - * RdbDocController - * - * @author lzy - **/ -@ConnectionInfoAspect -@RequestMapping("/api/rdb/doc") -@Controller -@Slf4j -public class RdbDocController { - - @Autowired - private TableService tableService; - - @Autowired - private RdbWebConverter rdbWebConverter; - - - /** - * export data - * - * @param request - */ - @PostMapping("/export") - public void export(@Valid @RequestBody DataExportRequest request, HttpServletResponse response) throws Exception { - //复制模板 - ExportTypeEnum exportType = EasyEnumUtils.getEnum(ExportTypeEnum.class, request.getExportType()); - response.setCharacterEncoding("utf-8"); - String fileName = URLEncoder.encode( - request.getDatabaseName() + "_" + LocalDateTime.now().format(DatePattern.PURE_DATETIME_FORMATTER), - StandardCharsets.UTF_8) - .replaceAll("\\+", "%20"); - TablePageQueryParam queryParam = rdbWebConverter.tablePageRequest2param(request); - queryParam.setPageNo(1); - queryParam.setPageSize(Integer.MAX_VALUE); - TableSelector tableSelector = new TableSelector(); - tableSelector.setColumnList(true); - tableSelector.setIndexList(true); - PageResult
tableDTOPageResult = tableService.pageQuery(queryParam, tableSelector); - List tableVOS = rdbWebConverter.tableDto2vo(tableDTOPageResult.getData()); - TableQueryParam param = rdbWebConverter.tableRequest2param(request); - for (TableVO tableVO: tableVOS) { - param.setTableName(tableVO.getName()); - tableVO.setColumnList(tableService.queryColumns(param)); - tableVO.setIndexList(tableService.queryIndexes(param)); - } - Class targetClass = ExportServiceFactory.get(exportType.getCode()); - Constructor constructor = targetClass.getDeclaredConstructor(); - DatabaseExportService databaseExportService = (DatabaseExportService) constructor.newInstance(); - response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + databaseExportService.getSuffix()); - response.setContentType(databaseExportService.getContentType()); - // 设置数据集合 - databaseExportService.setExportList(tableVOS); - databaseExportService.generate(request.getDatabaseName(), response.getOutputStream(), new ExportOptions()); - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/SchemaController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/SchemaController.java index 156fdb96a..81044e2fd 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/SchemaController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/SchemaController.java @@ -56,8 +56,8 @@ public class SchemaController { public ListResult list(@Valid DataSourceBaseRequest request) { SchemaQueryParam queryParam = SchemaQueryParam.builder().dataSourceId(request.getDataSourceId()).dataBaseName( request.getDatabaseName()).refresh(request.isRefresh()).build(); - ListResult tableColumns = databaseService.querySchema(queryParam); - List tableVOS = rdbWebConverter.schemaDto2vo(tableColumns.getData()); + List schemas = databaseService.querySchema(queryParam); + List tableVOS = rdbWebConverter.schemaDto2vo(schemas); return ListResult.of(tableVOS); } @@ -71,7 +71,8 @@ public ListResult list(@Valid DataSourceBaseRequest request) { public ActionResult deleteSchema(@Valid @RequestBody DataSourceBaseRequest request) { SchemaOperationParam param = SchemaOperationParam.builder().databaseName(request.getDatabaseName()) .schemaName(request.getSchemaName()).build(); - return databaseService.deleteSchema(param); + databaseService.deleteSchema(param); + return ActionResult.isSuccess(); } /** @@ -87,7 +88,7 @@ public DataResult createSchema(@Valid @RequestBody SchemaCreateRequest requ .owner(request.getOwner()) .comment(request.getComment()) .build(); - return databaseService.createSchema(schema); + return DataResult.of(databaseService.createSchema(schema)); } /** @@ -100,6 +101,7 @@ public DataResult createSchema(@Valid @RequestBody SchemaCreateRequest requ public ActionResult modifySchema(@Valid @RequestBody UpdateSchemaRequest request) { SchemaOperationParam param = SchemaOperationParam.builder().databaseName(request.getDatabaseName()) .schemaName(request.getSchemaName()).newSchemaName(request.getNewSchemaName()).build(); - return databaseService.modifySchema(param); + databaseService.modifySchema(param); + return ActionResult.isSuccess(); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/SchemaDiffController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/SchemaDiffController.java new file mode 100644 index 000000000..f6e160e7c --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/SchemaDiffController.java @@ -0,0 +1,56 @@ +package ai.chat2db.server.web.api.controller.rdb; + +import ai.chat2db.server.domain.api.model.schemaDiff.SchemaDiffResult; +import ai.chat2db.server.domain.api.param.schemaDiff.MigrateResult; +import ai.chat2db.server.domain.api.param.schemaDiff.SchemaCompareParam; +import ai.chat2db.server.domain.api.param.schemaDiff.SchemaMigrateParam; +import ai.chat2db.server.domain.api.service.SchemaDiffService; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; +import ai.chat2db.server.web.api.controller.rdb.request.SchemaCompareRequest; +import ai.chat2db.server.web.api.controller.rdb.request.SchemaMigrateRequest; +import jakarta.validation.Valid; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/rdb/schema/diff") +public class SchemaDiffController { + + @Autowired + private SchemaDiffService schemaDiffService; + + @PostMapping("/compare") + public DataResult compare(@Valid @RequestBody SchemaCompareRequest request) { + SchemaCompareParam param = SchemaCompareParam.builder() + .sourceDataSourceId(request.getSourceDataSourceId()) + .sourceDatabaseName(request.getSourceDatabaseName()) + .sourceSchemaName(request.getSourceSchemaName()) + .targetDataSourceId(request.getTargetDataSourceId()) + .targetDatabaseName(request.getTargetDatabaseName()) + .targetSchemaName(request.getTargetSchemaName()) + .tableNames(request.getTableNames()) + .compareOption(request.getCompareOption() != null + ? request.getCompareOption() + : new ai.chat2db.server.domain.api.param.schemaDiff.CompareOption()) + .build(); + SchemaDiffResult result = schemaDiffService.compare(param); + return DataResult.of(result); + } + + @PostMapping("/migrate") + public DataResult migrate(@Valid @RequestBody SchemaMigrateRequest request) { + SchemaMigrateParam param = SchemaMigrateParam.builder() + .targetDataSourceId(request.getTargetDataSourceId()) + .targetDatabaseName(request.getTargetDatabaseName()) + .targetSchemaName(request.getTargetSchemaName()) + .ddlStatements(request.getDdlStatements()) + .executeInTransaction(request.isExecuteInTransaction()) + .continueOnError(request.isContinueOnError()) + .build(); + MigrateResult result = schemaDiffService.migrate(param); + return DataResult.of(result); + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java index 131a6bf6c..a21e424e4 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java @@ -1,55 +1,68 @@ package ai.chat2db.server.web.api.controller.rdb; -import ai.chat2db.server.domain.api.param.*; -import ai.chat2db.server.domain.api.service.DatabaseService; -import ai.chat2db.server.domain.api.service.DlTemplateService; +import java.util.List; +import java.util.stream.Collectors; + +import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import ai.chat2db.server.domain.api.param.DeprecatedTableParam; +import ai.chat2db.server.domain.api.param.DropParam; +import ai.chat2db.server.domain.api.param.ShowCreateTableParam; +import ai.chat2db.server.domain.api.param.TablePageQueryParam; +import ai.chat2db.server.domain.api.param.TableQueryParam; +import ai.chat2db.server.domain.api.param.TableSelector; +import ai.chat2db.server.domain.api.param.TypeQueryParam; import ai.chat2db.server.domain.api.service.TableService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; -import ai.chat2db.server.tools.base.wrapper.result.PageResult; -import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; -import ai.chat2db.server.web.api.controller.ai.EmbeddingController; import ai.chat2db.server.web.api.controller.rdb.converter.RdbWebConverter; -import ai.chat2db.server.web.api.controller.rdb.request.*; +import ai.chat2db.server.web.api.controller.rdb.request.BatchTableModifySqlRequest; +import ai.chat2db.server.web.api.controller.rdb.request.BatchTableOperationRequest; +import ai.chat2db.server.web.api.controller.rdb.request.DdlExportRequest; +import ai.chat2db.server.web.api.controller.rdb.request.DeprecatedTableRequest; +import ai.chat2db.server.web.api.controller.rdb.request.TableBriefQueryRequest; +import ai.chat2db.server.web.api.controller.rdb.request.TableCreateDdlQueryRequest; +import ai.chat2db.server.web.api.controller.rdb.request.TableDeleteRequest; +import ai.chat2db.server.web.api.controller.rdb.request.TableDetailQueryRequest; +import ai.chat2db.server.web.api.controller.rdb.request.TableModifySqlRequest; +import ai.chat2db.server.web.api.controller.rdb.request.TableUpdateDdlQueryRequest; +import ai.chat2db.server.web.api.controller.rdb.request.TypeQueryRequest; import ai.chat2db.server.web.api.controller.rdb.vo.ColumnVO; +import ai.chat2db.server.web.api.controller.rdb.vo.ExecuteResultVO; import ai.chat2db.server.web.api.controller.rdb.vo.IndexVO; import ai.chat2db.server.web.api.controller.rdb.vo.SqlVO; import ai.chat2db.server.web.api.controller.rdb.vo.TableVO; -import ai.chat2db.spi.model.*; -import ai.chat2db.spi.sql.Chat2DBContext; -import ai.chat2db.spi.sql.ConnectInfo; -import com.google.common.collect.Lists; +import ai.chat2db.spi.model.SimpleTable; +import ai.chat2db.spi.model.Sql; +import ai.chat2db.spi.model.Table; +import ai.chat2db.spi.model.TableColumn; +import ai.chat2db.spi.model.TableIndex; +import ai.chat2db.spi.model.TableMeta; +import ai.chat2db.spi.model.Type; +import ai.chat2db.spi.model.ExecuteResult; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.*; - -import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; @Slf4j @ConnectionInfoAspect @RequestMapping("/api/rdb/table") @RestController -public class TableController extends EmbeddingController { +public class TableController { @Autowired private TableService tableService; - @Autowired - private DlTemplateService dlTemplateService; - @Autowired private RdbWebConverter rdbWebConverter; - @Autowired - private DatabaseService databaseService; - - public static ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); - /** * 查询当前DB下的表列表 * @@ -62,23 +75,9 @@ public WebPageResult list(@Valid TableBriefQueryRequest request) { TableSelector tableSelector = new TableSelector(); tableSelector.setColumnList(false); tableSelector.setIndexList(false); - PageResult
tableDTOPageResult = tableService.pageQuery(queryParam, tableSelector); - List tableVOS = rdbWebConverter.tableDto2vo(tableDTOPageResult.getData()); -// ConnectInfo connectInfo = Chat2DBContext.getConnectInfo(); -// singleThreadExecutor.submit(() -> { -// try { -// Chat2DBContext.putContext(connectInfo); -// syncTableVector(request); -//// syncTableEs(request); -// } catch (Exception e) { -// log.error("sync table vector error", e); -// } finally { -// Chat2DBContext.removeContext(); -// } -// log.info("sync table vector finish"); -// }); - return WebPageResult.of(tableVOS, tableDTOPageResult.getTotal(), request.getPageNo(), - request.getPageSize()); + List
tables = tableService.pageQuery(queryParam, tableSelector); + List tableVOS = rdbWebConverter.tableDto2vo(tables); + return WebPageResult.of(tableVOS, queryParam.getLastDocId()); } /** @@ -90,14 +89,11 @@ public WebPageResult list(@Valid TableBriefQueryRequest request) { @GetMapping("/table_list") public ListResult tableList(@Valid TableBriefQueryRequest request) { TablePageQueryParam queryParam = rdbWebConverter.tablePageRequest2param(request); - return tableService.queryTables(queryParam); - + List tables = tableService.queryTables(queryParam); + return ListResult.of(tables); } - - - /** * 查询当前DB下的表columns * d @@ -113,6 +109,7 @@ public ListResult columnList(@Valid TableDetailQueryRequest request) { return ListResult.of(tableVOS); } + /** * 查询当前DB下的表index * @@ -127,18 +124,6 @@ public ListResult indexList(@Valid TableDetailQueryRequest request) { return ListResult.of(indexVOS); } - /** - * 查询当前DB下的表key - * - * @param request - * @return - */ - @GetMapping("/key_list") - public ListResult keyList(@Valid TableDetailQueryRequest request) { - // TODO 增加查询key实现 - return ListResult.of(Lists.newArrayList()); - } - /** * 导出建表语句 * @@ -148,7 +133,8 @@ public ListResult keyList(@Valid TableDetailQueryRequest request) { @GetMapping("/export") public DataResult export(@Valid DdlExportRequest request) { ShowCreateTableParam param = rdbWebConverter.ddlExport2showCreate(request); - return tableService.showCreateTable(param); + String ddl = tableService.showCreateTable(param); + return DataResult.of(ddl); } /** @@ -159,7 +145,8 @@ public DataResult export(@Valid DdlExportRequest request) { */ @GetMapping("/create/example") public DataResult createExample(@Valid TableCreateDdlQueryRequest request) { - return tableService.createTableExample(request.getDbType()); + String sql = tableService.createTableExample(request.getDbType()); + return DataResult.of(sql); } /** @@ -170,7 +157,8 @@ public DataResult createExample(@Valid TableCreateDdlQueryRequest reques */ @GetMapping("/update/example") public DataResult updateExample(@Valid TableUpdateDdlQueryRequest request) { - return tableService.alterTableExample(request.getDbType()); + String sql = tableService.alterTableExample(request.getDbType()); + return DataResult.of(sql); } /** @@ -185,9 +173,8 @@ public DataResult
query(@Valid TableDetailQueryRequest request) { TableSelector tableSelector = new TableSelector(); tableSelector.setColumnList(true); tableSelector.setIndexList(true); - return tableService.query(queryParam, tableSelector); - //TableVO tableVO = rdbWebConverter.tableDto2vo(tableDTODataResult.getData()); - //return DataResult.of(tableVO); + Table table = tableService.query(queryParam, tableSelector); + return DataResult.of(table); } /** @@ -198,7 +185,7 @@ public DataResult
query(@Valid TableDetailQueryRequest request) { */ @PostMapping("/modify/sql") public ListResult modifySql(@Valid @RequestBody TableModifySqlRequest request) { - Table table = rdbWebConverter.tableRequest2param(request.getNewTable()); + Table table = rdbWebConverter.tableRequest2param(request.getNewTable()); table.setSchemaName(request.getSchemaName()); table.setDatabaseName(request.getDatabaseName()); for (TableColumn tableColumn : table.getColumnList()) { @@ -211,12 +198,22 @@ public ListResult modifySql(@Valid @RequestBody TableModifySqlRequest req tableIndex.setTableName(table.getName()); tableIndex.setDatabaseName(request.getDatabaseName()); } - - return tableService.buildSql(rdbWebConverter.tableRequest2param(request.getOldTable()),table) - .map(rdbWebConverter::dto2vo); + List sqls = tableService.buildSql(rdbWebConverter.tableRequest2param(request.getOldTable()), table); + List sqlVOS = sqls.stream().map(sql -> rdbWebConverter.dto2vo(sql)).collect(Collectors.toList()); + return ListResult.of(sqlVOS); } - + /** + * 批量获取修改表的sql语句 + * + * @param request + * @return + */ + @PostMapping("/batch/modify/sql") + public ListResult batchModifySql(@Valid @RequestBody BatchTableModifySqlRequest request) { + List sqls = tableService.buildBatchSql(request.getOldTables(), request.getNewTables()); + return ListResult.of(sqls); + } /** * 数据库支持的数据类型 @@ -248,6 +245,115 @@ public DataResult tableMeta(@Valid TypeQueryRequest request) { @PostMapping("/delete") public ActionResult delete(@Valid @RequestBody TableDeleteRequest request) { DropParam dropParam = rdbWebConverter.tableDelete2dropParam(request); - return tableService.drop(dropParam); + tableService.drop(dropParam); + return ActionResult.isSuccess(); + } + + /** + * 截断表 + * + * @param request + * @return + */ + @PostMapping("/truncate") + public ActionResult truncate(@Valid @RequestBody TableDeleteRequest request) { + DropParam truncateParam = rdbWebConverter.tableDelete2dropParam(request); + tableService.truncate(truncateParam); + return ActionResult.isSuccess(); + } + + /** + * 废弃表 + * + * @param request + * @return + */ + @PostMapping("/deprecated") + public ActionResult deprecated(@Valid @RequestBody DeprecatedTableRequest request) { + DeprecatedTableParam param = rdbWebConverter.deprecatedTableRequest2param(request); + tableService.deprecatedTable(param); + return ActionResult.isSuccess(); + } + + /** + * 取消废弃表 + * + * @param request + * @return + */ + @PostMapping("/cancel_deprecated") + public ActionResult cancelDeprecated(@Valid @RequestBody DeprecatedTableRequest request) { + DeprecatedTableParam param = rdbWebConverter.deprecatedTableRequest2param(request); + tableService.deleteDeprecatedTable(param); + return ActionResult.isSuccess(); } + + /** + * 查询回收站中的废弃表列表 + * + * @param request + * @return + */ + @GetMapping("/deprecated_list") + public ListResult deprecatedList(@Valid TableBriefQueryRequest request) { + TablePageQueryParam queryParam = rdbWebConverter.tablePageRequest2param(request); + TableSelector tableSelector = new TableSelector(); + tableSelector.setColumnList(false); + tableSelector.setIndexList(false); + + List
tables = tableService.pageQueryDeprecated(queryParam, tableSelector); + List tableVOS = rdbWebConverter.tableDto2vo(tables); + + return ListResult.of(tableVOS); + } + + /** + * 批量优化表 + * + * @param request + * @return + */ + @PostMapping("/batch/optimize") + public ListResult batchOptimize(@Valid @RequestBody BatchTableOperationRequest request) { + List results = tableService.batchOptimizeTables( + request.getTableNames(), request.getDatabaseName(), request.getSchemaName()); + List voList = results.stream().map(this::executeResult2vo).collect(Collectors.toList()); + return ListResult.of(voList); + } + + /** + * 批量分析表 + * + * @param request + * @return + */ + @PostMapping("/batch/analyze") + public ListResult batchAnalyze(@Valid @RequestBody BatchTableOperationRequest request) { + List results = tableService.batchAnalyzeTables( + request.getTableNames(), request.getDatabaseName(), request.getSchemaName()); + List voList = results.stream().map(this::executeResult2vo).collect(Collectors.toList()); + return ListResult.of(voList); + } + + private ExecuteResultVO executeResult2vo(ExecuteResult result) { + ExecuteResultVO vo = new ExecuteResultVO(); + vo.setSql(result.getSql()); + vo.setOriginalSql(result.getOriginalSql()); + vo.setDescription(result.getDescription()); + vo.setMessage(result.getMessage()); + vo.setSuccess(result.getSuccess()); + vo.setUpdateCount(result.getUpdateCount()); + vo.setHeaderList(result.getHeaderList()); + vo.setDataList(result.getDataList()); + vo.setSqlType(result.getSqlType()); + vo.setHasNextPage(result.getHasNextPage()); + vo.setPageNo(result.getPageNo()); + vo.setPageSize(result.getPageSize()); + vo.setFuzzyTotal(result.getFuzzyTotal()); + vo.setDuration(result.getDuration()); + vo.setTableName(result.getTableName()); + vo.setVkSuggestions(result.getVkSuggestions()); + return vo; + } + } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TreeController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TreeController.java new file mode 100644 index 000000000..f365a2031 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TreeController.java @@ -0,0 +1,114 @@ +package ai.chat2db.server.web.api.controller.rdb; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import ai.chat2db.server.domain.api.model.TreeNode; +import ai.chat2db.server.domain.api.param.TreeSearchParam; +import ai.chat2db.server.domain.api.service.FunctionService; +import ai.chat2db.server.domain.api.service.ProcedureService; +import ai.chat2db.server.domain.api.service.TableService; +import ai.chat2db.server.domain.api.service.TriggerService; +import ai.chat2db.server.domain.api.service.ViewService; +import ai.chat2db.server.tools.base.wrapper.result.ListResult; +import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; +import ai.chat2db.server.web.api.controller.rdb.request.TreeSearchRequest; +import ai.chat2db.server.web.api.controller.rdb.vo.TreeNodeVO; +import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@ConnectionInfoAspect +@RequestMapping("/api/rdb/tree") +@RestController +public class TreeController { + + @Autowired + private TableService tableService; + + @Autowired + private ViewService viewService; + + @Autowired + private FunctionService functionService; + + @Autowired + private ProcedureService procedureService; + + @Autowired + private TriggerService triggerService; + + @GetMapping("/search") + public ListResult search(@Valid TreeSearchRequest request) { + TreeSearchParam param = toParam(request); + List result = new ArrayList<>(); + + String type = request.getTreeNodeType(); + if (StringUtils.isBlank(type) || "all".equalsIgnoreCase(type)) { + result.addAll(tableService.searchTreeNodes(param)); + result.addAll(viewService.searchTreeNodes(param)); + result.addAll(functionService.searchTreeNodes(param)); + result.addAll(procedureService.searchTreeNodes(param)); + result.addAll(triggerService.searchTreeNodes(param)); + } else { + String lowerType = type.toLowerCase(); + switch (lowerType) { + case "table": + result.addAll(tableService.searchTreeNodes(param)); + break; + case "view": + result.addAll(viewService.searchTreeNodes(param)); + break; + case "function": + result.addAll(functionService.searchTreeNodes(param)); + break; + case "procedure": + result.addAll(procedureService.searchTreeNodes(param)); + break; + case "trigger": + result.addAll(triggerService.searchTreeNodes(param)); + break; + default: + log.warn("Unknown tree node type: {}", type); + } + } + + return ListResult.of(toVOList(result)); + } + + private TreeSearchParam toParam(TreeSearchRequest request) { + return TreeSearchParam.builder() + .dataSourceId(request.getDataSourceId()) + .databaseName(request.getDatabaseName()) + .schemaName(request.getSchemaName()) + .searchKey(request.getSearchKey()) + .treeNodeType(request.getTreeNodeType()) + .refresh(request.isRefresh()) + .build(); + } + + private List toVOList(List nodes) { + List voList = new ArrayList<>(); + for (TreeNode node : nodes) { + TreeNodeVO vo = new TreeNodeVO(); + vo.setUuid(node.getUuid()); + vo.setKey(node.getKey()); + vo.setName(node.getName()); + vo.setTreeNodeType(node.getTreeNodeType()); + vo.setPretendNodeType(node.getPretendNodeType()); + vo.setComment(node.getComment()); + vo.setIsLeaf(node.getIsLeaf()); + vo.setPinned(node.getPinned()); + vo.setParentPath(node.getParentPath()); + vo.setExtraParams(node.getExtraParams()); + voList.add(vo); + } + return voList; + } +} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TriggerController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TriggerController.java index 9ba1d3063..99722f554 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TriggerController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TriggerController.java @@ -15,6 +15,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import java.util.List; + @ConnectionInfoAspect @RequestMapping("/api/rdb/trigger") @RestController @@ -25,14 +27,15 @@ public class TriggerController { @GetMapping("/list") public WebPageResult list(@Valid TriggerPageRequest request) { - ListResult listResult = triggerService.triggers(request.getDatabaseName(), request.getSchemaName()); - Long total = CollectionUtils.isNotEmpty(listResult.getData()) ? Long.valueOf(listResult.getData().size()) : 0L; - Integer pageSize = listResult.getData() != null ? listResult.getData().size() : 0; - return WebPageResult.of(listResult.getData(), total, 1, pageSize); + List list = triggerService.triggersWithCache(request.getDataSourceId(), + request.getDatabaseName(), request.getSchemaName(), request.getSearchKey(), request.isRefresh()); + Long total = CollectionUtils.isNotEmpty(list) ? Long.valueOf(list.size()) : 0L; + Integer pageSize = list != null ? list.size() : 0; + return WebPageResult.of(list, total, 1, pageSize); } @GetMapping("/detail") public DataResult detail(@Valid TriggerDetailRequest request) { - return triggerService.detail(request.getDatabaseName(), request.getSchemaName(), request.getTriggerName()); + return DataResult.of(triggerService.detail(request.getDatabaseName(), request.getSchemaName(), request.getTriggerName())); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/ViewController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/ViewController.java index 3475f33bc..7885f331b 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/ViewController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/ViewController.java @@ -41,9 +41,10 @@ public class ViewController { @GetMapping("/list") public WebPageResult list(@Valid TableBriefQueryRequest request) { - ListResult
tableDTOPageResult = viewService.views(request.getDatabaseName(), request.getSchemaName()); - List tableVOS = rdbWebConverter.tableDto2vo(tableDTOPageResult.getData()); - Integer pageSize = tableDTOPageResult.getData() != null ? tableDTOPageResult.getData().size() : 0; + List
tableList = viewService.viewsWithCache(request.getDataSourceId(), + request.getDatabaseName(), request.getSchemaName(), request.getSearchKey(), request.isRefresh()); + List tableVOS = rdbWebConverter.tableDto2vo(tableList); + Integer pageSize = tableList != null ? tableList.size() : 0; return WebPageResult.of(tableVOS, Long.valueOf(tableVOS.size()), 1, pageSize); } @@ -59,14 +60,15 @@ public ListResult columnList(@Valid TableDetailQueryRequest request) { @GetMapping("/detail") public DataResult detail(@Valid TableDetailQueryRequest request) { - DataResult
dataResult = viewService.detail(request.getDatabaseName(),request.getSchemaName(),request.getTableName()); - TableVO tableVO = rdbWebConverter.tableDto2vo(dataResult.getData()); + Table dataResult = viewService.detail(request.getDatabaseName(),request.getSchemaName(),request.getTableName()); + TableVO tableVO = rdbWebConverter.tableDto2vo(dataResult); return DataResult.of(tableVO); } @PostMapping("/delete") public ActionResult delete(@Valid TableDeleteRequest request) { DropParam dropParam = rdbWebConverter.tableDelete2dropParam(request); - return tableService.drop(dropParam); + tableService.drop(dropParam); + return ActionResult.isSuccess(); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/converter/DataGenerationConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/converter/DataGenerationConverter.java new file mode 100644 index 000000000..b195aa523 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/converter/DataGenerationConverter.java @@ -0,0 +1,50 @@ +package ai.chat2db.server.web.api.controller.rdb.converter; + +import ai.chat2db.server.domain.api.param.ColumnConfigParam; +import ai.chat2db.server.domain.api.param.DataGenerationRequest; +import ai.chat2db.server.web.api.controller.rdb.request.DataGenerationRequestVO; +import org.springframework.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.List; + +public class DataGenerationConverter { + + public static DataGenerationRequest voToRequest(DataGenerationRequestVO vo) { + if (vo == null) { + return null; + } + + DataGenerationRequest request = new DataGenerationRequest(); + request.setDataSourceId(vo.getDataSourceId()); + request.setDatabaseName(vo.getDatabaseName()); + request.setSchemaName(vo.getSchemaName()); + request.setTableName(vo.getTableName()); + request.setRowCount(vo.getRowCount()); + request.setBatchSize(vo.getBatchSize()); + request.setPreviewMode(vo.getPreviewMode()); + + if (!CollectionUtils.isEmpty(vo.getColumnConfigs())) { + List columnConfigs = new ArrayList<>(); + for (DataGenerationRequestVO.ColumnConfigVO voConfig : vo.getColumnConfigs()) { + ColumnConfigParam param = new ColumnConfigParam(); + param.setColumnName(voConfig.getColumnName()); + param.setDataType(voConfig.getDataType()); + param.setExpression(voConfig.getExpression()); + param.setComment(voConfig.getComment()); + param.setNullable(voConfig.getNullable()); + param.setAutoIncrement(voConfig.getAutoIncrement()); + param.setMaxLength(voConfig.getMaxLength()); + param.setScale(voConfig.getScale()); + param.setForeignKey(voConfig.getForeignKey()); + param.setForeignKeySourceType(voConfig.getForeignKeySourceType()); + param.setReferencedTable(voConfig.getReferencedTable()); + param.setReferencedColumnName(voConfig.getReferencedColumnName()); + columnConfigs.add(param); + } + request.setColumnConfigs(columnConfigs); + } + + return request; + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/converter/RdbWebConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/converter/RdbWebConverter.java index b04663dc9..67803d61b 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/converter/RdbWebConverter.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/converter/RdbWebConverter.java @@ -2,9 +2,43 @@ import java.util.List; -import ai.chat2db.server.domain.api.param.*; +import org.apache.commons.lang3.StringUtils; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; + +import ai.chat2db.server.domain.api.param.DlCountParam; +import ai.chat2db.server.domain.api.param.DeprecatedTableParam; +import ai.chat2db.server.domain.api.param.DlExecuteParam; +import ai.chat2db.server.domain.api.param.DropKeyParam; +import ai.chat2db.server.domain.api.param.DropParam; +import ai.chat2db.server.domain.api.param.OrderByParam; +import ai.chat2db.server.domain.api.param.SchemaQueryParam; +import ai.chat2db.server.domain.api.param.ShowCreateTableParam; +import ai.chat2db.server.domain.api.param.TablePageQueryParam; +import ai.chat2db.server.domain.api.param.TableQueryParam; +import ai.chat2db.server.domain.api.param.TableVectorParam; +import ai.chat2db.server.domain.api.param.UpdateSelectResultParam; +import ai.chat2db.server.web.api.controller.ai.request.ChatQueryRequest; import ai.chat2db.server.web.api.controller.data.source.vo.DatabaseVO; -import ai.chat2db.server.web.api.controller.rdb.request.*; +import ai.chat2db.server.web.api.controller.rdb.request.DataExportRequest; +import ai.chat2db.server.web.api.controller.rdb.request.ColumnRequest; +import ai.chat2db.server.web.api.controller.rdb.request.DataExportRequest; +import ai.chat2db.server.web.api.controller.rdb.request.DatabaseCreateRequest; +import ai.chat2db.server.web.api.controller.rdb.request.DdlCountRequest; +import ai.chat2db.server.web.api.controller.rdb.request.DdlExportRequest; +import ai.chat2db.server.web.api.controller.rdb.request.DdlRequest; +import ai.chat2db.server.web.api.controller.rdb.request.DeprecatedTableRequest; +import ai.chat2db.server.web.api.controller.rdb.request.DmlRequest; +import ai.chat2db.server.web.api.controller.rdb.request.DmlTableRequest; +import ai.chat2db.server.web.api.controller.rdb.request.KeyDeleteRequest; +import ai.chat2db.server.web.api.controller.rdb.request.OrderByRequest; +import ai.chat2db.server.web.api.controller.rdb.request.SelectResultUpdateRequest; +import ai.chat2db.server.web.api.controller.rdb.request.TableBriefQueryRequest; +import ai.chat2db.server.web.api.controller.rdb.request.TableDeleteRequest; +import ai.chat2db.server.web.api.controller.rdb.request.TableDetailQueryRequest; +import ai.chat2db.server.web.api.controller.rdb.request.TableMilvusQueryRequest; +import ai.chat2db.server.web.api.controller.rdb.request.TableRequest; import ai.chat2db.server.web.api.controller.rdb.vo.ColumnVO; import ai.chat2db.server.web.api.controller.rdb.vo.ExecuteResultVO; import ai.chat2db.server.web.api.controller.rdb.vo.IndexVO; @@ -21,9 +55,6 @@ import ai.chat2db.spi.model.Table; import ai.chat2db.spi.model.TableColumn; import ai.chat2db.spi.model.TableIndex; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.Mappings; /** * @author moji @@ -99,6 +130,14 @@ public abstract class RdbWebConverter { * @return */ public abstract SqlVO dto2vo(Sql dto); + + /** + * 参数转换 + * + * @param request + * @return + */ + public abstract TablePageQueryParam chatQueryRequest2page(ChatQueryRequest request); /** * 参数转换 * @@ -136,6 +175,12 @@ public abstract class RdbWebConverter { * @return */ public abstract DropParam tableDelete2dropParam(TableDeleteRequest request); + /** + * 参数转换 + * @param request + * @return + */ + public abstract DropKeyParam keyDelete2dropParm(KeyDeleteRequest request); /** @@ -160,6 +205,9 @@ public abstract class RdbWebConverter { * @param dto * @return */ + @Mappings({ + @Mapping(target = "comment", expression = "java(getComment(dto))") + }) public abstract ColumnVO columnDto2vo(TableColumn dto); /** @@ -170,6 +218,10 @@ public abstract class RdbWebConverter { */ public abstract List columnDto2vo(List dtos); + // 自定义方法,获取非空的comment + protected String getComment(TableColumn dto) { + return StringUtils.defaultIfBlank(dto.getComment(), dto.getAiComment()); + } /** * 模型转换 * @@ -198,6 +250,9 @@ public abstract class RdbWebConverter { @Mappings({ @Mapping(source = "columnList", target = "columnList"), @Mapping(source = "indexList", target = "indexList"), + @Mapping(source = "comment", target = "rawComment"), + @Mapping(source = "aiComment", target = "aiComment"), + @Mapping(target = "comment", expression = "java(getComment(dto))") }) public abstract TableVO tableDto2vo(Table dto); @@ -216,6 +271,12 @@ public abstract class RdbWebConverter { */ public abstract List schemaDto2vo(List tableColumns); + + // 自定义方法,获取非空的comment + protected String getComment(Table dto) { + return StringUtils.defaultIfBlank(dto.getComment(), dto.getAiComment()); + } + /** * 模型转换 * @param dto @@ -254,4 +315,9 @@ public abstract class RdbWebConverter { public abstract EsTableSchemaRequest req2req(TableBriefQueryRequest request); public abstract TablePageQueryParam schemaReq2page(EsTableSchemaRequest request); + public abstract SchemaQueryParam chatQueryRequest2schemaParam(ChatQueryRequest queryRequest); + + public abstract TableQueryParam chatQueryRequest2Param(ChatQueryRequest queryRequest); + + public abstract DeprecatedTableParam deprecatedTableRequest2param(DeprecatedTableRequest request); } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/conf/ExportOptions.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/conf/ExportOptions.java index 7f4151a96..58f8313fc 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/conf/ExportOptions.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/conf/ExportOptions.java @@ -18,6 +18,11 @@ public class ExportOptions { */ private Boolean isExportIndex = Boolean.FALSE; + /** + * 是否导出外键关系 + */ + private Boolean isExportForeignKey = Boolean.FALSE; + /** * 导出文件后缀 **/ diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/constant/PatternConstant.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/constant/PatternConstant.java index 38521e54d..d5261709f 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/constant/PatternConstant.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/constant/PatternConstant.java @@ -22,8 +22,8 @@ public final class PatternConstant { public static String TABLE_BODY = "|%s|%s|%s|%s|%s|%s|%s|%s|"; public static String TABLE_SEPARATOR = "|:----:|----|----|----|----|----|----|----|"; public static String ALL_INDEX_TABLE_HEADER = ""; - public static String INDEX_TABLE_BODY = "|%s|%s|%s|%s|"; - public static String INDEX_TABLE_SEPARATOR = "|:----:|----|----|----|"; + public static String INDEX_TABLE_BODY = "|%s|%s|%s|%s|%s|"; + public static String INDEX_TABLE_SEPARATOR = "|:----:|----|----|----|----|"; /** * Html @@ -34,5 +34,5 @@ public final class PatternConstant { public static String HTML_TABLE_HEADER = ""; public static String HTML_TABLE_BODY = ""; public static String HTML_INDEX_TABLE_HEADER = ""; - public static String HTML_INDEX_TABLE_BODY = ""; + public static String HTML_INDEX_TABLE_BODY = ""; } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/BatchTableModifySqlRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/BatchTableModifySqlRequest.java new file mode 100644 index 000000000..5c9d6d6ac --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/BatchTableModifySqlRequest.java @@ -0,0 +1,27 @@ +package ai.chat2db.server.web.api.controller.rdb.request; + +import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; +import ai.chat2db.spi.model.Table; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +/** + * 批量修改表sql请求 + */ +@Data +public class BatchTableModifySqlRequest extends DataSourceBaseRequest { + + /** + * 旧的表结构 + * 为空代表新建表 + */ + private List
%s%s%s%s%s%s%s%s
%s%s%s%s
%s%s%s%s%s
oldTables; + + /** + * 新的表结构 + */ + @NotNull + private List
newTables; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/BatchTableOperationRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/BatchTableOperationRequest.java new file mode 100644 index 000000000..35646ea6f --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/BatchTableOperationRequest.java @@ -0,0 +1,20 @@ +package ai.chat2db.server.web.api.controller.rdb.request; + +import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +import java.util.List; + +/** + * 批量表操作请求(OPTIMIZE/ANALYZE) + */ +@Data +public class BatchTableOperationRequest extends DataSourceBaseRequest { + + /** + * 表名列表 + */ + @NotEmpty + private List tableNames; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/CreateVirtualFKRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/CreateVirtualFKRequest.java new file mode 100644 index 000000000..82f628351 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/CreateVirtualFKRequest.java @@ -0,0 +1,31 @@ +package ai.chat2db.server.web.api.controller.rdb.request; + +import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequestInfo; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class CreateVirtualFKRequest implements DataSourceBaseRequestInfo { + + @NotNull + private Long dataSourceId; + + private String databaseName; + + private String schemaName; + + @NotBlank + private String tableName; + + @NotBlank + private String columnName; + + @NotBlank + private String referencedTable; + + @NotBlank + private String referencedColumnName; + + private String comment; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/DataGenerationRequestVO.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/DataGenerationRequestVO.java new file mode 100644 index 000000000..d3e6cfc52 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/DataGenerationRequestVO.java @@ -0,0 +1,42 @@ +package ai.chat2db.server.web.api.controller.rdb.request; + +import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequestInfo; +import lombok.Data; + +import java.util.List; + +@Data +public class DataGenerationRequestVO implements DataSourceBaseRequestInfo { + + private Long dataSourceId; + + private String databaseName; + + private String schemaName; + + private String tableName; + + private Integer rowCount; + + private List columnConfigs; + + private Integer batchSize = 1000; + + private Boolean previewMode = false; + + @Data + public static class ColumnConfigVO { + private String columnName; + private String dataType; + private String expression; + private String comment; + private Boolean nullable; + private Boolean autoIncrement; + private Integer maxLength; + private Integer scale; + private Boolean foreignKey; + private String foreignKeySourceType; + private String referencedTable; + private String referencedColumnName; + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/DeleteFKByNameRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/DeleteFKByNameRequest.java new file mode 100644 index 000000000..c0db5f94a --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/DeleteFKByNameRequest.java @@ -0,0 +1,24 @@ +package ai.chat2db.server.web.api.controller.rdb.request; + +import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequestInfo; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class DeleteFKByNameRequest implements DataSourceBaseRequestInfo { + + @NotNull + private Long dataSourceId; + + @NotBlank + private String databaseName; + + private String schemaName; + + @NotBlank + private String tableName; + + @NotBlank + private String keyName; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/DeleteFKRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/DeleteFKRequest.java new file mode 100644 index 000000000..bae5d974e --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/DeleteFKRequest.java @@ -0,0 +1,15 @@ +package ai.chat2db.server.web.api.controller.rdb.request; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class DeleteFKRequest { + + @NotNull + private Long id; + + @NotBlank + private String sourceType; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/DeprecatedTableRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/DeprecatedTableRequest.java new file mode 100644 index 000000000..3b112299a --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/DeprecatedTableRequest.java @@ -0,0 +1,34 @@ +package ai.chat2db.server.web.api.controller.rdb.request; + +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class DeprecatedTableRequest { + + /** + * 数据源id + */ + @NotNull + private Long dataSourceId; + + /** + * DB名称 + */ + private String databaseName; + + /** + * 表所在空间 + */ + private String schemaName; + /** + * Deprecated table name + */ + private String tableName; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/DmlRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/DmlRequest.java index 593426a15..25dd8dcc8 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/DmlRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/DmlRequest.java @@ -21,6 +21,11 @@ public class DmlRequest extends DataSourceBaseRequest implements DataSourceConso @NotNull private String sql; + /** + * 原始编辑器中执行脚本的起始行(从1开始) + */ + private Integer scriptStartLine; + /** * 控制台id */ diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/ErDiagramQueryRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/ErDiagramQueryRequest.java new file mode 100644 index 000000000..53bf4f82c --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/ErDiagramQueryRequest.java @@ -0,0 +1,32 @@ +package ai.chat2db.server.web.api.controller.rdb.request; + +import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * ER图查询请求 + */ +@Data +public class ErDiagramQueryRequest extends DataSourceBaseRequest { + + /** + * 表名过滤条件,支持模糊匹配 + */ + private String tableNameFilter; + + /** + * 是否包含虚拟外键(根据命名规范推断的外键),默认为true + */ + private Boolean includeVirtualFk = true; + + /** + * 是否同步数据库真实外键到本地,默认为false + */ + private Boolean syncForeignKeys = false; + + /** + * 是否只显示有外键关系的表,默认为false + */ + private Boolean onlyRelatedTables = false; +} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/ForeignKeyListRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/ForeignKeyListRequest.java new file mode 100644 index 000000000..0bb216ec2 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/ForeignKeyListRequest.java @@ -0,0 +1,18 @@ +package ai.chat2db.server.web.api.controller.rdb.request; + +import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequestInfo; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class ForeignKeyListRequest implements DataSourceBaseRequestInfo { + + @NotNull + private Long dataSourceId; + + private String databaseName; + + private String schemaName; + + private String tableName; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/ForeignKeySyncRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/ForeignKeySyncRequest.java new file mode 100644 index 000000000..69da9d36f --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/ForeignKeySyncRequest.java @@ -0,0 +1,18 @@ +package ai.chat2db.server.web.api.controller.rdb.request; + +import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequestInfo; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class ForeignKeySyncRequest implements DataSourceBaseRequestInfo { + + @NotNull + private Long dataSourceId; + + private String databaseName; + + private String schemaName; + + private String tableName; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/KeyDeleteRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/KeyDeleteRequest.java new file mode 100644 index 000000000..6961a72db --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/KeyDeleteRequest.java @@ -0,0 +1,18 @@ +package ai.chat2db.server.web.api.controller.rdb.request; + +import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 外键删除 + */ +@Data +public class KeyDeleteRequest extends DataSourceBaseRequest { + + /** + * 外键名 + */ + @NotNull + private String keyName; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/SchemaCompareRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/SchemaCompareRequest.java new file mode 100644 index 000000000..d61bb528d --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/SchemaCompareRequest.java @@ -0,0 +1,31 @@ +package ai.chat2db.server.web.api.controller.rdb.request; + +import java.util.List; + +import ai.chat2db.server.domain.api.param.schemaDiff.CompareOption; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class SchemaCompareRequest { + + @NotNull + private Long sourceDataSourceId; + + @NotNull + private String sourceDatabaseName; + + private String sourceSchemaName; + + @NotNull + private Long targetDataSourceId; + + @NotNull + private String targetDatabaseName; + + private String targetSchemaName; + + private List tableNames; + + private CompareOption compareOption; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/SchemaMigrateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/SchemaMigrateRequest.java new file mode 100644 index 000000000..e5bc7547a --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/SchemaMigrateRequest.java @@ -0,0 +1,25 @@ +package ai.chat2db.server.web.api.controller.rdb.request; + +import java.util.List; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class SchemaMigrateRequest { + + @NotNull + private Long targetDataSourceId; + + @NotNull + private String targetDatabaseName; + + private String targetSchemaName; + + @NotNull + private List ddlStatements; + + private boolean executeInTransaction; + + private boolean continueOnError; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableBriefQueryRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableBriefQueryRequest.java index ed3395630..4853c02f3 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableBriefQueryRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableBriefQueryRequest.java @@ -44,4 +44,14 @@ public class TableBriefQueryRequest extends PageQueryRequest implements DataSour */ private boolean refresh; + /** + * 排序字段: name, rowCount + */ + private String sortField; + + /** + * 排序方向: ascend, descend + */ + private String sortOrder; + } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableDeleteRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableDeleteRequest.java index e1828b929..42d5b078e 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableDeleteRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableDeleteRequest.java @@ -1,10 +1,7 @@ package ai.chat2db.server.web.api.controller.rdb.request; -import jakarta.validation.constraints.NotNull; - -import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; - +import jakarta.validation.constraints.NotNull; import lombok.Data; /** diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableRequest.java index 3d38ab415..b35ef1a98 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableRequest.java @@ -1,7 +1,9 @@ package ai.chat2db.server.web.api.controller.rdb.request; +import java.util.Collections; import java.util.List; +import ai.chat2db.spi.model.ForeignKey; import ai.chat2db.spi.model.TableColumn; import ai.chat2db.spi.model.TableIndex; import com.fasterxml.jackson.annotation.JsonAlias; @@ -40,6 +42,11 @@ public class TableRequest { */ private List indexList; + /** + * 外键列表 + */ + private List foreignKeyList; + /** * 空间名 diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TreeSearchRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TreeSearchRequest.java new file mode 100644 index 000000000..604e333cd --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TreeSearchRequest.java @@ -0,0 +1,30 @@ +package ai.chat2db.server.web.api.controller.rdb.request; + +import java.io.Serial; + +import jakarta.validation.constraints.NotNull; + +import ai.chat2db.server.tools.base.wrapper.request.PageQueryRequest; +import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequestInfo; + +import lombok.Data; + +@Data +public class TreeSearchRequest extends PageQueryRequest implements DataSourceBaseRequestInfo { + + @Serial + private static final long serialVersionUID = 1L; + + @NotNull + private Long dataSourceId; + + private String databaseName; + + private String schemaName; + + private String searchKey; + + private String treeNodeType; + + private boolean refresh; +} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/UpdateVirtualFKRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/UpdateVirtualFKRequest.java new file mode 100644 index 000000000..2ea10ea9d --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/UpdateVirtualFKRequest.java @@ -0,0 +1,31 @@ +package ai.chat2db.server.web.api.controller.rdb.request; + +import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequestInfo; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class UpdateVirtualFKRequest implements DataSourceBaseRequestInfo { + + @NotNull + private Long id; + + @NotNull + private Long dataSourceId; + + private String databaseName; + + private String schemaName; + + private String comment; + + private String tableName; + + private String columnName; + + private String referencedTable; + + private String referencedColumnName; + + private String vkName; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ColumnVO.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ColumnVO.java index 241a1345c..f2d9512c6 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ColumnVO.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ColumnVO.java @@ -43,7 +43,7 @@ public class ColumnVO { * 比如 varchar ,double */ - private Integer dataType; + private String dataType; /** diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/DeleteFKResult.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/DeleteFKResult.java new file mode 100644 index 000000000..7e984f7f9 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/DeleteFKResult.java @@ -0,0 +1,11 @@ +package ai.chat2db.server.web.api.controller.rdb.vo; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class DeleteFKResult { + + private String executedDDL; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ExecuteResultVO.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ExecuteResultVO.java index 69147adeb..178d9ba49 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ExecuteResultVO.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ExecuteResultVO.java @@ -2,8 +2,8 @@ import java.util.List; - import ai.chat2db.spi.model.Header; +import ai.chat2db.spi.model.VirtualForeignKeySuggestion; import lombok.Data; /** @@ -14,6 +14,31 @@ @Data public class ExecuteResultVO { + /** + * 语句序号(从1开始) + */ + private Integer statementIndex; + + /** + * 语句在原始脚本中的起始行(从1开始) + */ + private Integer statementStartLine; + + /** + * 语句在原始脚本中的结束行(从1开始) + */ + private Integer statementEndLine; + + /** + * 错误在当前语句内的行号(从1开始) + */ + private Integer errorLineInStatement; + + /** + * 错误在整段脚本中的绝对行号(从1开始) + */ + private Integer errorLine; + /** * 执行的sql */ @@ -98,4 +123,10 @@ public class ExecuteResultVO { * 表名 */ private String tableName; + + /** + * 虚拟外键建议列表 + */ + private List vkSuggestions; + } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ForeignKeyVO.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ForeignKeyVO.java new file mode 100644 index 000000000..42bc38c88 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ForeignKeyVO.java @@ -0,0 +1,37 @@ +package ai.chat2db.server.web.api.controller.rdb.vo; + +import lombok.Builder; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +@Builder +public class ForeignKeyVO { + + private Long id; + + private String name; + + private String tableName; + + private String columnName; + + private String referencedTable; + + private String referencedColumnName; + + private String comment; + + private Integer updateRule; + + private Integer deleteRule; + + private String sourceType; + + private Boolean editable; + + private String virtualProperty; + + private LocalDateTime syncTime; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/SchemaVO.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/SchemaVO.java index 841363514..0e3ed0e38 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/SchemaVO.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/SchemaVO.java @@ -13,4 +13,8 @@ public class SchemaVO { * 数据名字 */ private String name; + /** + * 子节点类型 + */ + private String treeNodeType; } \ No newline at end of file diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/SyncResult.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/SyncResult.java new file mode 100644 index 000000000..730cffc56 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/SyncResult.java @@ -0,0 +1,15 @@ +package ai.chat2db.server.web.api.controller.rdb.vo; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class SyncResult { + + private int added; + + private int deleted; + + private int unchanged; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/TableVO.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/TableVO.java index fd18e242f..f73789c06 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/TableVO.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/TableVO.java @@ -2,8 +2,10 @@ import java.util.List; +import ai.chat2db.spi.model.ForeignKey; import ai.chat2db.spi.model.TableColumn; import ai.chat2db.spi.model.TableIndex; +import ai.chat2db.spi.model.VirtualForeignKey; import lombok.Data; /** @@ -24,6 +26,16 @@ public class TableVO { */ private String comment; + /** + * 数据库原始注释 + */ + private String rawComment; + + /** + * AI 生成注释 + */ + private String aiComment; + /** * 列 */ @@ -34,6 +46,16 @@ public class TableVO { */ private List indexList; + /** + * 外键列表 + */ + private List foreignKeyList; + + /** + * 虚拟外键 + */ + private List virtualForeignKeyList; + /** * 是否已经被固定 */ @@ -43,4 +65,9 @@ public class TableVO { * ddl */ private String ddl; + + /** + * 预估行数 + */ + private Long rowCount; } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/TreeNodeVO.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/TreeNodeVO.java new file mode 100644 index 000000000..975b55db6 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/TreeNodeVO.java @@ -0,0 +1,30 @@ +package ai.chat2db.server.web.api.controller.rdb.vo; + +import java.util.List; +import java.util.Map; + +import lombok.Data; + +@Data +public class TreeNodeVO { + + private String uuid; + + private String key; + + private String name; + + private String treeNodeType; + + private String pretendNodeType; + + private String comment; + + private Boolean isLeaf; + + private Boolean pinned; + + private List parentPath; + + private Map extraParams; +} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/redis/RedisKeyManageController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/redis/RedisKeyManageController.java index e2ab8a72f..5959a104d 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/redis/RedisKeyManageController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/redis/RedisKeyManageController.java @@ -1,20 +1,37 @@ package ai.chat2db.server.web.api.controller.redis; +import ai.chat2db.server.tools.base.excption.BusinessException; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; -import ai.chat2db.server.tools.base.wrapper.result.ListResult; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; +import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; import ai.chat2db.server.web.api.controller.redis.request.KeyCreateRequest; import ai.chat2db.server.web.api.controller.redis.request.KeyDeleteRequest; +import ai.chat2db.server.web.api.controller.redis.request.KeyPartialUpdateRequest; import ai.chat2db.server.web.api.controller.redis.request.KeyQueryRequest; import ai.chat2db.server.web.api.controller.redis.request.KeyUpdateRequest; import ai.chat2db.server.web.api.controller.redis.vo.KeyVO; +import ai.chat2db.spi.MetaData; +import ai.chat2db.spi.redis.RedisKeyBrowser; +import ai.chat2db.spi.redis.RedisKeyInfo; +import ai.chat2db.spi.redis.RedisKeyScanResult; +import ai.chat2db.spi.sql.Chat2DBContext; -import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.beans.BeanUtils; +import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletionException; +import java.util.concurrent.CompletableFuture; /** * redis key运维类 @@ -25,17 +42,67 @@ */ @RequestMapping("/api/redis/key") @RestController +@ConnectionInfoAspect public class RedisKeyManageController { + private static final int DEFAULT_KEY_COUNT = 1000; + private static final long STREAM_TIMEOUT = 30 * 60 * 1000L; + + /** + * 流式查询当前DB下的key列表 + * + * @param request + * @return + * @throws IOException + */ + @GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) + public SseEmitter stream(KeyQueryRequest request) throws IOException { + SseEmitter emitter = new SseEmitter(STREAM_TIMEOUT); + emitter.send(SseEmitter.event() + .name("connect") + .data(LocalDateTime.now().toString()) + .reconnectTime(3000)); + + try { + RedisKeyBrowser browser = getRedisKeyBrowser(); + int count = request.getCount() == null ? DEFAULT_KEY_COUNT : request.getCount(); + CompletableFuture resultFuture = browser.streamKeys(request.getDatabaseName(), + request.getSearchKey(), request.getCursor(), count, batch -> { + sendEvent(emitter, "keys", batch.stream().map(this::toVO).toList()); + }); + resultFuture.whenCompleteAsync((result, throwable) -> { + if (throwable != null) { + Throwable cause = unwrapCompletionException(throwable); + sendStreamError(emitter, cause); + return; + } + try { + sendEvent(emitter, "done", Map.of( + "total", result.getTotal(), + "cursor", result.getCursor(), + "hasMore", result.getHasMore() + )); + emitter.complete(); + } catch (Exception e) { + sendStreamError(emitter, e); + } + }); + } catch (Exception e) { + sendStreamError(emitter, e); + } + return emitter; + } + /** - * 查询当前DB下的key列表 + * 获取缓存key详情 * * @param request * @return */ - @GetMapping("/list") - public ListResult list(KeyQueryRequest request) { - return null; + @GetMapping("/query") + public DataResult query(KeyQueryRequest request) { + RedisKeyInfo keyInfo = getRedisKeyBrowser().queryKey(request.getDatabaseName(), request.getKeyName()); + return DataResult.of(toVO(keyInfo)); } /** @@ -46,7 +113,9 @@ public ListResult list(KeyQueryRequest request) { */ @PostMapping("/create") public ActionResult create(@RequestBody KeyCreateRequest request) { - return null; + getRedisKeyBrowser().createKey(request.getDatabaseName(), request.getName(), request.getKeyType(), + request.getValue(), request.getTtl()); + return ActionResult.isSuccess(); } /** @@ -57,7 +126,26 @@ public ActionResult create(@RequestBody KeyCreateRequest request) { */ @RequestMapping(value = "/update",method = {RequestMethod.POST, RequestMethod.PUT}) public ActionResult update(@RequestBody KeyUpdateRequest request) { - return null; + Object updateTtl = request.getUpdateTtl(); + Long ttl = updateTtl == null ? null : Long.valueOf(String.valueOf(updateTtl)); + getRedisKeyBrowser().updateKey(request.getDatabaseName(), request.getOriginalKey(), request.getUpdateKey(), + request.getKeyType(), request.getValue(), ttl); + return ActionResult.isSuccess(); + } + + /** + * 部分更新key(仅修改hash的增量字段) + * + * @param request + * @return + */ + @PostMapping("/partial-update") + public ActionResult partialUpdate(@RequestBody KeyPartialUpdateRequest request) { + Object updateTtl = request.getUpdateTtl(); + Long ttl = updateTtl == null ? null : Long.valueOf(String.valueOf(updateTtl)); + getRedisKeyBrowser().partialUpdateKey(request.getDatabaseName(), request.getKeyName(), + request.getKeyType(), request.getAddedFields(), request.getRemovedFields(), ttl); + return ActionResult.isSuccess(); } @@ -67,8 +155,47 @@ public ActionResult update(@RequestBody KeyUpdateRequest request) { * @param request * @return */ - @DeleteMapping("/delete") + @RequestMapping(value = "/delete", method = {RequestMethod.POST, RequestMethod.DELETE}) public ActionResult delete(@RequestBody KeyDeleteRequest request) { - return null; + getRedisKeyBrowser().deleteKey(request.getDatabaseName(), request.getKeyName()); + return ActionResult.isSuccess(); + } + + private RedisKeyBrowser getRedisKeyBrowser() { + MetaData metaData = Chat2DBContext.getMetaData(); + if (metaData instanceof RedisKeyBrowser browser) { + return browser; + } + throw new BusinessException("当前数据源不是 Redis"); + } + + private KeyVO toVO(RedisKeyInfo keyInfo) { + KeyVO vo = new KeyVO(); + BeanUtils.copyProperties(keyInfo, vo); + return vo; + } + + private void sendEvent(SseEmitter emitter, String eventName, Object data) { + try { + emitter.send(SseEmitter.event().name(eventName).data(data)); + } catch (IOException e) { + throw new BusinessException("Redis key stream send failed", null, e); + } + } + + private void sendStreamError(SseEmitter emitter, Throwable throwable) { + try { + sendEvent(emitter, "redis_error", Map.of("message", throwable.getMessage())); + } catch (Exception ignored) { + // ignore send failure and complete the emitter with the original error + } + emitter.completeWithError(throwable); + } + + private Throwable unwrapCompletionException(Throwable throwable) { + if (throwable instanceof CompletionException && throwable.getCause() != null) { + return throwable.getCause(); + } + return throwable; } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/redis/RedisKeyValueManageController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/redis/RedisKeyValueManageController.java deleted file mode 100644 index 742550963..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/redis/RedisKeyValueManageController.java +++ /dev/null @@ -1,60 +0,0 @@ -package ai.chat2db.server.web.api.controller.redis; - -import ai.chat2db.server.tools.base.wrapper.result.ActionResult; -import ai.chat2db.server.tools.base.wrapper.result.DataResult; -import ai.chat2db.server.web.api.controller.redis.request.KeyQueryRequest; -import ai.chat2db.server.web.api.controller.redis.request.KeyValueManageRequest; -import ai.chat2db.server.web.api.controller.redis.request.ValueUpdateRequest; -import ai.chat2db.server.web.api.controller.redis.vo.KeyVO; - -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; - -/** - * redis数据运维类 - * - * @author moji - * @version MysqlDataManageController.java, v 0.1 2022年09月16日 17:37 moji Exp $ - * @date 2022/09/16 - */ -@RequestMapping("/api/redis/kv") -@RestController -public class RedisKeyValueManageController { - - /** - * redis ddl命令执行 - * - * @param request - * @return - */ - @RequestMapping(value = "/manage",method = {RequestMethod.POST, RequestMethod.PUT}) - public DataResult manage(@RequestBody KeyValueManageRequest request) { - return null; - } - - /** - * 获取缓存key详情 - * - * @param request - * @return - */ - @GetMapping("/query") - public DataResult query(KeyQueryRequest request) { - return null; - } - - /** - * 更新key值 - * - * @param request - * @return - */ - @RequestMapping(value = "/update",method = {RequestMethod.POST, RequestMethod.PUT}) - public ActionResult update(@RequestBody ValueUpdateRequest request) { - return null; - } - -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/redis/RedisMonitorController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/redis/RedisMonitorController.java new file mode 100644 index 000000000..dd527141d --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/redis/RedisMonitorController.java @@ -0,0 +1,82 @@ +package ai.chat2db.server.web.api.controller.redis; + +import ai.chat2db.server.tools.base.excption.BusinessException; +import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; +import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; +import ai.chat2db.spi.MetaData; +import ai.chat2db.spi.redis.RedisCommandMonitor; +import ai.chat2db.spi.sql.Chat2DBContext; +import ai.chat2db.spi.sql.ConnectInfo; + +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; + +@RequestMapping("/api/redis/monitor") +@RestController +@ConnectionInfoAspect +public class RedisMonitorController { + + private static final long STREAM_TIMEOUT = 24 * 60 * 60 * 1000L; + + @GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) + public SseEmitter stream(DataSourceBaseRequest request) throws IOException { + SseEmitter emitter = new SseEmitter(STREAM_TIMEOUT); + AtomicBoolean running = new AtomicBoolean(true); + ConnectInfo connectInfo = Chat2DBContext.getConnectInfo().copy(); + + emitter.onCompletion(() -> running.set(false)); + emitter.onTimeout(() -> running.set(false)); + emitter.onError(error -> running.set(false)); + emitter.send(SseEmitter.event() + .name("connect") + .data(LocalDateTime.now().toString()) + .reconnectTime(3000)); + + CompletableFuture.runAsync(() -> { + try { + Chat2DBContext.putContext(connectInfo); + getRedisCommandMonitor().monitor(request.getDatabaseName(), line -> { + if (running.get()) { + sendEvent(emitter, "command", Map.of("line", line)); + } + }, running::get); + sendEvent(emitter, "done", Map.of("time", LocalDateTime.now().toString())); + emitter.complete(); + } catch (Exception e) { + if (running.get()) { + sendEvent(emitter, "redis_error", Map.of("message", e.getMessage())); + emitter.completeWithError(e); + } + } finally { + running.set(false); + Chat2DBContext.removeContext(); + } + }); + return emitter; + } + + private RedisCommandMonitor getRedisCommandMonitor() { + MetaData metaData = Chat2DBContext.getMetaData(); + if (metaData instanceof RedisCommandMonitor monitor) { + return monitor; + } + throw new BusinessException("当前数据源不是 Redis"); + } + + private void sendEvent(SseEmitter emitter, String eventName, Object data) { + try { + emitter.send(SseEmitter.event().name(eventName).data(data)); + } catch (IOException e) { + throw new BusinessException("Redis monitor stream send failed", null, e); + } + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/redis/request/KeyCreateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/redis/request/KeyCreateRequest.java index 3316aa488..aed282eee 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/redis/request/KeyCreateRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/redis/request/KeyCreateRequest.java @@ -2,7 +2,6 @@ import jakarta.validation.constraints.NotNull; -import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import lombok.Data; @@ -21,6 +20,12 @@ public class KeyCreateRequest extends DataSourceBaseRequest { @NotNull private String name; + /** + * key类型 + */ + @NotNull + private String keyType; + /** * key值 */ diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/redis/request/KeyDeleteRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/redis/request/KeyDeleteRequest.java index ba22a0cb1..e5b0a8c02 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/redis/request/KeyDeleteRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/redis/request/KeyDeleteRequest.java @@ -1,6 +1,5 @@ package ai.chat2db.server.web.api.controller.redis.request; -import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import lombok.Data; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/redis/request/KeyPartialUpdateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/redis/request/KeyPartialUpdateRequest.java new file mode 100644 index 000000000..51d07004f --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/redis/request/KeyPartialUpdateRequest.java @@ -0,0 +1,26 @@ +package ai.chat2db.server.web.api.controller.redis.request; + +import jakarta.validation.constraints.NotNull; + +import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; + +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Data +public class KeyPartialUpdateRequest extends DataSourceBaseRequest { + + @NotNull + private String keyName; + + @NotNull + private String keyType; + + private Map addedFields; + + private List removedFields; + + private Object updateTtl; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/redis/request/KeyQueryRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/redis/request/KeyQueryRequest.java index 3f44ea925..b92bbbebf 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/redis/request/KeyQueryRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/redis/request/KeyQueryRequest.java @@ -1,6 +1,5 @@ package ai.chat2db.server.web.api.controller.redis.request; -import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import lombok.Data; @@ -22,4 +21,15 @@ public class KeyQueryRequest extends DataSourceBaseRequest { * 搜索关键词 */ private String searchKey; + + /** + * Redis SCAN 游标 + */ + private String cursor; + + /** + * 返回数量 + */ + private Integer count; + } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/redis/request/KeyUpdateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/redis/request/KeyUpdateRequest.java index e37a290ad..af7016efd 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/redis/request/KeyUpdateRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/redis/request/KeyUpdateRequest.java @@ -2,7 +2,6 @@ import jakarta.validation.constraints.NotNull; -import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import lombok.Data; @@ -26,6 +25,17 @@ public class KeyUpdateRequest extends DataSourceBaseRequest { */ private String updateKey; + /** + * key类型 + */ + @NotNull + private String keyType; + + /** + * 更新后key值 + */ + private Object value; + /** * 原始ttl值 */ diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/redis/vo/KeyVO.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/redis/vo/KeyVO.java index 318cbf96b..0b61af468 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/redis/vo/KeyVO.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/redis/vo/KeyVO.java @@ -29,4 +29,9 @@ public class KeyVO { * 过期时间 */ private Long ttl; + + /** + * key 内存占用字节数 + */ + private Long size; } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/sql/SqlController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/sql/SqlController.java index 97e3b1e6c..44593940a 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/sql/SqlController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/sql/SqlController.java @@ -2,12 +2,19 @@ import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; +import ai.chat2db.server.web.api.controller.sql.biz.SqlExecuteBizService; import ai.chat2db.server.web.api.controller.sql.request.SqlFormatRequest; +import ai.chat2db.server.web.api.controller.sql.request.SqlFileExecuteRequest; import com.github.vertical_blank.sqlformatter.SqlFormatter; import jakarta.validation.Valid; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; /** * SQL Controller @@ -17,6 +24,9 @@ @RestController public class SqlController { + @Autowired + private SqlExecuteBizService sqlExecuteBizService; + /** * SQL Format * @@ -34,4 +44,11 @@ public DataResult list(@Valid SqlFormatRequest sqlFormatRequest) { return DataResult.of(sql); } + @PostMapping("/execute_file") + public DataResult executeFile( + @RequestPart("file") MultipartFile file, + @ModelAttribute SqlFileExecuteRequest request) { + return sqlExecuteBizService.executeSqlFile(file, request); + } + } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/sql/biz/SqlExecuteBizService.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/sql/biz/SqlExecuteBizService.java new file mode 100644 index 000000000..99c4936fe --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/sql/biz/SqlExecuteBizService.java @@ -0,0 +1,267 @@ +package ai.chat2db.server.web.api.controller.sql.biz; + +import ai.chat2db.server.domain.api.enums.TaskStatusEnum; +import ai.chat2db.server.domain.api.enums.TaskTypeEnum; +import ai.chat2db.server.domain.api.param.TaskCreateParam; +import ai.chat2db.server.domain.api.param.TaskUpdateParam; +import ai.chat2db.server.domain.api.service.TaskService; +import ai.chat2db.server.domain.repository.Dbutils; +import ai.chat2db.server.tools.base.excption.BusinessException; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; +import ai.chat2db.server.tools.common.model.Context; +import ai.chat2db.server.tools.common.model.LoginUser; +import ai.chat2db.server.tools.common.util.ContextUtils; +import ai.chat2db.server.tools.common.util.I18nUtils; +import ai.chat2db.server.web.api.controller.sql.request.SqlFileExecuteRequest; +import ai.chat2db.spi.sql.Chat2DBContext; +import ai.chat2db.spi.sql.ConnectInfo; +import cn.hutool.core.io.FileUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicInteger; + +@Slf4j +@Component +public class SqlExecuteBizService { + + @Autowired + private TaskService taskService; + + public DataResult executeSqlFile(MultipartFile file, SqlFileExecuteRequest request) { + if (file == null) { + throw new BusinessException("common.paramError"); + } + + DataResult dataResult = createExecuteTask(request); + LoginUser loginUser = ContextUtils.getLoginUser(); + ConnectInfo connectInfo = Chat2DBContext.getConnectInfo().copy(); + + File safeFile; + try { + String originalFilename = file.getOriginalFilename(); + if (originalFilename == null) { + originalFilename = "sql_execute_file"; + } + safeFile = FileUtil.createTempFile(originalFilename, "", true); + file.transferTo(safeFile); + } catch (Exception e) { + log.error("save sql file error", e); + throw new BusinessException("dataSource.importError", new Object[]{e.getMessage()}, e); + } + + final File finalSafeFile = safeFile; + CompletableFuture.runAsync(() -> { + buildContext(loginUser, connectInfo); + try { + doExecuteSql(finalSafeFile, dataResult.getData()); + } finally { + FileUtil.del(finalSafeFile); + } + }).whenComplete((aVoid, throwable) -> { + updateTaskStatus(dataResult.getData(), throwable); + removeContext(); + }); + + return dataResult; + } + + private DataResult createExecuteTask(SqlFileExecuteRequest request) { + TaskCreateParam param = new TaskCreateParam(); + param.setTaskName("execute_sql_file"); + param.setTaskType(TaskTypeEnum.UPLOAD_TABLE_STRUCTURE.name()); + param.setDatabaseName(request.getDatabaseName()); + param.setSchemaName(request.getSchemaName()); + param.setDataSourceId(request.getDataSourceId()); + param.setUserId(ContextUtils.getUserId()); + param.setTaskProgress("0"); + Long taskId = taskService.create(param); + return DataResult.of(taskId); + } + + private void doExecuteSql(File file, Long taskId) { + final AtomicInteger processedCount = new AtomicInteger(0); + Connection connection = Chat2DBContext.getConnection(); + boolean originalAutoCommit; + try { + originalAutoCommit = connection.getAutoCommit(); + } catch (SQLException e) { + throw new BusinessException("dataSource.importError", new Object[]{e.getMessage()}, e); + } + + try (Statement statement = connection.createStatement()) { + connection.setAutoCommit(false); + List sqlStatements = readSqlStatements(file); + for (SqlStatementItem item : sqlStatements) { + String trimmedSql = item.getSql().trim(); + if (StringUtils.isNotBlank(trimmedSql) && !trimmedSql.startsWith("--")) { + statement.execute(trimmedSql); + int count = processedCount.incrementAndGet(); + if (count % 200 == 0) { + updateProgressCount(taskId, count); + } + } + } + connection.commit(); + updateProgressCount(taskId, processedCount.get()); + } catch (Exception e) { + rollbackQuietly(connection); + if (e instanceof SQLException sqlException) { + int failedIndex = processedCount.get() + 1; + throw new BusinessException( + "dataSource.executeSqlErrorDetail", + new Object[]{ + failedIndex, + findStatementLine(file, failedIndex), + sqlException.getMessage(), + buildSqlSnippet(file, failedIndex) + }, + e + ); + } + throw new BusinessException("dataSource.importError", new Object[]{e.getMessage()}, e); + } finally { + try { + connection.setAutoCommit(originalAutoCommit); + } catch (SQLException e) { + log.warn("restore autoCommit failed", e); + } + } + } + + private void rollbackQuietly(Connection connection) { + try { + connection.rollback(); + } catch (SQLException ex) { + log.warn("rollback failed", ex); + } + } + + private int findStatementLine(File file, int statementIndex) { + try { + List statements = readSqlStatements(file); + if (statementIndex <= 0 || statementIndex > statements.size()) { + return -1; + } + return statements.get(statementIndex - 1).getStartLine(); + } catch (IOException ignore) { + return -1; + } + } + + private String buildSqlSnippet(File file, int statementIndex) { + try { + List statements = readSqlStatements(file); + if (statementIndex <= 0 || statementIndex > statements.size()) { + return ""; + } + String sql = statements.get(statementIndex - 1).getSql(); + sql = sql.replaceAll("\\s+", " ").trim(); + if (sql.length() > 300) { + return sql.substring(0, 300) + "..."; + } + return sql; + } catch (IOException ignore) { + return ""; + } catch (Exception e) { + throw new BusinessException("dataSource.importError", new Object[]{e.getMessage()}, e); + } + } + + private List readSqlStatements(File file) throws IOException { + List statements = new ArrayList<>(); + StringBuilder currentStatement = new StringBuilder(); + int startLine = 1; + int lineNo = 0; + try (BufferedReader reader = new BufferedReader(new FileReader(file, StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + lineNo++; + if (currentStatement.length() == 0) { + startLine = lineNo; + } + currentStatement.append(line).append("\n"); + if (line.trim().endsWith(";")) { + String sql = currentStatement.toString().trim(); + if (StringUtils.isNotBlank(sql)) { + statements.add(new SqlStatementItem(sql, startLine)); + } + currentStatement = new StringBuilder(); + } + } + String remaining = currentStatement.toString().trim(); + if (StringUtils.isNotBlank(remaining)) { + statements.add(new SqlStatementItem(remaining, startLine)); + } + } + return statements; + } + + private static class SqlStatementItem { + private final String sql; + private final int startLine; + + private SqlStatementItem(String sql, int startLine) { + this.sql = sql; + this.startLine = startLine; + } + + public String getSql() { + return sql; + } + + public int getStartLine() { + return startLine; + } + } + + private void updateProgressCount(Long taskId, int processedCount) { + TaskUpdateParam updateParam = new TaskUpdateParam(); + updateParam.setId(taskId); + updateParam.setTaskProgress(String.valueOf(processedCount)); + taskService.updateStatus(updateParam); + } + + private void updateTaskStatus(Long id, Throwable throwable) { + TaskUpdateParam updateParam = new TaskUpdateParam(); + updateParam.setId(id); + if (throwable != null) { + log.error("execute sql file error", throwable); + updateParam.setTaskStatus(TaskStatusEnum.ERROR.name()); + if (throwable.getCause() instanceof BusinessException businessException) { + updateParam.setContent(I18nUtils.getMessage(businessException.getCode(), businessException.getArgs())); + } else { + updateParam.setContent(throwable.getMessage()); + } + } else { + updateParam.setTaskStatus(TaskStatusEnum.FINISH.name()); + } + taskService.updateStatus(updateParam); + } + + private void removeContext() { + Dbutils.removeSession(); + ContextUtils.removeContext(); + Chat2DBContext.removeContext(); + } + + private void buildContext(LoginUser loginUser, ConnectInfo connectInfo) { + ContextUtils.setContext(Context.builder().loginUser(loginUser).build()); + Dbutils.setSession(); + Chat2DBContext.putContext(connectInfo); + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/sql/request/SqlFileExecuteRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/sql/request/SqlFileExecuteRequest.java new file mode 100644 index 000000000..26542e221 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/sql/request/SqlFileExecuteRequest.java @@ -0,0 +1,8 @@ +package ai.chat2db.server.web.api.controller.sql.request; + +import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; +import lombok.Data; + +@Data +public class SqlFileExecuteRequest extends DataSourceBaseRequest { +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/system/SystemController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/system/SystemController.java index 6d0db0a45..e3fa6d723 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/system/SystemController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/system/SystemController.java @@ -15,7 +15,6 @@ import ai.chat2db.server.tools.common.model.ConfigJson; import ai.chat2db.server.tools.common.util.ConfigUtils; import ai.chat2db.server.tools.common.util.EasyEnumUtils; -import ai.chat2db.server.web.api.controller.ai.chat2db.client.Chat2dbAIClient; import ai.chat2db.server.web.api.controller.system.util.SystemUtils; import ai.chat2db.server.web.api.controller.system.vo.AppVersionVO; import ai.chat2db.server.web.api.controller.system.vo.SystemVO; @@ -77,19 +76,15 @@ public DataResult getLatestVersion(String currentVersion) { return DataResult.of(null); } String user = ""; - DataResult dataResult = configService.find(Chat2dbAIClient.CHAT2DB_OPENAI_KEY); - if (dataResult.getData() != null) { - user = dataResult.getData().getContent(); - } AppVersionVO appVersionVO = SystemUtils.getLatestVersion(currentVersion, "manual", user); if (appVersionVO == null) { appVersionVO = new AppVersionVO(); appVersionVO.setVersion(currentVersion); appVersionVO.setType("manual"); } - DataResult updateType = configService.find(UPDATE_TYPE); - if (updateType.getData() != null) { - appVersionVO.setType(updateType.getData().getContent()); + Config updateType = configService.find(UPDATE_TYPE); + if (updateType != null) { + appVersionVO.setType(updateType.getContent()); } // In this mode, no user login is required, so only local access is available appVersionVO.setDesktop(true); diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/system/util/SystemUtils.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/system/util/SystemUtils.java index 4bc0f3fec..9cc188ddd 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/system/util/SystemUtils.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/system/util/SystemUtils.java @@ -79,13 +79,10 @@ public static void upgrade(AppVersionVO appVersion) { private static final String LATEST_VERSION_URL = "http://test.sqlgpt.cn/gateway/api/client/version/check/v3?version=%s&type=%s&userId=%s"; public static AppVersionVO getLatestVersion(String version, String type, String userId) { - String url = String.format(LATEST_VERSION_URL, version, type, userId); - DataResult result = Forest.get(url) - .connectTimeout(Duration.ofMillis(5000)) - .readTimeout(Duration.ofMillis(10000)) - .execute(new TypeReference<>() { - }); - return result.getData(); + AppVersionVO appVersionVO = new AppVersionVO(); + appVersionVO.setVersion(version); + appVersionVO.setType(type); + return appVersionVO; } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/ExportController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/ExportController.java index 2c0d01e73..0e8be431a 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/ExportController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/ExportController.java @@ -7,14 +7,18 @@ import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +/** + * 数据导出控制器 + * 提供数据导出和文档导出功能的REST API接口 + */ @ConnectionInfoAspect @RequestMapping("/api/export") -@Controller +@RestController @Slf4j public class ExportController { @@ -23,16 +27,24 @@ public class ExportController { /** - * export data + * 导出数据 + * 将查询结果数据导出为文件 * - * @param request - * @return + * @param request 数据导出请求参数,包含导出配置、查询条件等信息 + * @return 导出任务ID,用于后续跟踪导出任务状态 */ @PostMapping("/export_data") public DataResult export(@Valid @RequestBody DataExportRequest request) { return taskBizService.exportResultData(request); } + /** + * 导出数据库结构文档 + * 将数据库表结构、字段信息等导出为文档格式 + * + * @param request 数据导出请求参数,包含导出配置、数据库连接等信息 + * @return 导出任务ID,用于后续跟踪导出任务状态 + */ @PostMapping("/export_doc") public DataResult exportDoc(@Valid @RequestBody DataExportRequest request) { return taskBizService.exportSchemaDoc(request); diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/ImportController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/ImportController.java new file mode 100644 index 000000000..0a1d0696e --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/ImportController.java @@ -0,0 +1,62 @@ +package ai.chat2db.server.web.api.controller.task; + +import ai.chat2db.server.tools.base.wrapper.result.DataResult; +import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; +import ai.chat2db.server.web.api.controller.task.biz.ImportBizService; +import ai.chat2db.server.web.api.controller.task.request.DataImportRequest; +import ai.chat2db.server.web.api.controller.task.request.FilePreviewRequest; +import ai.chat2db.server.web.api.controller.task.response.FilePreviewResult; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +/** + * 数据导入控制器 + * 提供数据导入功能的 REST API 接口 + */ +@ConnectionInfoAspect +@RequestMapping("/api/import") +@RestController +@Slf4j +public class ImportController { + + @Autowired + private ImportBizService importBizService; + + + /** + * 导入数据 + * 将文件数据导入到数据库表中 + * + * @param file 导入的文件 + * @param request 数据导入请求参数,包含目标表、文件类型等信息 + * @return 导入任务 ID,用于后续跟踪导入任务状态 + */ + @PostMapping("/import_data") + public DataResult importData( + @RequestPart("file") MultipartFile file, + @ModelAttribute DataImportRequest request) { + return importBizService.importData(file, request); + } + + /** + * 预览文件表头 + * 解析文件获取表头列表,并返回目标表字段信息用于字段映射 + * + * @param file 预览的文件 + * @param request 文件预览请求参数 + * @return 文件表头、目标表字段、自动匹配结果 + */ + @PostMapping("/preview_headers") + public DataResult previewHeaders( + @RequestPart("file") MultipartFile file, + @ModelAttribute FilePreviewRequest request) { + return importBizService.previewHeaders(file, request); + } + +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/TaskController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/TaskController.java index b6c784117..ced8de26e 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/TaskController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/TaskController.java @@ -3,8 +3,8 @@ import ai.chat2db.server.domain.api.model.Task; import ai.chat2db.server.domain.api.param.TaskPageParam; import ai.chat2db.server.domain.api.service.TaskService; +import ai.chat2db.server.tools.base.wrapper.ServicePage; import ai.chat2db.server.tools.base.wrapper.result.DataResult; -import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; @@ -15,16 +15,19 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import java.io.File; import java.net.MalformedURLException; +import java.nio.file.Paths; @ConnectionInfoAspect @RequestMapping("/api/task") -@Controller +@RestController @Slf4j public class TaskController { @@ -38,38 +41,58 @@ public WebPageResult list() { taskPageParam.setPageNo(1); taskPageParam.setPageSize(10); taskPageParam.setUserId(ContextUtils.getUserId()); - PageResult task = taskService.page(taskPageParam); - return WebPageResult.of(task.getData(), 100L, 1, 10); + ServicePage task = taskService.page(taskPageParam); + return WebPageResult.of(task.getData(), task.getTotal(), task.getPageNo(), task.getPageSize()); + } + + @GetMapping("/get/{id}") + public DataResult get(@PathVariable Long id) { + Task task = taskService.get(id); + return DataResult.of(task); + } + + @PostMapping("/cleanup") + public DataResult cleanup() { + return DataResult.of(taskService.cleanupFinishedTasks(ContextUtils.getUserId())); } @GetMapping("/download/{id}") public ResponseEntity download(@PathVariable Long id) { - DataResult task = taskService.get(id); - if(task.getData() == null){ + Task task = taskService.get(id); + if(task == null){ log.error("task is null"); throw new RuntimeException("task is null"); } - if(ContextUtils.getUserId() != task.getData().getUserId()){ + if(!ContextUtils.getUserId().equals(task.getUserId())){ log.error("task is not belong to user"); throw new RuntimeException("task is not belong to user"); } - Resource resource = null; + String downloadUrl = task.getDownloadUrl(); + if(downloadUrl == null || downloadUrl.isEmpty()){ + log.error("download url is null"); + throw new RuntimeException("download url is null"); + } + + File file = new File(downloadUrl); + if (!file.exists() || !file.canRead()) { + log.error("file not exists or not readable: {}", downloadUrl); + throw new RuntimeException("Could not read the file: " + downloadUrl); + } + + Resource resource; try { - resource = new UrlResource("file://"+task.getData().getDownloadUrl()); + resource = new UrlResource(file.toURI()); } catch (MalformedURLException e) { + log.error("malformed url: {}", downloadUrl, e); throw new RuntimeException(e); } - if (resource.exists() || resource.isReadable()) { - return ResponseEntity.ok() - .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"") - .contentType(MediaType.APPLICATION_OCTET_STREAM) - .body(resource); - } else { - throw new RuntimeException("Could not read the file!"); - } - + String filename = file.getName(); + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\"") + .contentType(MediaType.APPLICATION_OCTET_STREAM) + .body(resource); } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/TransferController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/TransferController.java new file mode 100644 index 000000000..7cc079f34 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/TransferController.java @@ -0,0 +1,27 @@ +package ai.chat2db.server.web.api.controller.task; + +import ai.chat2db.server.tools.base.wrapper.result.DataResult; +import ai.chat2db.server.web.api.controller.task.biz.TransferBizService; +import ai.chat2db.server.web.api.controller.task.request.DataTransferRequest; +import jakarta.validation.Valid; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 数据传输控制器 + */ +@RequestMapping("/api/transfer") +@RestController +public class TransferController { + + @Autowired + private TransferBizService transferBizService; + + @PostMapping("/data") + public DataResult transferData(@Valid @RequestBody DataTransferRequest request) { + return transferBizService.transferData(request); + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/AbstractExportStrategy.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/AbstractExportStrategy.java new file mode 100644 index 000000000..19c19f1c7 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/AbstractExportStrategy.java @@ -0,0 +1,56 @@ +package ai.chat2db.server.web.api.controller.task.biz; + +import ai.chat2db.server.tools.base.excption.BusinessException; +import ai.chat2db.spi.jdbc.DefaultValueHandler; +import ai.chat2db.spi.sql.Chat2DBContext; +import lombok.extern.slf4j.Slf4j; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +@Slf4j +public abstract class AbstractExportStrategy implements ExportStrategy { + + @Override + public void exportData(ExportContext exportContext) { + DefaultValueHandler valueHandler = new DefaultValueHandler(); + Connection connection = Chat2DBContext.getConnection(); + + try (PreparedStatement ps = createStreamStatement(connection, exportContext.getSql()); + ResultSet rs = ps.executeQuery()) { + + ResultSetMetaData metaData = rs.getMetaData(); + int columnCount = metaData.getColumnCount(); + + List headerNames = new ArrayList<>(columnCount); + for (int i = 1; i <= columnCount; i++) { + headerNames.add(metaData.getColumnLabel(i)); + } + + doExport(rs, metaData, headerNames, columnCount, valueHandler, exportContext); + + } catch (SQLException e) { + log.error("export data streaming error, strategy: {}", this.getClass().getSimpleName(), e); + throw new BusinessException("dataSource.exportError", new Object[]{e.getMessage()}, e); + } + } + + protected PreparedStatement createStreamStatement(Connection connection, String sql) throws SQLException { + PreparedStatement ps = connection.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); + ps.setFetchSize(Integer.MIN_VALUE); + return ps; + } + + protected abstract void doExport(ResultSet rs, ResultSetMetaData metaData, List headerNames, + int columnCount, DefaultValueHandler valueHandler, + ExportContext exportContext) throws SQLException; + + protected void updateProgress(int processedCount, ExportContext exportContext) { + exportContext.getProgressUpdater().accept(processedCount); + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/AbstractImportStrategy.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/AbstractImportStrategy.java new file mode 100644 index 000000000..e1804a859 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/AbstractImportStrategy.java @@ -0,0 +1,304 @@ +package ai.chat2db.server.web.api.controller.task.biz; + +import ai.chat2db.server.tools.base.excption.BusinessException; +import ai.chat2db.server.web.api.controller.task.request.FieldMapping; +import ai.chat2db.spi.SqlBuilder; +import ai.chat2db.spi.sql.Chat2DBContext; +import com.alibaba.excel.EasyExcel; +import com.alibaba.excel.context.AnalysisContext; +import com.alibaba.excel.metadata.data.ReadCellData; +import com.alibaba.excel.read.listener.ReadListener; +import lombok.extern.slf4j.Slf4j; + +import java.io.File; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +@Slf4j +public abstract class AbstractImportStrategy implements ImportStrategy { + + @Override + public void importData(File file, ImportContext importContext) { + final AtomicInteger processedCount = new AtomicInteger(0); + final List fileHeaders = new ArrayList<>(); + + try { + // REPLACE 模式:先清空表 + if ("REPLACE".equals(importContext.getImportMode())) { + truncateTable(importContext); + } + } catch (SQLException e) { + log.error("truncate table error", e); + throw new BusinessException("dataSource.importError", new Object[]{e.getMessage()}, e); + } + + try (PreparedStatement ps = prepareStatement(importContext)) { + + EasyExcel.read(file, new ReadListener>() { + + @Override + public void invokeHead(Map> headMap, AnalysisContext context) { + for (Map.Entry> entry : headMap.entrySet()) { + fileHeaders.add(entry.getValue().getStringValue().trim()); + } + + validateMappings(fileHeaders, importContext); + + log.info("import file headers: {}, target columns: {}", fileHeaders, importContext.getHeaderList()); + } + + @Override + public void invoke(Map data, AnalysisContext context) { + try { + Map sourceData = new LinkedHashMap<>(); + for (Map.Entry entry : data.entrySet()) { + if (entry.getKey() < fileHeaders.size()) { + sourceData.put(fileHeaders.get(entry.getKey()), entry.getValue()); + } + } + + Map rowData = convertToTargetData(sourceData, importContext); + + setParameters(ps, rowData, importContext); + ps.addBatch(); + + int count = processedCount.incrementAndGet(); + if (count % 200 == 0) { + ps.executeBatch(); + ps.clearBatch(); + importContext.getProgressUpdater().accept(count); + } + } catch (SQLException e) { + log.error("import data batch error at row {}, data: {}", processedCount.get() + 1, data, e); + throw new BusinessException("dataSource.importRowError", + new Object[]{processedCount.get() + 1, e.getMessage()}, e); + } + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + try { + ps.executeBatch(); + ps.clearBatch(); + importContext.getProgressUpdater().accept(processedCount.get()); + } catch (SQLException e) { + log.error("import data final batch error", e); + throw new BusinessException("dataSource.importError", new Object[]{e.getMessage()}, e); + } + } + }).sheet().doRead(); + } catch (SQLException e) { + log.error("import data error, strategy: {}", this.getClass().getSimpleName(), e); + throw new BusinessException("dataSource.importError", new Object[]{e.getMessage()}, e); + } + } + + private void setParameters(PreparedStatement ps, Map rowData, ImportContext importContext) + throws SQLException { + String mode = importContext.getImportMode(); + List allColumns = importContext.getHeaderList(); + List pkColumns = importContext.getPrimaryKeyColumns(); + + int paramIndex = 1; + if ("UPDATE".equals(mode)) { + // UPDATE: SET 非主键列 WHERE 主键列 + for (String col : allColumns) { + if (pkColumns == null || !pkColumns.contains(col)) { + ps.setObject(paramIndex++, normalizeValue(col, rowData.getOrDefault(col, null), importContext)); + } + } + if (pkColumns != null) { + for (String pk : pkColumns) { + ps.setObject(paramIndex++, normalizeValue(pk, rowData.getOrDefault(pk, null), importContext)); + } + } else { + for (String col : allColumns) { + ps.setObject(paramIndex++, normalizeValue(col, rowData.getOrDefault(col, null), importContext)); + } + } + } else if ("DELETE".equals(mode)) { + // DELETE: WHERE 主键列(或所有列) + if (pkColumns != null && !pkColumns.isEmpty()) { + for (String pk : pkColumns) { + ps.setObject(paramIndex++, normalizeValue(pk, rowData.getOrDefault(pk, null), importContext)); + } + } else { + for (String col : allColumns) { + ps.setObject(paramIndex++, normalizeValue(col, rowData.getOrDefault(col, null), importContext)); + } + } + } else { + // INSERT / UPSERT / INSERT_IGNORE / REPLACE(已转为INSERT): 所有列 + for (String col : allColumns) { + ps.setObject(paramIndex++, normalizeValue(col, rowData.getOrDefault(col, null), importContext)); + } + } + } + + private Object normalizeValue(String columnName, String rawValue, ImportContext importContext) { + if (rawValue == null) { + return null; + } + + String value = rawValue.trim(); + if (value.isEmpty()) { + return null; + } + + String dataType = null; + if (importContext.getColumnTypeMap() != null) { + dataType = importContext.getColumnTypeMap().get(columnName); + } + if (dataType == null) { + return value; + } + + String lowerType = dataType.toLowerCase(); + if (isNumericType(lowerType)) { + String normalizedBoolean = normalizeBooleanToNumeric(value); + return normalizedBoolean != null ? normalizedBoolean : value; + } + + if (isBooleanType(lowerType)) { + String normalizedBoolean = normalizeBooleanToNumeric(value); + if (normalizedBoolean != null) { + return "1".equals(normalizedBoolean); + } + } + + return value; + } + + private boolean isNumericType(String lowerType) { + return Arrays.asList("tinyint", "smallint", "mediumint", "int", "integer", "bigint", "decimal", "numeric", + "float", "double", "real", "bit").stream().anyMatch(lowerType::contains); + } + + private boolean isBooleanType(String lowerType) { + return lowerType.contains("boolean") || lowerType.contains("bool"); + } + + private String normalizeBooleanToNumeric(String value) { + String lowerValue = value.toLowerCase(); + if (Arrays.asList("true", "yes", "y", "on").contains(lowerValue)) { + return "1"; + } + if (Arrays.asList("false", "no", "n", "off").contains(lowerValue)) { + return "0"; + } + return null; + } + + private void validateMappings(List fileHeaders, ImportContext importContext) { + List mappings = importContext.getFieldMappings(); + if (mappings == null || mappings.isEmpty()) { + Map columnOrderMap = importContext.getColumnOrderMap(); + List missingColumns = new ArrayList<>(); + for (String fileHeader : fileHeaders) { + if (!columnOrderMap.containsKey(fileHeader)) { + missingColumns.add(fileHeader); + } + } + if (!missingColumns.isEmpty()) { + String missingColsStr = missingColumns.stream().collect(Collectors.joining(", ")); + throw new BusinessException("dataSource.importColumnNotFound", + new Object[]{missingColsStr}); + } + return; + } + + Map columnOrderMap = importContext.getColumnOrderMap(); + List invalidTargets = new ArrayList<>(); + for (FieldMapping mapping : mappings) { + if (!columnOrderMap.containsKey(mapping.getTargetField())) { + invalidTargets.add(mapping.getTargetField()); + } + } + if (!invalidTargets.isEmpty()) { + String invalidStr = invalidTargets.stream().collect(Collectors.joining(", ")); + throw new BusinessException("dataSource.importInvalidTargetField", + new Object[]{invalidStr}); + } + } + + private Map convertToTargetData(Map sourceData, ImportContext importContext) { + Map rowData = new LinkedHashMap<>(); + List mappings = importContext.getFieldMappings(); + + if (mappings == null || mappings.isEmpty()) { + for (String columnName : importContext.getHeaderList()) { + rowData.put(columnName, sourceData.getOrDefault(columnName, null)); + } + return rowData; + } + + Map targetToSourceMap = new LinkedHashMap<>(); + for (FieldMapping mapping : mappings) { + targetToSourceMap.put(mapping.getTargetField(), mapping.getSourceField()); + } + + for (String targetColumn : importContext.getHeaderList()) { + String sourceField = targetToSourceMap.get(targetColumn); + if (sourceField != null) { + rowData.put(targetColumn, sourceData.getOrDefault(sourceField, null)); + } else { + rowData.put(targetColumn, null); + } + } + + return rowData; + } + + public List readFileHeaders(File file) { + List headers = new ArrayList<>(); + EasyExcel.read(file, new ReadListener>() { + @Override + public void invokeHead(Map> headMap, AnalysisContext context) { + for (Map.Entry> entry : headMap.entrySet()) { + headers.add(entry.getValue().getStringValue().trim()); + } + } + + @Override + public void invoke(Map data, AnalysisContext context) { + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + } + }).sheet().doRead(); + return headers; + } + + protected PreparedStatement prepareStatement(ImportContext importContext) throws SQLException { + SqlBuilder sqlBuilder = Chat2DBContext.getSqlBuilder(); + String mode = importContext.getImportMode(); + if ("REPLACE".equals(mode)) { + mode = "INSERT"; + } + String sql = sqlBuilder.buildImportSql( + importContext.getTableName(), + importContext.getHeaders(), + importContext.getPrimaryKeyColumns(), + mode + ); + log.info("import SQL: {}", sql); + return importContext.getConnection().prepareStatement(sql); + } + + protected void truncateTable(ImportContext importContext) throws SQLException { + String sql = "TRUNCATE TABLE " + importContext.getTableName(); + log.info("truncate table SQL: {}", sql); + try (Statement stmt = importContext.getConnection().createStatement()) { + stmt.execute(sql); + } + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/CsvExportStrategy.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/CsvExportStrategy.java new file mode 100644 index 000000000..f727c055b --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/CsvExportStrategy.java @@ -0,0 +1,60 @@ +package ai.chat2db.server.web.api.controller.task.biz; + +import ai.chat2db.server.tools.common.util.EasyCollectionUtils; +import ai.chat2db.server.web.api.model.ExcelWrapper; +import ai.chat2db.spi.jdbc.DefaultValueHandler; +import com.alibaba.excel.EasyExcel; +import com.alibaba.excel.support.ExcelTypeEnum; +import com.alibaba.excel.write.builder.ExcelWriterBuilder; +import com.alibaba.excel.write.metadata.WriteSheet; +import com.google.common.collect.Lists; +import org.springframework.stereotype.Component; + +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +@Component +public class CsvExportStrategy extends AbstractExportStrategy { + + @Override + public void doExport(ResultSet rs, ResultSetMetaData metaData, List headerNames, + int columnCount, DefaultValueHandler valueHandler, + ExportContext exportContext) throws SQLException { + + ExcelWrapper excelWrapper = new ExcelWrapper(); + try { + ExcelWriterBuilder excelWriterBuilder = EasyExcel.write(exportContext.getFile()) + .charset(java.nio.charset.StandardCharsets.UTF_8) + .excelType(ExcelTypeEnum.CSV); + excelWriterBuilder.head(EasyCollectionUtils.toList(headerNames, name -> Lists.newArrayList(name))); + excelWrapper.setExcelWriter(excelWriterBuilder.build()); + excelWrapper.setWriteSheet(EasyExcel.writerSheet(0).build()); + + int processedCount = 0; + while (rs.next()) { + List row = new ArrayList<>(columnCount); + for (int i = 1; i <= columnCount; i++) { + row.add(valueHandler.getString(rs, i, false)); + } + List> writeDataList = Lists.newArrayList(); + writeDataList.add(row); + excelWrapper.getExcelWriter().write(writeDataList, excelWrapper.getWriteSheet()); + processedCount++; + updateProgress(processedCount, exportContext); + } + updateProgress(processedCount, exportContext); + } finally { + if (excelWrapper.getExcelWriter() != null) { + excelWrapper.getExcelWriter().finish(); + } + } + } + + @Override + public boolean supports(String exportType) { + return "CSV".equalsIgnoreCase(exportType); + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/CsvImportStrategy.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/CsvImportStrategy.java new file mode 100644 index 000000000..f679164d1 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/CsvImportStrategy.java @@ -0,0 +1,12 @@ +package ai.chat2db.server.web.api.controller.task.biz; + +import org.springframework.stereotype.Component; + +@Component +public class CsvImportStrategy extends AbstractImportStrategy { + + @Override + public boolean supports(String fileType) { + return "CSV".equalsIgnoreCase(fileType); + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/ExcelExportStrategy.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/ExcelExportStrategy.java new file mode 100644 index 000000000..7c2e4b9e5 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/ExcelExportStrategy.java @@ -0,0 +1,59 @@ +package ai.chat2db.server.web.api.controller.task.biz; + +import ai.chat2db.server.tools.common.util.EasyCollectionUtils; +import ai.chat2db.server.web.api.model.ExcelWrapper; +import ai.chat2db.spi.jdbc.DefaultValueHandler; +import com.alibaba.excel.EasyExcel; +import com.alibaba.excel.support.ExcelTypeEnum; +import com.alibaba.excel.write.builder.ExcelWriterBuilder; +import com.alibaba.excel.write.metadata.WriteSheet; +import com.google.common.collect.Lists; +import org.springframework.stereotype.Component; + +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +@Component +public class ExcelExportStrategy extends AbstractExportStrategy { + + @Override + public void doExport(ResultSet rs, ResultSetMetaData metaData, List headerNames, + int columnCount, DefaultValueHandler valueHandler, + ExportContext exportContext) throws SQLException { + + ExcelWrapper excelWrapper = new ExcelWrapper(); + try { + ExcelWriterBuilder excelWriterBuilder = EasyExcel.write(exportContext.getFile()) + .excelType(ExcelTypeEnum.XLSX); + excelWriterBuilder.head(EasyCollectionUtils.toList(headerNames, name -> Lists.newArrayList(name))); + excelWrapper.setExcelWriter(excelWriterBuilder.build()); + excelWrapper.setWriteSheet(EasyExcel.writerSheet(0).build()); + + int processedCount = 0; + while (rs.next()) { + List row = new ArrayList<>(columnCount); + for (int i = 1; i <= columnCount; i++) { + row.add(valueHandler.getString(rs, i, false)); + } + List> writeDataList = Lists.newArrayList(); + writeDataList.add(row); + excelWrapper.getExcelWriter().write(writeDataList, excelWrapper.getWriteSheet()); + processedCount++; + updateProgress(processedCount, exportContext); + } + updateProgress(processedCount, exportContext); + } finally { + if (excelWrapper.getExcelWriter() != null) { + excelWrapper.getExcelWriter().finish(); + } + } + } + + @Override + public boolean supports(String exportType) { + return "EXCEL".equalsIgnoreCase(exportType); + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/ExcelImportStrategy.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/ExcelImportStrategy.java new file mode 100644 index 000000000..55ce6a4f0 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/ExcelImportStrategy.java @@ -0,0 +1,12 @@ +package ai.chat2db.server.web.api.controller.task.biz; + +import org.springframework.stereotype.Component; + +@Component +public class ExcelImportStrategy extends AbstractImportStrategy { + + @Override + public boolean supports(String fileType) { + return "XLSX".equalsIgnoreCase(fileType) || "XLS".equalsIgnoreCase(fileType); + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/ExportContext.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/ExportContext.java new file mode 100644 index 000000000..a37bff801 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/ExportContext.java @@ -0,0 +1,19 @@ +package ai.chat2db.server.web.api.controller.task.biz; + +import com.alibaba.druid.DbType; +import lombok.Builder; +import lombok.Data; + +import java.io.File; +import java.util.function.Consumer; + +@Data +@Builder +public class ExportContext { + private String sql; + private File file; + private DbType dbType; + private String tableName; + private Long taskId; + private Consumer progressUpdater; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/ExportStrategy.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/ExportStrategy.java new file mode 100644 index 000000000..fe5f65e0f --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/ExportStrategy.java @@ -0,0 +1,8 @@ +package ai.chat2db.server.web.api.controller.task.biz; + +public interface ExportStrategy { + + void exportData(ExportContext exportContext); + + boolean supports(String exportType); +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/ExportStrategyFactory.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/ExportStrategyFactory.java new file mode 100644 index 000000000..f6aa7928a --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/ExportStrategyFactory.java @@ -0,0 +1,26 @@ +package ai.chat2db.server.web.api.controller.task.biz; + +import ai.chat2db.server.tools.base.excption.BusinessException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import jakarta.annotation.PostConstruct; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Component +public class ExportStrategyFactory { + + @Autowired + private List exportStrategies; + + public ExportStrategy getStrategy(String exportType) { + for (ExportStrategy strategy : exportStrategies) { + if (strategy.supports(exportType)) { + return strategy; + } + } + throw new BusinessException("dataSource.exportTypeNotSupported", new Object[]{exportType}); + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/ImportBizService.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/ImportBizService.java new file mode 100644 index 000000000..4f52eb7af --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/ImportBizService.java @@ -0,0 +1,309 @@ +package ai.chat2db.server.web.api.controller.task.biz; + +import ai.chat2db.server.domain.api.enums.TaskStatusEnum; +import ai.chat2db.server.domain.api.enums.TaskTypeEnum; +import ai.chat2db.server.domain.api.param.TaskCreateParam; +import ai.chat2db.server.domain.api.param.TableQueryParam; +import ai.chat2db.server.domain.api.param.TaskUpdateParam; +import ai.chat2db.server.domain.api.service.TableService; +import ai.chat2db.server.domain.api.service.TaskService; +import ai.chat2db.server.domain.repository.Dbutils; +import ai.chat2db.server.tools.common.util.I18nUtils; +import ai.chat2db.server.web.api.controller.task.request.DataImportRequest; +import ai.chat2db.server.web.api.controller.task.request.FieldMapping; +import ai.chat2db.server.web.api.controller.task.request.FilePreviewRequest; +import ai.chat2db.server.web.api.controller.task.response.FilePreviewResult; +import ai.chat2db.server.web.api.controller.task.response.TableColumnInfo; +import ai.chat2db.spi.model.TableColumn; +import ai.chat2db.server.tools.base.excption.BusinessException; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; +import ai.chat2db.server.tools.common.model.Context; +import ai.chat2db.server.tools.common.model.LoginUser; +import ai.chat2db.server.tools.common.util.ContextUtils; +import ai.chat2db.spi.model.Header; +import ai.chat2db.spi.sql.Chat2DBContext; +import ai.chat2db.spi.sql.ConnectInfo; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.Assert; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.sql.Connection; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +@Slf4j +@Component +public class ImportBizService { + + private static final ObjectMapper objectMapper = new ObjectMapper(); + + @Autowired + private TaskService taskService; + + @Autowired + private TableService tableService; + + @Autowired + private ImportStrategyFactory strategyFactory; + + public DataResult importData(MultipartFile file, DataImportRequest request) { + Assert.notNull(file, "file can not be null"); + Assert.notBlank(request.getTableName(), "tableName can not be blank"); + if ("SQL".equalsIgnoreCase(request.getFileType())) { + throw new BusinessException("dataSource.importSqlNotSupported"); + } + + DataResult dataResult = createImportTask(request); + + LoginUser loginUser = ContextUtils.getLoginUser(); + ConnectInfo connectInfo = Chat2DBContext.getConnectInfo().copy(); + + // 同步保存文件到安全位置,避免异步执行时临时文件被清理 + File safeFile; + try { + String originalFilename = file.getOriginalFilename(); + if (originalFilename == null) { + originalFilename = "import_file"; + } + safeFile = FileUtil.createTempFile(originalFilename, "", true); + file.transferTo(safeFile); + } catch (Exception e) { + log.error("save upload file error", e); + throw new BusinessException("dataSource.importError", new Object[]{e.getMessage()}, e); + } + + final File finalSafeFile = safeFile; + + CompletableFuture.runAsync(() -> { + buildContext(loginUser, connectInfo); + try { + doImportData(finalSafeFile, request, dataResult.getData()); + } finally { + // 确保清理临时文件 + FileUtil.del(finalSafeFile); + } + }).whenComplete((aVoid, throwable) -> { + updateImportStatus(dataResult.getData(), throwable); + removeContext(); + }); + + return dataResult; + } + + /** + * 预览文件表头和目标表字段 + */ + public DataResult previewHeaders(MultipartFile file, FilePreviewRequest request) { + Assert.notNull(file, "file can not be null"); + Assert.notBlank(request.getTableName(), "tableName can not be blank"); + + // 保存临时文件 + File tempFile; + try { + String originalFilename = file.getOriginalFilename(); + if (originalFilename == null) { + originalFilename = "preview_file"; + } + tempFile = FileUtil.createTempFile(originalFilename, "", true); + file.transferTo(tempFile); + } catch (Exception e) { + log.error("save upload file error", e); + throw new BusinessException("dataSource.importError", new Object[]{e.getMessage()}, e); + } + + try { + FilePreviewResult result = new FilePreviewResult(); + + // 获取文件表头 + String fileType = request.getFileType().toUpperCase(); + ImportStrategy strategy = strategyFactory.getStrategy(fileType); + if (strategy instanceof AbstractImportStrategy abstractStrategy) { + List fileHeaders = abstractStrategy.readFileHeaders(tempFile); + result.setFileHeaders(fileHeaders); + } else { + throw new BusinessException("dataSource.unsupportedFileType", new Object[]{fileType}); + } + + // 获取目标表字段 + List columns = getColumnList(request.getDataSourceId(), request.getDatabaseName(), + request.getSchemaName(), request.getTableName()); + List tableColumns = columns.stream().map(col -> { + TableColumnInfo info = new TableColumnInfo(); + info.setName(col.getName()); + info.setType(col.getColumnType()); + info.setPrimaryKey(Boolean.TRUE.equals(col.getPrimaryKey())); + return info; + }).collect(Collectors.toList()); + result.setTableColumns(tableColumns); + + // 自动匹配同名字段 + List fileHeaders = result.getFileHeaders(); + Map columnMap = columns.stream() + .collect(Collectors.toMap(TableColumn::getName, col -> col)); + List autoMappings = new ArrayList<>(); + for (String fileHeader : fileHeaders) { + FilePreviewResult.AutoMapping mapping = new FilePreviewResult.AutoMapping(); + mapping.setSourceField(fileHeader); + if (columnMap.containsKey(fileHeader)) { + mapping.setTargetField(fileHeader); + mapping.setMatched(true); + } else { + mapping.setTargetField(""); + mapping.setMatched(false); + } + autoMappings.add(mapping); + } + result.setAutoMappings(autoMappings); + + return DataResult.of(result); + } finally { + FileUtil.del(tempFile); + } + } + + private DataResult createImportTask(DataImportRequest request) { + TaskCreateParam param = new TaskCreateParam(); + param.setTaskName("import_" + request.getTableName()); + param.setTaskType(TaskTypeEnum.UPLOAD_TABLE_DATA.name()); + param.setDatabaseName(request.getDatabaseName()); + param.setSchemaName(request.getSchemaName()); + param.setTableName(request.getTableName()); + param.setDataSourceId(request.getDataSourceId()); + param.setUserId(ContextUtils.getUserId()); + param.setTaskProgress("0"); + Long taskId = taskService.create(param); + return DataResult.of(taskId); + } + + private void updateImportStatus(Long id, Throwable throwable) { + TaskUpdateParam updateParam = new TaskUpdateParam(); + updateParam.setId(id); + if (throwable != null) { + log.error("import error", throwable); + updateParam.setTaskStatus(TaskStatusEnum.ERROR.name()); + if (throwable.getCause() instanceof BusinessException businessException) { + updateParam.setContent(I18nUtils.getMessage(businessException.getCode(), businessException.getArgs())); + } else { + updateParam.setContent(throwable.getMessage()); + } + } else { + updateParam.setTaskStatus(TaskStatusEnum.FINISH.name()); + } + taskService.updateStatus(updateParam); + } + + private void doImportData(File file, DataImportRequest request, Long taskId) { + String fileType = request.getFileType().toUpperCase(); + + List columns = getColumnList(request.getDataSourceId(), request.getDatabaseName(), + request.getSchemaName(), request.getTableName()); + List headerList = columns.stream().map(TableColumn::getName).collect(Collectors.toList()); + + // 解析字段映射配置 + List fieldMappings = parseFieldMappings(request.getFieldMappings()); + + // 提取主键列 + List primaryKeyColumns = columns.stream() + .filter(col -> Boolean.TRUE.equals(col.getPrimaryKey())) + .map(TableColumn::getName) + .collect(Collectors.toList()); + + // 构建Header列表用于SqlBuilder + List
headers = columns.stream().map(col -> Header.builder() + .name(col.getName()) + .dataType(col.getColumnType()) + .primaryKey(col.getPrimaryKey()) + .build()).collect(Collectors.toList()); + + // 设置默认导入模式 + String importMode = request.getImportMode(); + if (importMode == null || importMode.isBlank()) { + importMode = "INSERT"; + } + + ImportStrategy strategy = strategyFactory.getStrategy(fileType); + + Connection connection = Chat2DBContext.getConnection(); + + Map columnOrderMap = new HashMap<>(); + Map columnTypeMap = new HashMap<>(); + for (int i = 0; i < headerList.size(); i++) { + columnOrderMap.put(headerList.get(i), i); + } + for (TableColumn column : columns) { + columnTypeMap.put(column.getName(), column.getColumnType()); + } + + ImportContext importContext = ImportContext.builder() + .taskId(taskId) + .tableName(request.getTableName()) + .headerList(headerList) + .columnOrderMap(columnOrderMap) + .columnTypeMap(columnTypeMap) + .columnCount(headerList.size()) + .connection(connection) + .progressUpdater(count -> updateProgressCount(taskId, count)) + .fieldMappings(fieldMappings) + .importMode(importMode) + .primaryKeyColumns(primaryKeyColumns) + .headers(headers) + .build(); + strategy.importData(file, importContext); + } + + /** + * 解析字段映射配置 + */ + private List parseFieldMappings(String fieldMappingsJson) { + if (fieldMappingsJson == null || fieldMappingsJson.isBlank()) { + return null; + } + try { + return objectMapper.readValue(fieldMappingsJson, new TypeReference>() {}); + } catch (Exception e) { + log.error("parse field mappings error", e); + throw new BusinessException("dataSource.invalidFieldMapping", new Object[]{e.getMessage()}); + } + } + + private List getColumnList(Long dataSourceId, String databaseName, String schemaName, String tableName) { + TableQueryParam queryParam = new TableQueryParam(); + queryParam.setDataSourceId(dataSourceId); + queryParam.setDatabaseName(databaseName); + queryParam.setSchemaName(schemaName); + queryParam.setTableName(tableName); + return tableService.queryColumns(queryParam); + } + + private void updateProgressCount(Long taskId, int processedCount) { + TaskUpdateParam updateParam = new TaskUpdateParam(); + updateParam.setId(taskId); + updateParam.setTaskProgress(String.valueOf(processedCount)); + taskService.updateStatus(updateParam); + } + + private void removeContext() { + Dbutils.removeSession(); + ContextUtils.removeContext(); + Chat2DBContext.removeContext(); + } + + private void buildContext(LoginUser loginUser, ConnectInfo connectInfo) { + ContextUtils.setContext(Context.builder() + .loginUser(loginUser) + .build()); + Dbutils.setSession(); + Chat2DBContext.putContext(connectInfo); + } + +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/ImportContext.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/ImportContext.java new file mode 100644 index 000000000..1c62c1788 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/ImportContext.java @@ -0,0 +1,51 @@ +package ai.chat2db.server.web.api.controller.task.biz; + +import ai.chat2db.server.web.api.controller.task.request.FieldMapping; +import ai.chat2db.spi.model.Header; +import lombok.Builder; +import lombok.Data; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +@Data +@Builder +public class ImportContext { + private Long taskId; + private String tableName; + private List headerList; + private Map columnOrderMap; + private Map columnTypeMap; + private int columnCount; + private Connection connection; + private Consumer progressUpdater; + + /** + * 字段映射配置列表 + */ + private List fieldMappings; + + /** + * 源字段到目标字段的映射 Map + * key: 源字段名, value: 目标字段名 + */ + private Map sourceToTargetMap; + + /** + * 导入模式 + */ + private String importMode; + + /** + * 主键列名列表 + */ + private List primaryKeyColumns; + + /** + * 表头元数据列表(用于SqlBuilder生成SQL) + */ + private List
headers; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/ImportStrategy.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/ImportStrategy.java new file mode 100644 index 000000000..4d44caad5 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/ImportStrategy.java @@ -0,0 +1,10 @@ +package ai.chat2db.server.web.api.controller.task.biz; + +import java.io.File; + +public interface ImportStrategy { + + void importData(File file, ImportContext context); + + boolean supports(String fileType); +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/ImportStrategyFactory.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/ImportStrategyFactory.java new file mode 100644 index 000000000..643657838 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/ImportStrategyFactory.java @@ -0,0 +1,23 @@ +package ai.chat2db.server.web.api.controller.task.biz; + +import ai.chat2db.server.tools.base.excption.BusinessException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +public class ImportStrategyFactory { + + @Autowired + private List strategies; + + public ImportStrategy getStrategy(String fileType) { + for (ImportStrategy strategy : strategies) { + if (strategy.supports(fileType)) { + return strategy; + } + } + throw new BusinessException("dataSource.unsupportedFileType", new Object[]{fileType}); + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/InsertExportStrategy.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/InsertExportStrategy.java new file mode 100644 index 000000000..54ddb9bae --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/InsertExportStrategy.java @@ -0,0 +1,69 @@ +package ai.chat2db.server.web.api.controller.task.biz; + +import ai.chat2db.spi.jdbc.DefaultValueHandler; +import com.alibaba.druid.DbType; +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; +import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; +import com.alibaba.druid.sql.ast.statement.SQLInsertStatement; +import com.alibaba.druid.sql.visitor.VisitorFeature; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +@Component +public class InsertExportStrategy extends AbstractExportStrategy { + + private static final SQLUtils.FormatOption INSERT_FORMAT_OPTION = new SQLUtils.FormatOption(true, false); + + static { + INSERT_FORMAT_OPTION.config(VisitorFeature.OutputNameQuote, true); + } + + @Override + public void doExport(ResultSet rs, ResultSetMetaData metaData, List headerNames, + int columnCount, DefaultValueHandler valueHandler, + ExportContext exportContext) throws SQLException { + + try (PrintWriter printWriter = new PrintWriter(exportContext.getFile(), StandardCharsets.UTF_8)) { + + List headerList = new ArrayList<>(columnCount); + for (String headerName : headerNames) { + headerList.add(new SQLIdentifierExpr(headerName)); + } + + int processedCount = 0; + while (rs.next()) { + SQLInsertStatement sqlInsertStatement = new SQLInsertStatement(); + sqlInsertStatement.setDbType(exportContext.getDbType()); + sqlInsertStatement.setTableSource(new SQLExprTableSource(exportContext.getTableName())); + sqlInsertStatement.getColumns().addAll(headerList); + + SQLInsertStatement.ValuesClause valuesClause = new SQLInsertStatement.ValuesClause(); + for (int i = 1; i <= columnCount; i++) { + valuesClause.addValue(valueHandler.getString(rs, i, false)); + } + sqlInsertStatement.setValues(valuesClause); + + printWriter.println(SQLUtils.toSQLString(sqlInsertStatement, exportContext.getDbType(), INSERT_FORMAT_OPTION) + ";"); + processedCount++; + updateProgress(processedCount, exportContext); + } + updateProgress(processedCount, exportContext); + } catch (IOException e) { + throw new SQLException("Failed to write INSERT export file", e); + } + } + + @Override + public boolean supports(String exportType) { + return "INSERT".equalsIgnoreCase(exportType); + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/TaskBizService.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/TaskBizService.java index 03da5b96a..0e58913e8 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/TaskBizService.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/TaskBizService.java @@ -6,24 +6,21 @@ import ai.chat2db.server.domain.api.service.TaskService; import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.tools.base.excption.BusinessException; +import ai.chat2db.server.tools.base.wrapper.ServicePage; import ai.chat2db.server.tools.base.wrapper.result.DataResult; -import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.common.model.Context; import ai.chat2db.server.tools.common.model.LoginUser; import ai.chat2db.server.tools.common.util.ContextUtils; -import ai.chat2db.server.tools.common.util.EasyCollectionUtils; -import ai.chat2db.server.web.api.controller.rdb.RdbDmlExportController; import ai.chat2db.server.web.api.controller.rdb.converter.RdbWebConverter; -import ai.chat2db.server.web.api.controller.rdb.doc.DatabaseExportService; import ai.chat2db.server.web.api.controller.rdb.doc.conf.ExportOptions; -import ai.chat2db.server.web.api.controller.rdb.factory.ExportServiceFactory; import ai.chat2db.server.web.api.controller.rdb.request.DataExportRequest; import ai.chat2db.server.web.api.controller.rdb.vo.TableVO; -import ai.chat2db.spi.jdbc.DefaultValueHandler; +import ai.chat2db.server.web.api.controller.task.biz.doc.SchemaDocExportContext; +import ai.chat2db.server.web.api.controller.task.biz.doc.SchemaDocExportStrategy; +import ai.chat2db.server.web.api.controller.task.biz.doc.SchemaDocExportStrategyFactory; import ai.chat2db.spi.model.Table; import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.sql.ConnectInfo; -import ai.chat2db.spi.sql.SQLExecutor; import ai.chat2db.spi.util.JdbcUtils; import ai.chat2db.spi.util.SqlUtils; import cn.hutool.core.date.DatePattern; @@ -32,31 +29,14 @@ import com.alibaba.druid.DbType; import com.alibaba.druid.sql.SQLUtils; import com.alibaba.druid.sql.ast.SQLStatement; -import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; -import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; -import com.alibaba.druid.sql.ast.statement.SQLInsertStatement; import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; -import com.alibaba.druid.sql.visitor.VisitorFeature; -import com.alibaba.excel.EasyExcel; -import com.alibaba.excel.ExcelWriter; -import com.alibaba.excel.support.ExcelTypeEnum; -import com.alibaba.excel.write.builder.ExcelWriterBuilder; -import com.alibaba.excel.write.metadata.WriteSheet; import com.google.common.collect.Lists; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.PrintWriter; -import java.lang.reflect.Constructor; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.time.LocalDateTime; @@ -67,39 +47,36 @@ @Component public class TaskBizService { - /** - * Format insert statement - */ - private static final SQLUtils.FormatOption INSERT_FORMAT_OPTION = new SQLUtils.FormatOption(true, false); - - static { - INSERT_FORMAT_OPTION.config(VisitorFeature.OutputNameQuote, true); - } - - @Autowired private TaskService taskService; - @Autowired private TableService tableService; @Autowired private RdbWebConverter rdbWebConverter; + @Autowired + private ExportStrategyFactory exportStrategyFactory; + + @Autowired + private SchemaDocExportStrategyFactory schemaDocExportStrategyFactory; + public DataResult exportResultData(DataExportRequest request) { String sql = ExportSizeEnum.CURRENT_PAGE.getCode().equals(request.getExportSize()) ? request.getSql() : request.getOriginalSql(); Assert.notBlank(sql, "dataSource.sqlEmpty"); DbType dbType = JdbcUtils.parse2DruidDbType(Chat2DBContext.getConnectInfo().getDbType()); String tableName = getTableName(request, sql, dbType); File file = createTempFile(tableName, request.getExportType()); + DataResult dataResult = createTask(tableName, request.getDatabaseName(), request.getSchemaName(), request.getDataSourceId(), tableName); LoginUser loginUser = ContextUtils.getLoginUser(); ConnectInfo connectInfo = Chat2DBContext.getConnectInfo().copy(); + CompletableFuture.runAsync(() -> { buildContext(loginUser, connectInfo); - doExport(sql, file, dbType, tableName, request.getExportType()); + doExportStreaming(sql, file, dbType, tableName, request.getExportType(), dataResult.getData()); }).whenComplete((aVoid, throwable) -> { updateStatus(dataResult.getData(), file, throwable); removeContext(); @@ -125,32 +102,31 @@ public DataResult exportSchemaDoc(DataExportRequest request) { private void doExportDoc(DataExportRequest request, File file) { try { TablePageQueryParam queryParam = rdbWebConverter.tablePageRequest2param(request); - queryParam.setPageNo(1); - queryParam.setPageSize(Integer.MAX_VALUE); TableSelector tableSelector = new TableSelector(); tableSelector.setColumnList(true); tableSelector.setIndexList(true); - PageResult
tableDTOPageResult = tableService.pageQuery(queryParam, tableSelector); - List tableVOS = rdbWebConverter.tableDto2vo(tableDTOPageResult.getData()); - TableQueryParam param = rdbWebConverter.tableRequest2param(request); - for (TableVO tableVO : tableVOS) { - param.setTableName(tableVO.getName()); - tableVO.setColumnList(tableService.queryColumns(param)); - tableVO.setIndexList(tableService.queryIndexes(param)); - } - Class targetClass = ExportServiceFactory.get(request.getExportType()); - Constructor constructor = targetClass.getDeclaredConstructor(); - DatabaseExportService databaseExportService = (DatabaseExportService) constructor.newInstance(); - // 设置数据集合 - databaseExportService.setExportList(tableVOS); - databaseExportService.generate(request.getDatabaseName(), new FileOutputStream(file), new ExportOptions()); + tableSelector.setForeignKey(true); + List
tables = tableService.pageQuery(queryParam, tableSelector); + + ExportOptions exportOptions = new ExportOptions(); + exportOptions.setIsExportIndex(true); + exportOptions.setIsExportForeignKey(true); + + SchemaDocExportContext context = SchemaDocExportContext.builder() + .tables(tables) + .databaseName(request.getDatabaseName()) + .file(file) + .exportOptions(exportOptions) + .build(); + + SchemaDocExportStrategy strategy = schemaDocExportStrategyFactory.getStrategy(request.getExportType()); + strategy.export(context); } catch (Exception e) { - log.error("export error", e); + log.error("export schema doc error", e); throw new BusinessException("dataSource.exportError"); } } - private void removeContext() { Dbutils.removeSession(); ContextUtils.removeContext(); @@ -174,14 +150,14 @@ private DataResult createTask(String tableName, String databaseName, Strin param.setTableName(tableName); param.setDataSourceId(datasourceId); param.setUserId(ContextUtils.getUserId()); - param.setTaskProgress("0.1"); - return taskService.create(param); + param.setTaskProgress("0"); + Long taskId = taskService.create(param); + return DataResult.of(taskId); } private void updateStatus(Long id, File file, Throwable throwable) { TaskUpdateParam updateParam = new TaskUpdateParam(); updateParam.setId(id); - updateParam.setTaskProgress("1"); updateParam.setDownloadUrl(file.getAbsolutePath()); if (throwable != null) { log.error("export error", throwable); @@ -192,19 +168,27 @@ private void updateStatus(Long id, File file, Throwable throwable) { taskService.updateStatus(updateParam); } - private void doExport(String sql, File file, DbType dbType, String tableName, String exportType) { - try { - if (ExportTypeEnum.CSV.getCode().equals(exportType)) { - doExportCsv(sql, file); - } else { - doExportInsert(sql, file, dbType, tableName); - } - } catch (Exception e) { - log.error("export error", e); - throw new BusinessException("dataSource.exportError"); - } + private void doExportStreaming(String sql, File file, DbType dbType, String tableName, String exportType, Long taskId) { + ExportStrategy strategy = exportStrategyFactory.getStrategy(exportType); + + ExportContext exportContext = ExportContext.builder() + .sql(sql) + .file(file) + .dbType(dbType) + .tableName(tableName) + .taskId(taskId) + .progressUpdater(count -> updateProgressCount(taskId, count)) + .build(); + + strategy.exportData(exportContext); } + private void updateProgressCount(Long taskId, int processedCount) { + TaskUpdateParam updateParam = new TaskUpdateParam(); + updateParam.setId(taskId); + updateParam.setTaskProgress(String.valueOf(processedCount)); + taskService.updateStatus(updateParam); + } private File createTempFile(String tableName, String exportType) { String fileName = URLEncoder.encode( @@ -226,6 +210,8 @@ private File createTempFile(String tableName, String exportType) { return FileUtil.createTempFile(fileName, ExportFileSuffix.PDF.getSuffix(), true); } else if (ExportTypeEnum.HTML.getCode().equals(exportType)) { return FileUtil.createTempFile(fileName, ExportFileSuffix.HTML.getSuffix(), true); + } else if (ExportTypeEnum.SQL.getCode().equals(exportType)) { + return FileUtil.createTempFile(fileName, ExportFileSuffix.SQL.getSuffix(), true); } return FileUtil.createTempFile(fileName, ".txt", true); } @@ -243,70 +229,4 @@ private String getTableName(DataExportRequest request, String sql, DbType dbType } return tableName; } - - private void doExportCsv(String sql, File file) { - RdbDmlExportController.ExcelWrapper excelWrapper = new RdbDmlExportController.ExcelWrapper(); - try { - ExcelWriterBuilder excelWriterBuilder = EasyExcel.write(file) - .charset(StandardCharsets.UTF_8) - .excelType(ExcelTypeEnum.CSV); - excelWrapper.setExcelWriterBuilder(excelWriterBuilder); - SQLExecutor.getInstance().execute(Chat2DBContext.getConnection(), sql, headerList -> { - excelWriterBuilder.head( - EasyCollectionUtils.toList(headerList, header -> Lists.newArrayList(header.getName()))); - excelWrapper.setExcelWriter(excelWriterBuilder.build()); - excelWrapper.setWriteSheet(EasyExcel.writerSheet(0).build()); - }, dataList -> { - List> writeDataList = Lists.newArrayList(); - writeDataList.add(dataList); - excelWrapper.getExcelWriter().write(writeDataList, excelWrapper.getWriteSheet()); - }, false, new DefaultValueHandler()); - } finally { - if (excelWrapper.getExcelWriter() != null) { - excelWrapper.getExcelWriter().finish(); - } - } - } - - private void doExportInsert(String sql, File file, DbType dbType, - String tableName) - throws IOException { - try (PrintWriter printWriter = new PrintWriter(file, StandardCharsets.UTF_8.name())) { - RdbDmlExportController.InsertWrapper insertWrapper = new RdbDmlExportController.InsertWrapper(); - SQLExecutor.getInstance().execute(Chat2DBContext.getConnection(), sql, - headerList -> insertWrapper.setHeaderList( - EasyCollectionUtils.toList(headerList, header -> new SQLIdentifierExpr(header.getName()))) - , dataList -> { - SQLInsertStatement sqlInsertStatement = new SQLInsertStatement(); - sqlInsertStatement.setDbType(dbType); - sqlInsertStatement.setTableSource(new SQLExprTableSource(tableName)); - sqlInsertStatement.getColumns().addAll(insertWrapper.getHeaderList()); - SQLInsertStatement.ValuesClause valuesClause = new SQLInsertStatement.ValuesClause(); - for (String s : dataList) { - valuesClause.addValue(s); - } - sqlInsertStatement.setValues(valuesClause); - - printWriter.println(SQLUtils.toSQLString(sqlInsertStatement, dbType, INSERT_FORMAT_OPTION) + ";"); - }, false, new DefaultValueHandler()); - } - } - - @Data - @SuperBuilder - @NoArgsConstructor - @AllArgsConstructor - public static class InsertWrapper { - private List headerList; - } - - @Data - @SuperBuilder - @NoArgsConstructor - @AllArgsConstructor - public static class ExcelWrapper { - private ExcelWriterBuilder excelWriterBuilder; - private ExcelWriter excelWriter; - private WriteSheet writeSheet; - } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/TransferBizService.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/TransferBizService.java new file mode 100644 index 000000000..74a602659 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/TransferBizService.java @@ -0,0 +1,328 @@ +package ai.chat2db.server.web.api.controller.task.biz; + +import ai.chat2db.server.domain.api.enums.TaskStatusEnum; +import ai.chat2db.server.domain.api.enums.TaskTypeEnum; +import ai.chat2db.server.domain.api.model.DataSource; +import ai.chat2db.server.domain.api.param.TaskCreateParam; +import ai.chat2db.server.domain.api.param.TaskUpdateParam; +import ai.chat2db.server.domain.api.service.DataSourceAccessBusinessService; +import ai.chat2db.server.domain.api.service.DataSourceService; +import ai.chat2db.server.domain.api.service.TaskService; +import ai.chat2db.server.domain.repository.Dbutils; +import ai.chat2db.server.tools.base.excption.BusinessException; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; +import ai.chat2db.server.tools.common.model.Context; +import ai.chat2db.server.tools.common.model.LoginUser; +import ai.chat2db.server.tools.common.util.ContextUtils; +import ai.chat2db.server.web.api.controller.task.request.DataTransferRequest; +import ai.chat2db.spi.Plugin; +import ai.chat2db.spi.sql.Chat2DBContext; +import ai.chat2db.spi.sql.ConnectInfo; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +@Slf4j +@Component +public class TransferBizService { + + private static final int BATCH_SIZE = 200; + + @Autowired + private TaskService taskService; + + @Autowired + private DataSourceService dataSourceService; + + @Autowired + private DataSourceAccessBusinessService dataSourceAccessBusinessService; + + public DataResult transferData(DataTransferRequest request) { + Long taskId = createTransferTask(request); + LoginUser loginUser = ContextUtils.getLoginUser(); + + CompletableFuture.runAsync(() -> { + ContextUtils.setContext(Context.builder().loginUser(loginUser).build()); + Dbutils.setSession(); + doTransferData(request, taskId); + }).whenComplete((aVoid, throwable) -> { + updateTransferStatus(taskId, throwable); + Dbutils.removeSession(); + ContextUtils.removeContext(); + Chat2DBContext.removeContext(); + }); + + return DataResult.of(taskId); + } + + private Long createTransferTask(DataTransferRequest request) { + TaskCreateParam param = new TaskCreateParam(); + param.setTaskName("transfer_" + buildTaskPart(request.getSourceDatabaseName(), request.getSourceSchemaName()) + + "_to_" + buildTaskPart(request.getTargetDatabaseName(), request.getTargetSchemaName())); + param.setTaskType(TaskTypeEnum.TRANSFER_TABLE_DATA.name()); + param.setDatabaseName(request.getTargetDatabaseName()); + param.setSchemaName(request.getTargetSchemaName()); + param.setTableName(StringUtils.join(request.getTableNames(), ",")); + param.setDataSourceId(request.getTargetDataSourceId()); + param.setUserId(ContextUtils.getUserId()); + param.setTaskProgress("0"); + return taskService.create(param); + } + + private String buildTaskPart(String databaseName, String schemaName) { + List names = new ArrayList<>(); + names.add(databaseName); + names.add(schemaName); + String taskPart = names.stream() + .filter(StringUtils::isNotBlank) + .collect(Collectors.joining("_")); + return StringUtils.isBlank(taskPart) ? "default" : taskPart; + } + + private void doTransferData(DataTransferRequest request, Long taskId) { + int totalCount = 0; + try (Connection sourceConnection = createConnection(request.getSourceDataSourceId(), + request.getSourceDatabaseName(), request.getSourceSchemaName()); + Connection targetConnection = createConnection(request.getTargetDataSourceId(), + request.getTargetDatabaseName(), request.getTargetSchemaName())) { + + targetConnection.setAutoCommit(true); + + for (String tableName : request.getTableNames()) { + updateContent(taskId, "Transferring table: " + tableName); + totalCount = transferTable(sourceConnection, targetConnection, request, tableName, taskId, totalCount); + } + } catch (Exception e) { + log.error("transfer data error", e); + throw e instanceof BusinessException ? (BusinessException)e + : new BusinessException("dataSource.importError", new Object[]{e.getMessage()}, e); + } + } + + private int transferTable(Connection sourceConnection, Connection targetConnection, DataTransferRequest request, + String tableName, Long taskId, int totalCount) throws SQLException { + List sourceColumns = queryColumns(sourceConnection, request.getSourceDatabaseName(), + request.getSourceSchemaName(), tableName); + List targetColumns = queryColumns(targetConnection, request.getTargetDatabaseName(), + request.getTargetSchemaName(), tableName); + + if (targetColumns.isEmpty()) { + throw new BusinessException("Target table not found: " + tableName); + } + + List columnMappings = intersectColumns(sourceColumns, targetColumns); + if (columnMappings.isEmpty()) { + throw new BusinessException("No common columns for table: " + tableName); + } + + List sourceTransferColumns = columnMappings.stream().map(ColumnMapping::sourceColumn).toList(); + List targetTransferColumns = columnMappings.stream().map(ColumnMapping::targetColumn).toList(); + String selectSql = buildSelectSql(sourceConnection, request.getSourceSchemaName(), tableName, + sourceTransferColumns); + String insertSql = buildInsertSql(targetConnection, request.getTargetSchemaName(), tableName, + targetTransferColumns); + + try (PreparedStatement selectStatement = sourceConnection.prepareStatement(selectSql, + ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); + PreparedStatement insertStatement = targetConnection.prepareStatement(insertSql)) { + selectStatement.setFetchSize(BATCH_SIZE); + try (ResultSet resultSet = selectStatement.executeQuery()) { + ResultSetMetaData metaData = resultSet.getMetaData(); + int columnCount = metaData.getColumnCount(); + int tableCount = 0; + + while (resultSet.next()) { + for (int i = 1; i <= columnCount; i++) { + insertStatement.setObject(i, resultSet.getObject(i)); + } + insertStatement.addBatch(); + tableCount++; + totalCount++; + if (tableCount % BATCH_SIZE == 0) { + insertStatement.executeBatch(); + insertStatement.clearBatch(); + updateProgressCount(taskId, totalCount); + } + } + + insertStatement.executeBatch(); + insertStatement.clearBatch(); + updateProgressCount(taskId, totalCount); + updateContent(taskId, "Finished table: " + tableName + ", rows: " + tableCount); + return totalCount; + } + } catch (SQLException e) { + throw new BusinessException("Transfer table " + tableName + " failed: " + e.getMessage(), + new Object[]{e.getMessage()}, e); + } + } + + private List queryColumns(Connection connection, String databaseName, String schemaName, String tableName) + throws SQLException { + DatabaseMetaData metaData = connection.getMetaData(); + List columns = readColumns(metaData, databaseName, schemaName, tableName); + if (!columns.isEmpty()) { + return columns; + } + columns = readColumns(metaData, null, schemaName, tableName); + if (!columns.isEmpty()) { + return columns; + } + return readColumns(metaData, null, null, tableName); + } + + private List readColumns(DatabaseMetaData metaData, String databaseName, String schemaName, String tableName) + throws SQLException { + List columns = new ArrayList<>(); + try (ResultSet rs = metaData.getColumns(databaseName, schemaName, tableName, null)) { + while (rs.next()) { + columns.add(rs.getString("COLUMN_NAME")); + } + } + return columns; + } + + private List intersectColumns(List sourceColumns, List targetColumns) { + Map targetColumnMap = targetColumns.stream() + .collect(Collectors.toMap(name -> name.toLowerCase(Locale.ROOT), name -> name, (left, right) -> left)); + return sourceColumns.stream() + .filter(name -> targetColumnMap.containsKey(name.toLowerCase(Locale.ROOT))) + .map(name -> new ColumnMapping(name, targetColumnMap.get(name.toLowerCase(Locale.ROOT)))) + .collect(Collectors.toList()); + } + + private String buildSelectSql(Connection connection, String schemaName, String tableName, List columns) + throws SQLException { + String columnSql = buildColumnSql(connection, columns); + return "SELECT " + columnSql + " FROM " + qualifiedTableName(connection, schemaName, tableName); + } + + private String buildInsertSql(Connection connection, String schemaName, String tableName, List columns) + throws SQLException { + String columnSql = buildColumnSql(connection, columns); + String placeholders = columns.stream().map(column -> "?").collect(Collectors.joining(", ")); + return "INSERT INTO " + qualifiedTableName(connection, schemaName, tableName) + + " (" + columnSql + ") VALUES (" + placeholders + ")"; + } + + private String buildColumnSql(Connection connection, List columns) throws SQLException { + List quotedColumns = new ArrayList<>(); + for (String column : columns) { + quotedColumns.add(quoteIdentifier(connection, column)); + } + return StringUtils.join(quotedColumns, ", "); + } + + private String qualifiedTableName(Connection connection, String schemaName, String tableName) throws SQLException { + if (StringUtils.isBlank(schemaName)) { + return quoteIdentifier(connection, tableName); + } + return quoteIdentifier(connection, schemaName) + "." + quoteIdentifier(connection, tableName); + } + + private String quoteIdentifier(Connection connection, String identifier) throws SQLException { + String quote = connection.getMetaData().getIdentifierQuoteString(); + if (StringUtils.isBlank(quote)) { + return identifier; + } + String trimmedQuote = quote.trim(); + if (StringUtils.isBlank(trimmedQuote)) { + return identifier; + } + return trimmedQuote + identifier.replace(trimmedQuote, trimmedQuote + trimmedQuote) + trimmedQuote; + } + + private Connection createConnection(Long dataSourceId, String databaseName, String schemaName) { + DataSource dataSource = dataSourceService.queryById(dataSourceId); + if (dataSource == null) { + throw new BusinessException("DataSource not found: " + dataSourceId); + } + dataSourceAccessBusinessService.checkPermission(dataSource); + + ConnectInfo connectInfo = new ConnectInfo(); + connectInfo.setDataSourceId(dataSourceId); + connectInfo.setUser(dataSource.getUserName()); + connectInfo.setPassword(dataSource.getPassword()); + connectInfo.setDbType(dataSource.getType()); + connectInfo.setUrl(dataSource.getUrl()); + connectInfo.setDatabase(databaseName); + connectInfo.setSchemaName(schemaName); + connectInfo.setDriver(dataSource.getDriver()); + connectInfo.setSsh(dataSource.getSsh()); + connectInfo.setSsl(dataSource.getSsl()); + connectInfo.setJdbc(dataSource.getJdbc()); + connectInfo.setExtendInfo(dataSource.getExtendInfo()); + connectInfo.setHost(dataSource.getHost()); + if (StringUtils.isNotBlank(dataSource.getPort())) { + connectInfo.setPort(Integer.parseInt(dataSource.getPort())); + } + ai.chat2db.spi.config.DriverConfig driverConfig = dataSource.getDriverConfig(); + if (driverConfig == null) { + driverConfig = Chat2DBContext.getDefaultDriverConfig(dataSource.getType()); + } + connectInfo.setDriverConfig(driverConfig); + connectInfo.setConsoleOwn(false); + + Plugin plugin = Chat2DBContext.PLUGIN_MAP.get(dataSource.getType()); + if (plugin == null) { + throw new BusinessException("Unsupported data source type: " + dataSource.getType()); + } + + ConnectInfo previousInfo = Chat2DBContext.getConnectInfo(); + try { + Chat2DBContext.putContext(connectInfo); + return plugin.getDBManage().getConnection(connectInfo); + } finally { + if (previousInfo != null) { + Chat2DBContext.putContext(previousInfo); + } else { + Chat2DBContext.remove(); + } + } + } + + private void updateProgressCount(Long taskId, int processedCount) { + TaskUpdateParam updateParam = new TaskUpdateParam(); + updateParam.setId(taskId); + updateParam.setTaskProgress(String.valueOf(processedCount)); + taskService.updateStatus(updateParam); + } + + private void updateContent(Long taskId, String content) { + TaskUpdateParam updateParam = new TaskUpdateParam(); + updateParam.setId(taskId); + updateParam.setContent(content); + taskService.updateStatus(updateParam); + } + + private void updateTransferStatus(Long id, Throwable throwable) { + TaskUpdateParam updateParam = new TaskUpdateParam(); + updateParam.setId(id); + if (throwable != null) { + log.error("transfer error", throwable); + updateParam.setTaskStatus(TaskStatusEnum.ERROR.name()); + Throwable cause = throwable.getCause() == null ? throwable : throwable.getCause(); + updateParam.setContent(cause.getMessage()); + } else { + updateParam.setTaskStatus(TaskStatusEnum.FINISH.name()); + } + taskService.updateStatus(updateParam); + } + + private record ColumnMapping(String sourceColumn, String targetColumn) { + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/doc/AbstractSchemaDocExportStrategy.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/doc/AbstractSchemaDocExportStrategy.java new file mode 100644 index 000000000..ca6c3e675 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/doc/AbstractSchemaDocExportStrategy.java @@ -0,0 +1,233 @@ +package ai.chat2db.server.web.api.controller.task.biz.doc; + +import ai.chat2db.server.domain.api.model.IndexInfo; +import ai.chat2db.server.domain.api.model.TableParameter; +import ai.chat2db.server.domain.api.model.ForeignKeyInfo; +import ai.chat2db.server.tools.common.util.I18nUtils; +import ai.chat2db.server.web.api.controller.rdb.doc.DatabaseExportService; +import ai.chat2db.server.web.api.controller.rdb.doc.constant.CommonConstant; +import ai.chat2db.server.web.api.controller.rdb.doc.constant.PatternConstant; +import ai.chat2db.server.web.api.controller.rdb.vo.TableVO; +import ai.chat2db.server.web.api.util.StringUtils; +import ai.chat2db.spi.model.Table; +import ai.chat2db.spi.model.TableColumn; +import ai.chat2db.spi.model.TableIndex; +import ai.chat2db.spi.model.TableIndexColumn; +import ai.chat2db.spi.model.ForeignKey; +import ai.chat2db.spi.model.VirtualForeignKey; +import lombok.extern.slf4j.Slf4j; + +import java.io.FileOutputStream; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +@Slf4j +public abstract class AbstractSchemaDocExportStrategy implements SchemaDocExportStrategy { + + @Override + public void export(SchemaDocExportContext context) { + initConstants(); + Map> tableParameterMap = buildTableParameterMap(context); + Map> indexMap = buildIndexMap(context); + List foreignKeyList = buildForeignKeyList(context); + + context.setTableParameterMap(tableParameterMap); + context.setIndexMap(indexMap); + context.setForeignKeyList(foreignKeyList); + + try (FileOutputStream fos = new FileOutputStream(context.getFile())) { + doExport(fos, context); + } catch (Exception e) { + log.error("export schema doc error, strategy: {}", this.getClass().getSimpleName(), e); + throw new RuntimeException("Export failed: " + e.getMessage(), e); + } + } + + protected abstract void doExport(java.io.OutputStream outputStream, SchemaDocExportContext context) throws Exception; + + private void initConstants() { + CommonConstant.INDEX_HEAD_NAMES = new String[]{ + I18nUtils.getMessage("main.indexName"), + I18nUtils.getMessage("main.indexFieldName"), + I18nUtils.getMessage("main.indexType"), + I18nUtils.getMessage("main.indexMethod"), + I18nUtils.getMessage("main.indexNote") + }; + CommonConstant.COLUMN_HEAD_NAMES = new String[]{ + I18nUtils.getMessage("main.fieldNo"), + I18nUtils.getMessage("main.fieldName"), + I18nUtils.getMessage("main.fieldType"), + I18nUtils.getMessage("main.fieldLength"), + I18nUtils.getMessage("main.fieldIfEmpty"), + I18nUtils.getMessage("main.fieldDefault"), + I18nUtils.getMessage("main.fieldDecimalPlaces"), + I18nUtils.getMessage("main.fieldNote") + }; + + StringBuilder mdIndex = new StringBuilder(PatternConstant.MD_SPLIT); + StringBuilder htmlIndex = new StringBuilder(""); + + StringBuilder mdColumn = new StringBuilder(PatternConstant.MD_SPLIT); + StringBuilder htmlColumn = new StringBuilder(""); + + PatternConstant.ALL_INDEX_TABLE_HEADER = mdIndex.toString(); + PatternConstant.HTML_INDEX_TABLE_HEADER = htmlIndex.toString(); + PatternConstant.ALL_TABLE_HEADER = mdColumn.toString(); + PatternConstant.HTML_TABLE_HEADER = htmlColumn.toString(); + } + + private Map> buildTableParameterMap(SchemaDocExportContext context) { + Map> listMap = new LinkedHashMap<>(); + + for (Table table : context.getTables()) { + TableParameter t = new TableParameter(); + t.setFieldName(table.getName() + "[" + StringUtils.isNull(table.getComment()) + "]"); + List colForTable = new LinkedList<>(); + for (TableColumn info : table.getColumnList()) { + TableParameter p = new TableParameter(); + p.setFieldName(info.getName()) + .setColumnDefault(info.getDefaultValue()) + .setColumnComment(info.getComment()) + .setColumnType(info.getColumnType()) + .setLength(String.valueOf(info.getColumnSize())) + .setIsNullAble(String.valueOf(info.getNullable())) + .setDecimalPlaces(String.valueOf(info.getDecimalDigits())); + colForTable.add(p); + } + String key = context.getDatabaseName() + DatabaseExportService.JOINER + t.getFieldName(); + listMap.put(key, colForTable); + } + + for (Map.Entry> map : listMap.entrySet()) { + List list = map.getValue(); + IntStream.range(0, list.size()).forEach(x -> { + list.get(x).setNo(String.valueOf(x + 1)); + }); + } + return listMap; + } + + private Map> buildIndexMap(SchemaDocExportContext context) { + Map> indexMap = new LinkedHashMap<>(); + boolean isExportIndex = Optional.ofNullable(context.getExportOptions().getIsExportIndex()).orElse(false); + if (!isExportIndex) { + return indexMap; + } + for (Table table : context.getTables()) { + String key = context.getDatabaseName() + DatabaseExportService.JOINER + table.getName(); + indexMap.put(key, vo2Info(table.getIndexList())); + } + return indexMap; + } + + private List vo2Info(List indexList) { + if (indexList == null) { + return Collections.emptyList(); + } + return indexList.stream().map(v -> { + IndexInfo info = new IndexInfo(); + info.setName(v.getName()); + List columnList = v.getColumnList(); + info.setColumnName(columnList != null ? columnList.stream().map(TableIndexColumn::getColumnName).collect(Collectors.joining(",")) : ""); + info.setIndexType(v.getType()); + info.setComment(v.getComment()); + return info; + }).collect(Collectors.toList()); + } + + private List buildForeignKeyList(SchemaDocExportContext context) { + boolean isExportForeignKey = Optional.ofNullable(context.getExportOptions().getIsExportForeignKey()).orElse(false); + if (!isExportForeignKey) { + return Collections.emptyList(); + } + String databaseName = context.getDatabaseName(); + List result = new ArrayList<>(); + for (Table table : context.getTables()) { + if (table.getForeignKeyList() != null) { + for (ForeignKey fk : table.getForeignKeyList()) { + ForeignKeyInfo info = foreignKeyToInfo(fk, "REAL"); + info.setDatabaseName(databaseName); + result.add(info); + } + } + if (table.getVirtualForeignKeyList() != null) { + for (VirtualForeignKey fk : table.getVirtualForeignKeyList()) { + ForeignKeyInfo info = foreignKeyToInfo(fk, "VIRTUAL"); + info.setDatabaseName(databaseName); + result.add(info); + } + } + } + return result; + } + + private ForeignKeyInfo foreignKeyToInfo(ForeignKey fk, String sourceType) { + ForeignKeyInfo info = new ForeignKeyInfo(); + info.setName(fk.getName()); + info.setTableName(fk.getTableName()); + info.setColumnName(fk.getColumn()); + info.setReferencedTable(fk.getReferencedTable()); + info.setReferencedColumnName(fk.getReferencedColumn()); + info.setSourceType(sourceType); + info.setComment(fk.getComment()); + info.setDeleteRule(getRuleName(fk.getDeleteRule())); + info.setUpdateRule(getRuleName(fk.getUpdateRule())); + return info; + } + + private String getRuleName(Integer rule) { + if (rule == null) { + return "NO ACTION"; + } + switch (rule) { + case 0: + return "CASCADE"; + case 1: + return "RESTRICT"; + case 2: + return "SET NULL"; + default: + return "NO ACTION"; + } + } + + protected String dealWith(String source) { + return StringUtils.isNullOrEmpty(source); + } + + protected Object[] getIndexValues(IndexInfo indexInfoVO) { + Object[] values = new Object[IndexInfo.class.getDeclaredFields().length]; + values[0] = dealWith(indexInfoVO.getName()); + values[1] = dealWith(indexInfoVO.getColumnName()); + values[2] = dealWith(indexInfoVO.getIndexType()); + values[3] = dealWith(indexInfoVO.getIndexMethod()); + values[4] = dealWith(indexInfoVO.getComment()); + return values; + } + + protected Object[] getColumnValues(TableParameter tableParameter) { + Object[] values = new Object[TableParameter.class.getDeclaredFields().length]; + values[0] = StringUtils.isNull(tableParameter.getNo()); + values[1] = StringUtils.isNull(tableParameter.getFieldName()); + values[2] = StringUtils.isNull(tableParameter.getColumnType()); + values[3] = StringUtils.isNull(tableParameter.getLength()); + values[4] = StringUtils.isNull(tableParameter.getIsNullAble()); + values[5] = StringUtils.isNull(tableParameter.getColumnDefault()); + values[6] = StringUtils.isNull(tableParameter.getDecimalPlaces()); + values[7] = StringUtils.isNull(tableParameter.getColumnComment()); + return values; + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/doc/ExcelSchemaDocExportStrategy.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/doc/ExcelSchemaDocExportStrategy.java new file mode 100644 index 000000000..82a023d79 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/doc/ExcelSchemaDocExportStrategy.java @@ -0,0 +1,82 @@ +package ai.chat2db.server.web.api.controller.task.biz.doc; + +import ai.chat2db.server.domain.api.enums.ExportTypeEnum; +import ai.chat2db.server.domain.api.model.TableParameter; +import ai.chat2db.server.domain.api.model.ForeignKeyInfo; +import ai.chat2db.server.tools.common.util.I18nUtils; +import ai.chat2db.server.web.api.controller.rdb.doc.adaptive.CustomCellWriteHeightConfig; +import ai.chat2db.server.web.api.controller.rdb.doc.adaptive.CustomCellWriteWidthConfig; +import ai.chat2db.server.web.api.controller.rdb.doc.merge.MyMergeExcel; +import ai.chat2db.server.web.api.controller.rdb.doc.style.CustomExcelStyle; +import com.alibaba.excel.EasyExcel; +import com.alibaba.excel.write.style.HorizontalCellStyleStrategy; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@Slf4j +@Component +public class ExcelSchemaDocExportStrategy extends AbstractSchemaDocExportStrategy { + + @Override + public boolean supports(String exportType) { + return ExportTypeEnum.EXCEL.getCode().equals(exportType); + } + + @Override + protected void doExport(OutputStream outputStream, SchemaDocExportContext context) throws Exception { + List export = new ArrayList<>(); + for (Map.Entry> item : context.getTableParameterMap().entrySet()) { + TableParameter t = new TableParameter(); + t.setNo(item.getKey()).setColumnComment(MyMergeExcel.NAME); + export.add(t); + export.addAll(item.getValue()); + } + + com.alibaba.excel.ExcelWriter excelWriter = EasyExcel.write(outputStream) + .registerWriteHandler(new HorizontalCellStyleStrategy(CustomExcelStyle.getHeadStyle(), CustomExcelStyle.getContentWriteCellStyle())) + .registerWriteHandler(new CustomCellWriteHeightConfig()) + .registerWriteHandler(new CustomCellWriteWidthConfig()) + .registerWriteHandler(new MyMergeExcel()) + .build(); + + com.alibaba.excel.write.metadata.WriteSheet writeSheet = EasyExcel.writerSheet(I18nUtils.getMessage("main.sheetName")) + .head(TableParameter.class) + .build(); + excelWriter.write(export, writeSheet); + + List foreignKeyList = context.getForeignKeyList(); + if (foreignKeyList != null && !foreignKeyList.isEmpty()) { + List> fkData = new ArrayList<>(); + List header = new ArrayList<>(); + header.add(I18nUtils.getMessage("workspace.tableRelation.masterTable")); + header.add(I18nUtils.getMessage("workspace.tableRelation.uniqueColumn")); + header.add(I18nUtils.getMessage("workspace.tableRelation.childTable")); + header.add(I18nUtils.getMessage("workspace.tableRelation.relationColumn")); + header.add(I18nUtils.getMessage("editTable.label.sourceType")); + header.add(I18nUtils.getMessage("editTable.label.comment")); + fkData.add(header); + + for (ForeignKeyInfo fk : foreignKeyList) { + List row = new ArrayList<>(); + row.add(fk.getReferencedTable()); + row.add(fk.getReferencedColumnName()); + row.add(fk.getTableName()); + row.add(fk.getColumnName()); + row.add(fk.getSourceType()); + row.add(fk.getComment()); + fkData.add(row); + } + + com.alibaba.excel.write.metadata.WriteSheet fkSheet = EasyExcel.writerSheet(I18nUtils.getMessage("workspace.tableRelation.title")) + .build(); + excelWriter.write(fkData, fkSheet); + } + + excelWriter.finish(); + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/doc/HtmlSchemaDocExportStrategy.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/doc/HtmlSchemaDocExportStrategy.java new file mode 100644 index 000000000..a2812cff3 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/doc/HtmlSchemaDocExportStrategy.java @@ -0,0 +1,129 @@ +package ai.chat2db.server.web.api.controller.task.biz.doc; + +import ai.chat2db.server.domain.api.enums.ExportTypeEnum; +import ai.chat2db.server.domain.api.model.IndexInfo; +import ai.chat2db.server.domain.api.model.TableParameter; +import ai.chat2db.server.domain.api.model.ForeignKeyInfo; +import ai.chat2db.server.tools.common.config.GlobalDict; +import ai.chat2db.server.tools.common.util.I18nUtils; +import ai.chat2db.server.web.api.controller.rdb.doc.constant.PatternConstant; +import ai.chat2db.server.web.api.util.StringUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.io.BufferedWriter; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import java.text.MessageFormat; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Slf4j +@Component +public class HtmlSchemaDocExportStrategy extends AbstractSchemaDocExportStrategy { + + @Override + public boolean supports(String exportType) { + return ExportTypeEnum.HTML.getCode().equals(exportType); + } + + @Override + protected void doExport(OutputStream outputStream, SchemaDocExportContext context) throws Exception { + Map>>> allMap = context.getTableParameterMap().entrySet() + .stream().collect(Collectors.groupingBy(v -> v.getKey().split("---")[0])); + StringBuilder htmlText = new StringBuilder(); + StringBuilder catalogue = new StringBuilder(); + + try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8))) { + for (Map.Entry>>> myMap : allMap.entrySet()) { + String database = myMap.getKey(); + String title = MessageFormat.format(PatternConstant.HTML_TITLE, I18nUtils.getMessage("main.databaseText") + database); + catalogue.append("
  • ").append(MessageFormat.format(PatternConstant.HTML_INDEX_ITEM, I18nUtils.getMessage("main.databaseText") + + database, I18nUtils.getMessage("main.databaseText") + database)).append("
      "); + htmlText.append(title).append("\n"); + + for (Map.Entry> parameterMap : myMap.getValue()) { + String tableName = parameterMap.getKey().split("---")[1]; + catalogue.append("
    1. ").append(MessageFormat.format(PatternConstant.HTML_INDEX_ITEM, database + tableName, tableName)); + htmlText.append(MessageFormat.format(PatternConstant.HTML_CATALOG, database + tableName, tableName)).append("\n

      "); + + if (!context.getIndexMap().isEmpty()) { + htmlText.append("
  • "); + for (int i = 0; i < CommonConstant.INDEX_HEAD_NAMES.length; i++) { + mdIndex.append(CommonConstant.INDEX_HEAD_NAMES[i]).append(i == CommonConstant.INDEX_HEAD_NAMES.length - 1 ? "" : PatternConstant.MD_SPLIT); + htmlIndex.append(CommonConstant.INDEX_HEAD_NAMES[i]).append(i == CommonConstant.INDEX_HEAD_NAMES.length - 1 ? "" : ""); + } + mdIndex.append(PatternConstant.MD_SPLIT); + htmlIndex.append("
    "); + for (int i = 0; i < CommonConstant.COLUMN_HEAD_NAMES.length; i++) { + mdColumn.append(CommonConstant.COLUMN_HEAD_NAMES[i]).append(i == CommonConstant.COLUMN_HEAD_NAMES.length - 1 ? "" : PatternConstant.MD_SPLIT); + htmlColumn.append(CommonConstant.COLUMN_HEAD_NAMES[i]).append(i == CommonConstant.COLUMN_HEAD_NAMES.length - 1 ? "" : ""); + } + mdColumn.append(PatternConstant.MD_SPLIT); + htmlColumn.append("
    \n"); + htmlText.append(PatternConstant.HTML_INDEX_TABLE_HEADER); + String name = parameterMap.getKey().split("\\[")[0]; + List indexInfoVOList = context.getIndexMap().get(name); + if (indexInfoVOList != null) { + for (IndexInfo indexInfo : indexInfoVOList) { + htmlText.append(String.format(PatternConstant.HTML_INDEX_TABLE_BODY, getIndexValues(indexInfo))); + } + } + htmlText.append("
    \n"); + htmlText.append("\n

    "); + } else { + htmlText.append(String.format(PatternConstant.HTML_INDEX_TABLE_BODY, getIndexValues(new IndexInfo()))); + } + + htmlText.append("\n"); + htmlText.append(PatternConstant.HTML_TABLE_HEADER); + List exportList = parameterMap.getValue(); + for (TableParameter tableParameter : exportList) { + htmlText.append(String.format(PatternConstant.HTML_TABLE_BODY, getColumnValues(tableParameter))); + } + htmlText.append("
    \n"); + } + + // 导出表间关系 + List foreignKeyList = context.getForeignKeyList(); + if (foreignKeyList != null && !foreignKeyList.isEmpty()) { + List dbForeignKeys = foreignKeyList.stream() + .filter(fk -> database.equals(fk.getDatabaseName())) + .collect(Collectors.toList()); + if (!dbForeignKeys.isEmpty()) { + htmlText.append("

    ").append(I18nUtils.getMessage("workspace.tableRelation.title")).append("

    \n"); + htmlText.append("\n\n"); + for (ForeignKeyInfo fk : dbForeignKeys) { + htmlText.append("\n"); + } + htmlText.append("
    ") + .append(I18nUtils.getMessage("workspace.tableRelation.masterTable")).append("") + .append(I18nUtils.getMessage("workspace.tableRelation.uniqueColumn")).append("") + .append(I18nUtils.getMessage("workspace.tableRelation.childTable")).append("") + .append(I18nUtils.getMessage("workspace.tableRelation.relationColumn")).append("") + .append(I18nUtils.getMessage("editTable.label.sourceType")).append("") + .append(I18nUtils.getMessage("editTable.label.comment")).append("
    ").append(dealWith(fk.getReferencedTable())).append("") + .append(dealWith(fk.getReferencedColumnName())).append("") + .append(dealWith(fk.getTableName())).append("") + .append(dealWith(fk.getColumnName())).append("") + .append(dealWith(fk.getSourceType())).append("") + .append(dealWith(fk.getComment())).append("
    \n"); + } + } + htmlText.append("

    "); + catalogue.append(""); + } + catalogue.append(""); + + try (InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("template/" + GlobalDict.TEMPLATE_FILE.get(0)); + ByteArrayOutputStream result = new ByteArrayOutputStream()) { + byte[] buffer = new byte[1024]; + int length; + while ((length = inputStream.read(buffer)) != -1) { + result.write(buffer, 0, length); + } + String str = result.toString(String.valueOf(StandardCharsets.UTF_8)); + str = str.replace("${data}", htmlText).replace("${catalogue}", catalogue); + writer.write(str); + } + writer.flush(); + } + } + + @Override + protected String dealWith(String source) { + return StringUtils.isNullForHtml(source); + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/doc/MarkdownSchemaDocExportStrategy.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/doc/MarkdownSchemaDocExportStrategy.java new file mode 100644 index 000000000..2f0afeda8 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/doc/MarkdownSchemaDocExportStrategy.java @@ -0,0 +1,123 @@ +package ai.chat2db.server.web.api.controller.task.biz.doc; + +import ai.chat2db.server.domain.api.enums.ExportTypeEnum; +import ai.chat2db.server.domain.api.model.IndexInfo; +import ai.chat2db.server.domain.api.model.TableParameter; +import ai.chat2db.server.domain.api.model.ForeignKeyInfo; +import ai.chat2db.server.tools.common.util.I18nUtils; +import ai.chat2db.server.web.api.controller.rdb.doc.constant.PatternConstant; +import ai.chat2db.server.web.api.util.StringUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.io.BufferedWriter; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Slf4j +@Component +public class MarkdownSchemaDocExportStrategy extends AbstractSchemaDocExportStrategy { + + @Override + public boolean supports(String exportType) { + return ExportTypeEnum.MARKDOWN.getCode().equals(exportType); + } + + @Override + protected void doExport(OutputStream outputStream, SchemaDocExportContext context) throws Exception { + Map>>> allMap = context.getTableParameterMap().entrySet() + .stream().collect(Collectors.groupingBy(v -> v.getKey().split("---")[0])); + + try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8))) { + for (Map.Entry>>> myMap : allMap.entrySet()) { + String database = myMap.getKey(); + String title = String.format(PatternConstant.TITLE, I18nUtils.getMessage("main.databaseText") + database); + writer.write(title); + writeLineSeparator(writer, 2); + + for (Map.Entry> parameterMap : myMap.getValue()) { + String tableName = parameterMap.getKey().split("---")[1]; + writer.write(String.format(PatternConstant.CATALOG, tableName)); + writeLineSeparator(writer, 1); + + if (!context.getIndexMap().isEmpty()) { + writer.write(PatternConstant.ALL_INDEX_TABLE_HEADER); + writeLineSeparator(writer, 1); + writer.write(PatternConstant.INDEX_TABLE_SEPARATOR); + writeLineSeparator(writer, 1); + String name = parameterMap.getKey().split("\\[")[0]; + List indexInfoVOList = context.getIndexMap().get(name); + if (indexInfoVOList != null) { + for (IndexInfo indexInfo : indexInfoVOList) { + writer.write(String.format(PatternConstant.INDEX_TABLE_BODY, getIndexValues(indexInfo))); + writeLineSeparator(writer, 1); + } + } + writeLineSeparator(writer, 1); + } + writeLineSeparator(writer, 2); + + writer.write(PatternConstant.ALL_TABLE_HEADER); + writeLineSeparator(writer, 1); + writer.write(PatternConstant.TABLE_SEPARATOR); + writeLineSeparator(writer, 1); + + List exportList = parameterMap.getValue(); + for (TableParameter tableParameter : exportList) { + writer.write(String.format(PatternConstant.TABLE_BODY, getColumnValues(tableParameter))); + writeLineSeparator(writer, 1); + } + writeLineSeparator(writer, 2); + } + + // 导出表间关系 + List foreignKeyList = context.getForeignKeyList(); + if (foreignKeyList != null && !foreignKeyList.isEmpty()) { + List dbForeignKeys = foreignKeyList.stream() + .filter(fk -> database.equals(fk.getDatabaseName())) + .collect(Collectors.toList()); + if (!dbForeignKeys.isEmpty()) { + writer.write("## " + I18nUtils.getMessage("workspace.tableRelation.title")); + writeLineSeparator(writer, 2); + writer.write("| " + I18nUtils.getMessage("workspace.tableRelation.masterTable") + + " | " + I18nUtils.getMessage("workspace.tableRelation.uniqueColumn") + + " | " + I18nUtils.getMessage("workspace.tableRelation.childTable") + + " | " + I18nUtils.getMessage("workspace.tableRelation.relationColumn") + + " | " + I18nUtils.getMessage("editTable.label.sourceType") + + " | " + I18nUtils.getMessage("editTable.label.comment") + " |"); + writeLineSeparator(writer, 1); + writer.write("|---|---|---|---|---|---|"); + writeLineSeparator(writer, 1); + for (ForeignKeyInfo fk : dbForeignKeys) { + writer.write(String.format("| %s | %s | %s | %s | %s | %s |", + dealWith(fk.getReferencedTable()), + dealWith(fk.getReferencedColumnName()), + dealWith(fk.getTableName()), + dealWith(fk.getColumnName()), + dealWith(fk.getSourceType()), + dealWith(fk.getComment()))); + writeLineSeparator(writer, 1); + } + writeLineSeparator(writer, 2); + } + } + } + writer.flush(); + } + } + + private void writeLineSeparator(BufferedWriter writer, int number) throws Exception { + for (int i = 0; i < number; i++) { + writer.write(System.lineSeparator()); + } + } + + @Override + protected String dealWith(String source) { + return StringUtils.isNullForHtml(source); + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/doc/PdfSchemaDocExportStrategy.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/doc/PdfSchemaDocExportStrategy.java new file mode 100644 index 000000000..0c6d6f804 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/doc/PdfSchemaDocExportStrategy.java @@ -0,0 +1,158 @@ +package ai.chat2db.server.web.api.controller.task.biz.doc; + +import ai.chat2db.server.domain.api.enums.ExportTypeEnum; +import ai.chat2db.server.domain.api.model.IndexInfo; +import ai.chat2db.server.domain.api.model.TableParameter; +import ai.chat2db.server.domain.api.model.ForeignKeyInfo; +import ai.chat2db.server.tools.common.util.I18nUtils; +import ai.chat2db.server.web.api.controller.rdb.doc.constant.CommonConstant; +import com.itextpdf.text.Document; +import com.itextpdf.text.Font; +import com.itextpdf.text.Paragraph; +import com.itextpdf.text.pdf.BaseFont; +import com.itextpdf.text.pdf.PdfPCell; +import com.itextpdf.text.pdf.PdfPTable; +import com.itextpdf.text.pdf.PdfWriter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.io.OutputStream; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +@Slf4j +@Component +public class PdfSchemaDocExportStrategy extends AbstractSchemaDocExportStrategy { + + @Override + public boolean supports(String exportType) { + return ExportTypeEnum.PDF.getCode().equals(exportType); + } + + @Override + protected void doExport(OutputStream outputStream, SchemaDocExportContext context) throws Exception { + boolean isExportIndex = context.getExportOptions().getIsExportIndex(); + Map>>> allMap = context.getTableParameterMap().entrySet() + .stream().collect(Collectors.groupingBy(v -> v.getKey().split("---")[0])); + + Document document = new Document(); + PdfWriter pdfWriter = PdfWriter.getInstance(document, outputStream); + pdfWriter.setStrictImageSequence(true); + + BaseFont baseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED); + Font font = new Font(baseFont, 10, Font.NORMAL); + Font headFont = new Font(baseFont, 12, Font.NORMAL); + Font titleFont = new Font(baseFont, 14, Font.BOLD); + + document.open(); + + for (Map.Entry>>> myMap : allMap.entrySet()) { + String database = myMap.getKey(); + String title = I18nUtils.getMessage("main.databaseText") + database; + Paragraph p = new Paragraph(title, titleFont); + document.add(p); + + for (Map.Entry> parameterMap : myMap.getValue()) { + String tableName = parameterMap.getKey().split("---")[1]; + Paragraph tableParagraph = new Paragraph(tableName, font); + document.add(tableParagraph); + + if (isExportIndex && !context.getIndexMap().isEmpty()) { + PdfPTable table = new PdfPTable(CommonConstant.INDEX_HEAD_NAMES.length); + process(table, CommonConstant.INDEX_HEAD_NAMES, headFont); + String name = parameterMap.getKey().split("\\[")[0]; + List indexInfoVOList = context.getIndexMap().get(name); + if (indexInfoVOList != null) { + for (IndexInfo indexInfo : indexInfoVOList) { + processWithObjects(table, getIndexValues(indexInfo), font); + } + } + table.setHorizontalAlignment(PdfPTable.ALIGN_LEFT); + table.setSpacingBefore(10f); + table.setSpacingAfter(20f); + document.add(table); + } + document.add(new Paragraph()); + + List exportList = parameterMap.getValue(); + PdfPTable table = new PdfPTable(CommonConstant.COLUMN_HEAD_NAMES.length); + process(table, CommonConstant.COLUMN_HEAD_NAMES, headFont); + for (TableParameter tableParameter : exportList) { + processWithObjects(table, getColumnValues(tableParameter), font); + } + table.setSpacingBefore(10f); + table.setSpacingAfter(20f); + table.setHorizontalAlignment(PdfPTable.ALIGN_LEFT); + document.add(table); + } + + // 导出表间关系 + List foreignKeyList = context.getForeignKeyList(); + if (foreignKeyList != null && !foreignKeyList.isEmpty()) { + List dbForeignKeys = foreignKeyList.stream() + .filter(fk -> database.equals(fk.getDatabaseName())) + .collect(Collectors.toList()); + if (!dbForeignKeys.isEmpty()) { + String relationTitle = I18nUtils.getMessage("workspace.tableRelation.title"); + Paragraph relationParagraph = new Paragraph(relationTitle, titleFont); + document.add(relationParagraph); + + String[] fkHeaders = { + I18nUtils.getMessage("workspace.tableRelation.masterTable"), + I18nUtils.getMessage("workspace.tableRelation.uniqueColumn"), + I18nUtils.getMessage("workspace.tableRelation.childTable"), + I18nUtils.getMessage("workspace.tableRelation.relationColumn"), + I18nUtils.getMessage("editTable.label.sourceType"), + I18nUtils.getMessage("editTable.label.comment") + }; + PdfPTable fkTable = new PdfPTable(fkHeaders.length); + process(fkTable, fkHeaders, headFont); + for (ForeignKeyInfo fk : dbForeignKeys) { + Object[] fkValues = { + fk.getReferencedTable(), + fk.getReferencedColumnName(), + fk.getTableName(), + fk.getColumnName(), + fk.getSourceType(), + fk.getComment() + }; + processWithObjects(fkTable, fkValues, font); + } + fkTable.setHorizontalAlignment(PdfPTable.ALIGN_LEFT); + fkTable.setSpacingBefore(10f); + fkTable.setSpacingAfter(20f); + document.add(fkTable); + } + } + } + document.close(); + } + + public static void process(PdfPTable table, T[] line, Font font) { + for (T s : line) { + if (Objects.isNull(s)) { + continue; + } + PdfPCell cell = new PdfPCell(new Paragraph(s.toString(), font)); + cell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER); + cell.setVerticalAlignment(PdfPCell.ALIGN_CENTER); + cell.setPaddingTop(5); + cell.setPaddingBottom(5); + table.addCell(cell); + } + } + + private static void processWithObjects(PdfPTable table, Object[] line, Font font) { + for (Object obj : line) { + String value = Objects.isNull(obj) ? "" : obj.toString(); + PdfPCell cell = new PdfPCell(new Paragraph(value, font)); + cell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER); + cell.setVerticalAlignment(PdfPCell.ALIGN_CENTER); + cell.setPaddingTop(5); + cell.setPaddingBottom(5); + table.addCell(cell); + } + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/doc/SchemaDocExportContext.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/doc/SchemaDocExportContext.java new file mode 100644 index 000000000..ae74ca64b --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/doc/SchemaDocExportContext.java @@ -0,0 +1,33 @@ +package ai.chat2db.server.web.api.controller.task.biz.doc; + +import ai.chat2db.server.domain.api.model.TableParameter; +import ai.chat2db.server.domain.api.model.IndexInfo; +import ai.chat2db.server.domain.api.model.ForeignKeyInfo; +import ai.chat2db.server.web.api.controller.rdb.doc.conf.ExportOptions; +import ai.chat2db.server.web.api.controller.rdb.vo.TableVO; +import ai.chat2db.spi.model.Table; +import lombok.Builder; +import lombok.Data; + +import java.io.File; +import java.util.List; +import java.util.Map; + +@Data +@Builder +public class SchemaDocExportContext { + + private List tables; + + private String databaseName; + + private File file; + + private ExportOptions exportOptions; + + private Map> tableParameterMap; + + private Map> indexMap; + + private List foreignKeyList; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/doc/SchemaDocExportStrategy.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/doc/SchemaDocExportStrategy.java new file mode 100644 index 000000000..39cebdbdf --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/doc/SchemaDocExportStrategy.java @@ -0,0 +1,8 @@ +package ai.chat2db.server.web.api.controller.task.biz.doc; + +public interface SchemaDocExportStrategy { + + void export(SchemaDocExportContext context); + + boolean supports(String exportType); +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/doc/SchemaDocExportStrategyFactory.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/doc/SchemaDocExportStrategyFactory.java new file mode 100644 index 000000000..3df950ff1 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/doc/SchemaDocExportStrategyFactory.java @@ -0,0 +1,23 @@ +package ai.chat2db.server.web.api.controller.task.biz.doc; + +import ai.chat2db.server.tools.base.excption.BusinessException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +public class SchemaDocExportStrategyFactory { + + @Autowired + private List strategies; + + public SchemaDocExportStrategy getStrategy(String exportType) { + for (SchemaDocExportStrategy strategy : strategies) { + if (strategy.supports(exportType)) { + return strategy; + } + } + throw new BusinessException("dataSource.exportTypeNotSupported", new Object[]{exportType}); + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/doc/SqlSchemaDocExportStrategy.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/doc/SqlSchemaDocExportStrategy.java new file mode 100644 index 000000000..1599038ca --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/doc/SqlSchemaDocExportStrategy.java @@ -0,0 +1,36 @@ +package ai.chat2db.server.web.api.controller.task.biz.doc; + +import ai.chat2db.server.domain.api.enums.ExportTypeEnum; +import ai.chat2db.server.web.api.controller.rdb.vo.TableVO; +import ai.chat2db.spi.SqlBuilder; +import ai.chat2db.spi.model.Table; +import ai.chat2db.spi.sql.Chat2DBContext; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; + +@Slf4j +@Component +public class SqlSchemaDocExportStrategy extends AbstractSchemaDocExportStrategy { + + @Override + public boolean supports(String exportType) { + return ExportTypeEnum.SQL.getCode().equals(exportType); + } + + @Override + protected void doExport(OutputStream outputStream, SchemaDocExportContext context) throws Exception { + SqlBuilder sqlBuilder = Chat2DBContext.getSqlBuilder(); + try (OutputStreamWriter writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8)) { + for (Table table : context.getTables()) { + String ddl = sqlBuilder.buildCreateTableSql(table); + writer.write(ddl); + writer.write("\n\n"); + } + writer.flush(); + } + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/doc/WordSchemaDocExportStrategy.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/doc/WordSchemaDocExportStrategy.java new file mode 100644 index 000000000..ef90daef8 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/doc/WordSchemaDocExportStrategy.java @@ -0,0 +1,140 @@ +package ai.chat2db.server.web.api.controller.task.biz.doc; + +import ai.chat2db.server.domain.api.enums.ExportTypeEnum; +import ai.chat2db.server.domain.api.model.IndexInfo; +import ai.chat2db.server.domain.api.model.TableParameter; +import ai.chat2db.server.domain.api.model.ForeignKeyInfo; +import ai.chat2db.server.tools.common.config.GlobalDict; +import ai.chat2db.server.tools.common.util.I18nUtils; +import ai.chat2db.server.web.api.controller.rdb.doc.constant.CommonConstant; +import com.deepoove.poi.XWPFTemplate; +import com.deepoove.poi.data.Includes; +import com.deepoove.poi.data.RowRenderData; +import com.deepoove.poi.data.Rows; +import com.deepoove.poi.data.Tables; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Slf4j +@Component +public class WordSchemaDocExportStrategy extends AbstractSchemaDocExportStrategy { + + @Override + public boolean supports(String exportType) { + return ExportTypeEnum.WORD.getCode().equals(exportType); + } + + @Override + protected void doExport(OutputStream outputStream, SchemaDocExportContext context) throws Exception { + boolean isExportIndex = context.getExportOptions().getIsExportIndex(); + InputStream filePath = this.getClass().getClassLoader().getResourceAsStream("template/" + GlobalDict.TEMPLATE_FILE.get(1)); + InputStream subFile = this.getClass().getClassLoader().getResourceAsStream("template/" + GlobalDict.TEMPLATE_FILE.get(2)); + Map>>> allMap = context.getTableParameterMap().entrySet() + .stream().collect(Collectors.groupingBy(v -> v.getKey().split("---")[0])); + List> list = new ArrayList<>(); + Map myDataMap = new HashMap<>(2); + + RowRenderData indexHeaderRow = Rows.of(CommonConstant.INDEX_HEAD_NAMES).center().textBold().textColor("000000").bgColor("bfbfbf").create(); + RowRenderData tableHeaderRow = Rows.of(CommonConstant.COLUMN_HEAD_NAMES).center().textBold().textColor("000000").bgColor("bfbfbf").create(); + + String[] fkHeaders = { + I18nUtils.getMessage("workspace.tableRelation.masterTable"), + I18nUtils.getMessage("workspace.tableRelation.uniqueColumn"), + I18nUtils.getMessage("workspace.tableRelation.childTable"), + I18nUtils.getMessage("workspace.tableRelation.relationColumn"), + I18nUtils.getMessage("editTable.label.sourceType"), + I18nUtils.getMessage("editTable.label.comment") + }; + RowRenderData fkHeaderRow = Rows.of(fkHeaders).center().textBold().textColor("000000").bgColor("bfbfbf").create(); + + for (Map.Entry>>> myMap : allMap.entrySet()) { + String database = myMap.getKey(); + int i = 1; + for (Map.Entry> parameterMap : myMap.getValue()) { + Map tableData = new HashMap<>(8); + if (isExportIndex) { + String name = parameterMap.getKey().split("\\[")[0]; + List indexInfoVOList = context.getIndexMap().get(name); + List rowList = getIndexValues(indexInfoVOList, indexHeaderRow); + tableData.put("indexTable", Tables.create(rowList.toArray(new RowRenderData[0]))); + } + if (i == 1) { + Map map = new HashMap<>(2); + map.put("dataBase", database); + tableData.put("ifDatabase", map); + } + String tableName = parameterMap.getKey().split("---")[1]; + tableData.put("number", i); + tableData.put("name", tableName); + List tableParameterList = parameterMap.getValue(); + List rowList = getColumnValues(tableParameterList, tableHeaderRow); + tableData.put("table", Tables.create(rowList.toArray(new RowRenderData[0]))); + i++; + list.add(tableData); + } + + List foreignKeyList = context.getForeignKeyList(); + if (foreignKeyList != null && !foreignKeyList.isEmpty()) { + List dbForeignKeys = foreignKeyList.stream() + .filter(fk -> database.equals(fk.getDatabaseName())) + .collect(Collectors.toList()); + if (!dbForeignKeys.isEmpty()) { + List fkRowList = new ArrayList<>(); + fkRowList.add(fkHeaderRow); + for (ForeignKeyInfo fk : dbForeignKeys) { + String[] values = new String[]{ + dealWith(fk.getReferencedTable()), + dealWith(fk.getReferencedColumnName()), + dealWith(fk.getTableName()), + dealWith(fk.getColumnName()), + dealWith(fk.getSourceType()), + dealWith(fk.getComment()) + }; + fkRowList.add(Rows.of(values).center().create()); + } + Map relationData = new HashMap<>(2); + relationData.put("relationTable", Tables.create(fkRowList.toArray(new RowRenderData[0]))); + relationData.put("relationTitle", I18nUtils.getMessage("workspace.tableRelation.title")); + list.add(relationData); + } + } + } + myDataMap.put("mydata", Includes.ofStream(subFile).setRenderModel(list).create()); + XWPFTemplate template = XWPFTemplate.compile(filePath).render(myDataMap); + ai.chat2db.server.web.api.util.AddToTopic.generateTOC(template.getXWPFDocument(), outputStream); + } + + private List getColumnValues(List list, RowRenderData tableHeaderRow) { + List rowRenderDataList = new ArrayList<>(); + rowRenderDataList.add(tableHeaderRow); + for (TableParameter tableParameter : list) { + String[] values = Arrays.stream(getColumnValues(tableParameter)).toArray(String[]::new); + rowRenderDataList.add(Rows.of(values).center().create()); + } + return rowRenderDataList; + } + + private List getIndexValues(List list, RowRenderData tableHeaderRow) { + List rowRenderDataList = new ArrayList<>(); + rowRenderDataList.add(tableHeaderRow); + if (list == null || list.isEmpty()) { + String[] values = Arrays.stream(getIndexValues(new IndexInfo())).toArray(String[]::new); + rowRenderDataList.add(Rows.of(values).center().create()); + return rowRenderDataList; + } + for (IndexInfo indexInfo : list) { + String[] values = Arrays.stream(getIndexValues(indexInfo)).toArray(String[]::new); + rowRenderDataList.add(Rows.of(values).center().create()); + } + return rowRenderDataList; + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/enums/ImportModeEnum.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/enums/ImportModeEnum.java new file mode 100644 index 000000000..19f0d47e9 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/enums/ImportModeEnum.java @@ -0,0 +1,10 @@ +package ai.chat2db.server.web.api.controller.task.enums; + +public enum ImportModeEnum { + INSERT, + UPDATE, + UPSERT, + INSERT_IGNORE, + DELETE, + REPLACE +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/request/DataImportRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/request/DataImportRequest.java new file mode 100644 index 000000000..040227930 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/request/DataImportRequest.java @@ -0,0 +1,46 @@ +package ai.chat2db.server.web.api.controller.task.request; + +import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 数据导入请求参数 + */ +@Data +public class DataImportRequest extends DataSourceBaseRequest { + + /** + * 目标表名 + */ + @NotBlank + private String tableName; + + /** + * 文件类型:CSV, XLSX, XLS + */ + @NotNull + private String fileType; + + /** + * 数据库名 + */ + private String databaseName; + + /** + * schema 名 + */ + private String schemaName; + + /** + * 字段映射配置(JSON格式) + * 格式:[{"sourceField":"源字段","targetField":"目标字段","primaryKey":false}] + */ + private String fieldMappings; + + /** + * 导入模式:INSERT/UPDATE/UPSERT/INSERT_IGNORE/DELETE/REPLACE + */ + private String importMode; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/request/DataTransferRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/request/DataTransferRequest.java new file mode 100644 index 000000000..2b466e525 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/request/DataTransferRequest.java @@ -0,0 +1,31 @@ +package ai.chat2db.server.web.api.controller.task.request; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +/** + * 数据传输请求参数 + */ +@Data +public class DataTransferRequest { + + @NotNull + private Long sourceDataSourceId; + + private String sourceDatabaseName; + + private String sourceSchemaName; + + @NotNull + private Long targetDataSourceId; + + private String targetDatabaseName; + + private String targetSchemaName; + + @NotEmpty + private List tableNames; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/request/FieldMapping.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/request/FieldMapping.java new file mode 100644 index 000000000..1ded10659 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/request/FieldMapping.java @@ -0,0 +1,24 @@ +package ai.chat2db.server.web.api.controller.task.request; + +import lombok.Data; + +/** + * 字段映射配置 + */ +@Data +public class FieldMapping { + /** + * 源字段名(文件列名) + */ + private String sourceField; + + /** + * 目标字段名(表字段) + */ + private String targetField; + + /** + * 是否主键 + */ + private Boolean primaryKey; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/request/FilePreviewRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/request/FilePreviewRequest.java new file mode 100644 index 000000000..605534580 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/request/FilePreviewRequest.java @@ -0,0 +1,35 @@ +package ai.chat2db.server.web.api.controller.task.request; + +import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 文件预览请求参数 + */ +@Data +public class FilePreviewRequest extends DataSourceBaseRequest { + + /** + * 目标表名 + */ + @NotBlank + private String tableName; + + /** + * 文件类型:CSV, XLSX, XLS + */ + @NotNull + private String fileType; + + /** + * 数据库名 + */ + private String databaseName; + + /** + * schema 名 + */ + private String schemaName; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/response/FilePreviewResult.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/response/FilePreviewResult.java new file mode 100644 index 000000000..b69a02903 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/response/FilePreviewResult.java @@ -0,0 +1,44 @@ +package ai.chat2db.server.web.api.controller.task.response; + +import lombok.Data; + +import java.util.List; + +/** + * 文件预览结果 + */ +@Data +public class FilePreviewResult { + /** + * 文件列头列表 + */ + private List fileHeaders; + + /** + * 目标表字段列表 + */ + private List tableColumns; + + /** + * 自动匹配结果 + */ + private List autoMappings; + + @Data + public static class AutoMapping { + /** + * 源字段名 + */ + private String sourceField; + + /** + * 目标字段名 + */ + private String targetField; + + /** + * 是否自动匹配成功(同名字段) + */ + private boolean matched; + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/response/TableColumnInfo.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/response/TableColumnInfo.java new file mode 100644 index 000000000..ff2e52440 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/response/TableColumnInfo.java @@ -0,0 +1,24 @@ +package ai.chat2db.server.web.api.controller.task.response; + +import lombok.Data; + +/** + * 表字段信息 + */ +@Data +public class TableColumnInfo { + /** + * 字段名 + */ + private String name; + + /** + * 字段类型 + */ + private String type; + + /** + * 是否主键 + */ + private boolean primaryKey; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java deleted file mode 100644 index 14abe51ae..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java +++ /dev/null @@ -1,219 +0,0 @@ -package ai.chat2db.server.web.api.http; - -import ai.chat2db.server.tools.base.wrapper.result.ActionResult; -import ai.chat2db.server.tools.base.wrapper.result.DataResult; -import ai.chat2db.server.tools.common.config.Chat2dbProperties; -import ai.chat2db.server.web.api.http.request.*; -import ai.chat2db.server.web.api.http.response.*; -import com.dtflys.forest.Forest; -import com.dtflys.forest.utils.TypeReference; -import jakarta.annotation.Resource; -import org.springframework.stereotype.Service; - -import java.time.Duration; - - -/** - * Gateway 的http 服务 - * - * @author Jiaju Zhuang - */ -//@BaseRequest( -// baseURL = "{gatewayBaseUrl}" -//) -@Service -public class GatewayClientService { - - @Resource - private Chat2dbProperties chat2dbProperties; - - /** - * 获取公众号的二维码 - * - * @return - */ - public DataResult getLoginQrCode() { - DataResult result = Forest.get(chat2dbProperties.getGateway().getBaseUrl() + "/api/client/loginQrCode") - .connectTimeout(Duration.ofMillis(5000)) - .readTimeout(Duration.ofMillis(10000)) - .execute(new TypeReference<>() { - }); - return result; - } - - /** - * Refresh login - * - * @param token - * @return - */ - public DataResult getLoginStatus(String token) { - DataResult result = Forest.get(chat2dbProperties.getGateway().getBaseUrl() + "/api/client/loginStatus") - .connectTimeout(Duration.ofMillis(5000)) - .addQuery("token", token) - .readTimeout(Duration.ofMillis(10000)) - .execute(new TypeReference<>() { - }); - return result; - - } - - /** - * 返回剩余次数 - * - * @param key - * @return - */ - public DataResult remaininguses(String key) { - DataResult result = Forest.get(chat2dbProperties.getGateway().getBaseUrl() + "/api/client/remaininguses/" + key) - .connectTimeout(Duration.ofMillis(5000)) - .readTimeout(Duration.ofMillis(10000)) - .execute(new TypeReference<>() { - }); - return result; - - } - - - /** - * Obtain invitation QR code - * - * @param apiKey - * @return - */ - public DataResult getInviteQrCode(String apiKey) { - DataResult result = Forest.get(chat2dbProperties.getGateway().getBaseUrl() + "/api/client/inviteQrCode") - .connectTimeout(Duration.ofMillis(5000)) - .addQuery("apiKey", apiKey) - .readTimeout(Duration.ofMillis(10000)) - .execute(new TypeReference<>() { - }); - return result; - - - } - - /** - * save knowledge vector - * - * @param request - * @return - */ - public ActionResult knowledgeVectorSave(KnowledgeRequest request) { - - ActionResult result = Forest.post(chat2dbProperties.getGateway().getBaseUrl() + "/api/client/milvus/knowledge/save") - .connectTimeout(Duration.ofMillis(5000)) - .readTimeout(Duration.ofMillis(10000)) - .contentType("application/json") - .addBody(request) - .execute(new TypeReference<>() { - }); - return result; - - } - - /** - * save table schema vector - * - * @param request - * @return - */ - public ActionResult schemaVectorSave(TableSchemaRequest request) { - ActionResult result = Forest.post(chat2dbProperties.getGateway().getBaseUrl() + "/api/client/milvus/schema/save") - .connectTimeout(Duration.ofMillis(5000)) - .readTimeout(Duration.ofMillis(10000)) - .contentType("application/json") - .addBody(request) - .execute(new TypeReference<>() { - }); - return result; - } - - /** - * save table schema vector - * - * @param request - * @return - */ - public ActionResult schemaEsSave(EsTableSchemaRequest request) { - ActionResult result = Forest.post(chat2dbProperties.getGateway().getBaseUrl() + "/api/client/es/schema/save") - .connectTimeout(Duration.ofMillis(5000)) - .readTimeout(Duration.ofMillis(10000)) - .contentType("application/json") - .addBody(request) - .execute(new TypeReference<>() { - }); - return result; - } - - /** - * save knowledge vector - * - * @param searchVectors - * @return - */ - public DataResult knowledgeVectorSearch(KnowledgeRequest searchVectors) { - DataResult result = Forest.post(chat2dbProperties.getGateway().getBaseUrl() + "/api/client/milvus/knowledge/search") - .connectTimeout(Duration.ofMillis(5000)) - .readTimeout(Duration.ofMillis(10000)) - .contentType("application/json") - .addBody(searchVectors) - .execute(new TypeReference<>() { - }); - return result; - } - - /** - * save table schema vector - * - * @param request - * @return - */ - public DataResult schemaVectorSearch(TableSchemaRequest request) { - DataResult result = Forest.post(chat2dbProperties.getGateway().getBaseUrl() + "/api/client/milvus/schema/search") - .connectTimeout(Duration.ofMillis(5000)) - .readTimeout(Duration.ofMillis(10000)) - .contentType("application/json") - .addBody(request) - .execute(new TypeReference<>() { - }); - return result; - } - - /** - * save table schema vector - * - * @param request - * @return - */ - public DataResult schemaEsSearch(EsTableSchemaRequest request) { - DataResult result = Forest.post(chat2dbProperties.getGateway().getBaseUrl() + "/api/client/es/schema/search") - .connectTimeout(Duration.ofMillis(5000)) - .readTimeout(Duration.ofMillis(10000)) - .contentType("application/json") - .addBody(request) - .execute(new TypeReference<>() { - }); - return result; - } - - /** - * check in white list - * - * @param whiteListRequest - * @return - */ - public DataResult checkInWhite(WhiteListRequest whiteListRequest) { - // 去掉白名单 - return DataResult.of(false); -// DataResult result = Forest.get(chat2dbProperties.getGateway().getBaseUrl() + "/api/client/whitelist/check") -// .connectTimeout(Duration.ofMillis(5000)) -// .readTimeout(Duration.ofMillis(10000)) -// .addQuery(whiteListRequest) -// .execute(new TypeReference<>() { -// }); -// return result; - } - - -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/request/TableSchemaRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/request/TableSchemaRequest.java index 324e7a7e9..120ecba33 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/request/TableSchemaRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/request/TableSchemaRequest.java @@ -26,5 +26,6 @@ public class TableSchemaRequest { private List schemaList; + @lombok.Builder.Default private Boolean deleteBeforeInsert = false; } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/model/ExcelWrapper.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/model/ExcelWrapper.java new file mode 100644 index 000000000..c9a083dbd --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/model/ExcelWrapper.java @@ -0,0 +1,17 @@ +package ai.chat2db.server.web.api.model; + +import com.alibaba.excel.ExcelWriter; +import com.alibaba.excel.write.metadata.WriteSheet; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class ExcelWrapper { + private ExcelWriter excelWriter; + private WriteSheet writeSheet; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/ws/WsService.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/ws/WsService.java index a9c9b4eb8..04729e3ac 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/ws/WsService.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/ws/WsService.java @@ -12,7 +12,6 @@ import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.common.exception.ParamBusinessException; import ai.chat2db.server.tools.common.model.LoginUser; -import ai.chat2db.server.web.api.controller.ai.chat2db.client.Chat2dbAIClient; import ai.chat2db.server.web.api.controller.rdb.converter.RdbWebConverter; import ai.chat2db.server.web.api.controller.rdb.request.DmlRequest; import ai.chat2db.server.web.api.controller.rdb.vo.ExecuteResultVO; @@ -49,12 +48,10 @@ public class WsService { @Autowired private DlTemplateService dlTemplateService; - public static ExecutorService executorService = Executors.newFixedThreadPool(10); - public ListResult execute(DmlRequest request) { DlExecuteParam param = rdbWebConverter.request2param(request); - ListResult resultDTOListResult = dlTemplateService.execute(param); - List resultVOS = rdbWebConverter.dto2vo(resultDTOListResult.getData()); + List resultList = dlTemplateService.execute(param); + List resultVOS = rdbWebConverter.dto2vo(resultList); return ListResult.of(resultVOS); } @@ -62,7 +59,7 @@ public ListResult execute(DmlRequest request) { public LoginUser doLogin(String token) { Long userId = RoleCodeEnum.DESKTOP.getDefaultUserId(); LoginUser loginUser = MemoryCacheManage.computeIfAbsent(CacheKey.getLoginUserKey(userId), () -> { - User user = userService.query(userId).getData(); + User user = userService.query(userId); if (user == null) { return null; } @@ -82,9 +79,8 @@ public LoginUser doLogin(String token) { } public ConnectInfo toInfo(Long dataSourceId, String database, Long consoleId, String schemaName) { - DataResult result = dataSourceService.queryById(dataSourceId); - DataSource dataSource = result.getData(); - if (!result.success() || dataSource == null) { + DataSource dataSource = dataSourceService.queryById(dataSourceId); + if (dataSource == null) { throw new ParamBusinessException("dataSourceId"); } // Verify permissions @@ -116,14 +112,4 @@ public ConnectInfo toInfo(Long dataSourceId, String database, Long consoleId, St } - private String getApiKey() { - ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); - Config keyConfig = configService.find(Chat2dbAIClient.CHAT2DB_OPENAI_KEY).getData(); - if (Objects.isNull(keyConfig) || StringUtils.isBlank(keyConfig.getContent())) { - return null; - } - return keyConfig.getContent(); - } - - } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/resources/prompt-templates.yml b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/resources/prompt-templates.yml new file mode 100644 index 000000000..b19834c1c --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/resources/prompt-templates.yml @@ -0,0 +1,335 @@ +# 提示词模板配置 +# 用于 AI 生成 SQL、解释、优化等场景 + +prompts: + # 自然语言转 SQL + nl_2_sql: + name: "nl_2_sql" + description: "将自然语言转换成SQL" + template: | + ### 请根据以下 table properties 和 SQL input{description}. {ext} + # + ### {db_type} SQL tables, with their properties: + # + # {schema} + # + # + ### SQL input: {message} + + # SQL 解释 + sql_explain: + name: "sql_explain" + description: "解释SQL" + template: | + ### 请根据以下 table properties 和 SQL input{description}. {ext} + # + ### {db_type} SQL tables, with their properties: + # + # {schema} + # + # + ### SQL input: {message} + + # SQL 优化 + sql_optimizer: + name: "sql_optimizer" + description: "提供优化建议" + template: | + ### 请根据以下 table properties 和 SQL input{description}. {ext} + # + ### {db_type} SQL tables, with their properties: + # + # {schema} + # + ### SQL Execution Plan (EXPLAIN): + # {explain_plan} + # + ### SQL input: {message} + + # SQL 转换 + sql_2_sql: + name: "sql_2_sql" + description: "进行SQL转换" + template: | + ### 请根据以下 table properties 和 SQL input{description}. {ext} + # + ### {db_type} SQL tables, with their properties: + # + # {schema} + # + # + ### SQL input: {message} + # + ### 目标 SQL 类型:{target_sql_type} + + # 表选择 + select_tables: + name: "select_tables" + description: "Select tables for query" + template: | + ### Based on the following table properties and SQL input{description}. {ext} + # + ### {db_type} SQL tables, with their properties: + # + # {schema} + # + # + ### SQL input: {message} + + Output only JSON in format: {"table_names":["table1","table2"]}. Do not output any other text. + + # 文本生成 + text_generation: + name: "text_generation" + description: "文本生成" + template: "{message}" + + # 标题生成 + title_generation: + name: "title_generation" + description: "生成标题" + template: | + 请为以下 SQL 查询生成一个简洁的中文标题(不超过 20 字),直接输出标题内容,不要包含任何引号或额外解释: + + {message} + + # 自然语言转注释 + nl_2_comment: + name: "nl_2_comment" + description: "猜测表和字段注释" + template: | + ### 请根据以下 table properties 和 SQL input{description}. {ext} + # + ### {db_type} SQL tables, with their properties: + # + # {schema} + # + ### 任务:为表和字段生成合适的中文注释 + + 请分析以下表和字段的用途,为它们生成准确、简洁的中文注释。 + + **输出格式要求(严格遵守 JSON 格式):** + + ```json + { + "table_comment": "表的注释内容", + "column_comments": [ + { + "column_name": "字段名1", + "comment": "字段1的注释内容" + }, + { + "column_name": "字段名2", + "comment": "字段2的注释内容" + } + ] + } + ``` + + **注意事项:** + 1. 只输出 JSON 内容,不要包含其他解释文字 + 2. 注释应该简洁明了,不超过 50 字 + 3. 注释应该准确反映表/字段的业务含义 + 4. 使用中文注释 + + # 批量猜测表注释 + nl_2_comment_batch: + name: "nl_2_comment_batch" + description: "批量猜测表注释" + template: | + ### 请根据以下 table properties 和 SQL input{description}. {ext} + # + ### {db_type} SQL tables, with their properties: + # + # {schema} + # + ### 任务:为所有表生成合适的中文注释 + + 请根据表名和字段名推测每个表的业务含义,生成简洁的中文注释。 + + **输出格式要求(严格遵守 JSON 格式):** + + ```json + { + "tables": [ + { + "table_name": "表名1", + "table_comment": "表1的注释内容" + }, + { + "table_name": "表名2", + "table_comment": "表2的注释内容" + } + ] + } + ``` + + **注意事项:** + 1. 只输出 JSON 内容,不要包含其他解释文字 + 2. 注释应该简洁明了,不超过 50 字 + 3. 注释应该准确反映表的业务含义 + 4. 使用中文注释 + + # 智能字段映射推荐 + nl_2_field_mapping: + name: "nl_2_field_mapping" + description: "智能字段映射推荐" + template: | + ### 任务:根据源文件字段和目标数据库表结构,推荐最佳字段映射方案 + + **数据库类型**: {db_type} + + **源文件字段列表**: + {source_fields} + + **目标表字段结构**: + {schema} + + **要求**: + 1. 根据字段名、数据类型、语义智能匹配源字段到目标字段 + 2. 考虑数据类型兼容性 + 3. 考虑字段命名语义相似性(如 name -> user_name, email -> user_email) + 4. 为每个源字段推荐最合适的目标字段 + 5. 如果某个源字段没有合适的目标字段,可以省略 + + **输出格式(严格 JSON,不要包含其他文字)**: + ```json + { + "mappings": [ + { + "sourceField": "源字段名", + "targetField": "目标字段名", + "confidence": 0.95 + } + ] + } + ``` + + **注意事项**: + 1. 只输出 JSON 内容,不要包含其他解释文字 + 2. confidence 值为 0-1 之间的小数,表示匹配置信度 + 3. 确保所有 sourceField 都在源字段列表中存在 + 4. 确保所有 targetField 都在目标表字段结构中存在 + + # 智能数据生成表达式推荐 + nl_2_data_expression: + name: "nl_2_data_expression" + description: "智能数据生成表达式推荐" + template: | + ### 任务:为数据库表的每个字段推荐合适的 datafaker 表达式 + + **数据库类型**: {db_type} + + **表字段信息**: + {schema} + + **可用的 datafaker 表达式示例(统一使用 #{Provider.snake_case_method} 格式)**: + - 车辆: #{Vehicle.vin}, #{Vehicle.drive_type}, #{Vehicle.license_plate} + - 姓名: #{Name.first_name}, #{Name.last_name}, #{Name.full_name} + - 邮箱: #{Internet.email_address}, #{Internet.url} + - 电话: #{PhoneNumber.cell_phone}, #{PhoneNumber.phone_number} + - 地址: #{Address.full_address}, #{Address.city}, #{Address.country} + - 日期: #{Date.past '30','DAYS'}, #{Date.future '30','DAYS'}, #{Date.birthday} + - 数值: #{Number.number_between '1','1000'}, #{Number.random_double '2','0','9999'} + - 文本: #{Lorem.sentence}, #{Lorem.word}, #{Lorem.paragraph} + - 公司: #{Company.name}, #{Company.industry}, #{Company.catch_phrase} + - ID: #{Internet.uuid}, #{Code.isbn10}, #{Code.asin} + - 布尔: #{Bool.bool} + + **要求**: + 1. 根据字段名、数据类型、注释推荐合适的表达式 + 2. 考虑数据类型和长度限制(如 VARCHAR 长度、DECIMAL 精度) + 3. 表达式必须符合 datafaker 语法,格式必须是 #{Provider.method} 或带参数的 #{Provider.method 'arg1','arg2'} + 4. 如果字段是主键或自增,可以跳过 + 5. 如果字段允许 NULL 且没有合适表达式,可以留空 + 6. 优先从上面的示例中选择表达式;不要输出未在示例或官方 providers 中确认存在的方法 + + **输出格式(严格 JSON,不要包含其他文字)**: + ```json + { + "column_expressions": [ + { + "column_name": "字段名", + "expression": "#{Name.first_name}", + "reason": "推荐原因" + } + ] + } + ``` + + **注意事项**: + 1. 只输出 JSON 内容,不要包含其他解释文字 + 2. expression 必须是有效的 datafaker 表达式,以 #{ 开头并以 } 结尾 + 3. reason 简要说明为什么推荐这个表达式 + 4. 确保所有 column_name 都在表字段信息中存在 + + # SQL错误修复 + sql_fix: + name: "sql_fix" + description: "SQL错误修复" + template: | + ### 任务:分析并修复SQL执行错误 + + **数据库类型**: {db_type} + + **错误信息**: + {error_message} + + **原始SQL**: + {original_sql} + + **要求**: + 1. 分析错误原因(简要说明) + 2. 提供修复后的SQL(必须是完整可执行的SQL) + 3. 如果无法修复,请说明原因 + + **输出格式**: + 先简要说明错误原因(1-2句话),然后提供修复后的SQL,使用代码块包裹: + + ```sql + 修复后的SQL + ``` + + 如果无法修复,请说明原因。 + + **注意事项**: + 1. 修复后的SQL必须是完整可执行的 + 2. 使用 ```sql 代码块包裹SQL + 3. 保持简洁,不要过多解释 + + # SQL 补全 + sql_completion: + name: "sql_completion" + description: "SQL补全" + template: | + ### 任务:根据当前 SQL 片段生成一段完整、可执行的 SQL。 + + **数据库类型**: {db_type} + + **表结构信息**: + {schema} + + **当前 SQL 片段**: + {message} + + **输出要求**: + 1. 只输出一段完整 SQL,不要输出 Markdown、代码块、解释、标题或多余文本。 + 2. SQL 必须适配当前数据库类型和表结构。 + 3. 请基于当前 SQL 片段补全或改写为最合理的完整 SQL。 + 4. 如果上下文不足,请生成最接近当前内容的合法 SQL,不要询问用户。 + + # 自然语言转 Redis 命令 + redis_nl_2_command: + name: "redis_nl_2_command" + description: "将自然语言转换成 Redis 命令" + template: | + ### 请根据 Redis command input 生成 Redis 命令。{ext} + # + ### 数据库类型: {db_type} + # + ### Redis command input: {message} + # + ### 输出要求 + 1. 只输出可执行的 Redis 命令,不要输出 SQL。 + 2. 不要输出 Markdown、代码块、解释、标题或多余文本。 + 3. 如果需要多条命令,每行输出一条 Redis 命令。 diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/test/java/ai/chat2db/server/web/api/controller/ai/prompt/PromptBuilderImplTest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/test/java/ai/chat2db/server/web/api/controller/ai/prompt/PromptBuilderImplTest.java new file mode 100644 index 000000000..dffcd4bed --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/test/java/ai/chat2db/server/web/api/controller/ai/prompt/PromptBuilderImplTest.java @@ -0,0 +1,68 @@ +package ai.chat2db.server.web.api.controller.ai.prompt; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +import ai.chat2db.server.web.api.controller.ai.enums.PromptType; + +class PromptBuilderImplTest { + + private final PromptBuilderImpl promptBuilder = new PromptBuilderImpl( + new PromptTemplateRegistry(), + new PromptValidator()); + + @Test + void buildNl2SqlWithoutHistoryKeepsInitialTemplate() { + String prompt = promptBuilder.context(PromptContext.builder() + .promptType(PromptType.NL_2_SQL) + .message("查询最近 7 天订单") + .schemaDdl("CREATE TABLE orders(id bigint, created_at datetime)") + .dataSourceType("MYSQL") + .build()).build(); + + assertTrue(prompt.contains("### SQL input: 查询最近 7 天订单")); + assertFalse(prompt.contains("连续对话修正要求")); + assertFalse(prompt.contains("上一版 SQL")); + } + + @Test + void buildNl2SqlRevisionIncludesHistoryAndPreviousSql() { + String prompt = promptBuilder.context(PromptContext.builder() + .promptType(PromptType.NL_2_SQL) + .message("按金额倒序,限制 100 条") + .schemaDdl("CREATE TABLE orders(id bigint, amount decimal(10, 2))") + .dataSourceType("MYSQL") + .isRevision(true) + .previousSql("select id, amount from orders") + .history("[{\"role\":\"user\",\"content\":\"查询订单\"}," + + "{\"role\":\"assistant\",\"content\":\"```sql\\nselect id, amount from orders\\n```\"}]") + .build()).build(); + + assertTrue(prompt.contains("连续对话修正要求")); + assertTrue(prompt.contains("最近对话历史")); + assertTrue(prompt.contains("user: 查询订单")); + assertTrue(prompt.contains("上一版 SQL")); + assertTrue(prompt.contains("select id, amount from orders")); + assertTrue(prompt.contains("生成一条完整的新 SQL")); + } + + @Test + void buildNl2SqlRevisionTruncatesLongHistory() { + String longContent = "a".repeat(2000); + + String prompt = promptBuilder.context(PromptContext.builder() + .promptType(PromptType.NL_2_SQL) + .message("只查前 100 条") + .schemaDdl("CREATE TABLE orders(id bigint)") + .dataSourceType("MYSQL") + .isRevision(true) + .previousSql("select * from orders") + .history("[{\"role\":\"user\",\"content\":\"" + longContent + "\"}]") + .build()).build(); + + assertTrue(prompt.contains("...已截断...")); + assertTrue(new PromptValidator().isValidLength(prompt)); + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/test/java/ai/chat2db/server/web/api/controller/ai/statemachine/helper/ExplainTableNameExtractorTest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/test/java/ai/chat2db/server/web/api/controller/ai/statemachine/helper/ExplainTableNameExtractorTest.java new file mode 100644 index 000000000..53deeeb6e --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/test/java/ai/chat2db/server/web/api/controller/ai/statemachine/helper/ExplainTableNameExtractorTest.java @@ -0,0 +1,62 @@ +package ai.chat2db.server.web.api.controller.ai.statemachine.helper; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +class ExplainTableNameExtractorTest { + + @Test + void extractFromMysqlTableColumn() { + ExplainResult result = ExplainResult.builder() + .planRows(List.of( + List.of("id", "select_type", "table", "type"), + List.of("1", "SIMPLE", "users", "ALL"), + List.of("1", "SIMPLE", "orders", "ref"))) + .formattedPlan("") + .build(); + + assertEquals( + List.of("users", "orders"), + ExplainTableNameExtractor.extractTableNames("select * from users join orders on users.id = orders.user_id", result)); + } + + @Test + void extractFromPostgresqlFormattedPlan() { + ExplainResult result = ExplainResult.builder() + .formattedPlan(""" + Nested Loop + -> Seq Scan on public.users u + -> Index Scan using orders_user_id_idx on orders o + """) + .build(); + + assertEquals( + List.of("users", "orders"), + ExplainTableNameExtractor.extractTableNames(null, result)); + } + + @Test + void fallbackToSqlParser() { + ExplainResult result = ExplainResult.builder() + .formattedPlan("explain output without table names") + .build(); + + assertEquals( + List.of("users", "orders"), + ExplainTableNameExtractor.extractTableNames( + "select * from `users` u join public.orders o on u.id = o.user_id", + result)); + } + + @Test + void returnsEmptyListWhenNoTableFound() { + ExplainResult result = ExplainResult.builder() + .formattedPlan("Result (cost=0.00..0.01 rows=1 width=4)") + .build(); + + assertEquals(List.of(), ExplainTableNameExtractor.extractTableNames("select 1", result)); + } +} diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/DBManage.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/DBManage.java index 40a7d0077..ed9b4bddf 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/DBManage.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/DBManage.java @@ -108,4 +108,12 @@ void dropTrigger(Connection connection, @NotEmpty String databaseName, String sc */ void dropProcedure(Connection connection, @NotEmpty String databaseName, String schemaName, @NotEmpty String triggerName); + /** + * + * @param connection + * @param databaseName + * @param schemaName + * @param tableName + */ + void truncate(Connection connection,@NotEmpty String databaseName, String schemaName, @NotEmpty String tableName); } \ No newline at end of file diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java index f7e377002..77bb4e61c 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java @@ -190,12 +190,12 @@ List indexes(Connection connection, @NotEmpty String databaseName, S /** + * @param connection * @param databaseName * @param schemaName - * @param tableName * @return */ - TableMeta getTableMeta(String databaseName, String schemaName, String tableName); + TableMeta getTableMeta(Connection connection, String databaseName, String schemaName); /** @@ -216,4 +216,16 @@ List indexes(Connection connection, @NotEmpty String databaseName, S * Get command executor. */ CommandExecutor getCommandExecutor(); + + /** + * Querying all foreign keys under a table. + * + * @param connection + * @param databaseName + * @param schemaName + * @param tableName if null, returns foreign keys for all tables + * @return List of foreign keys + */ + List foreignKeys(Connection connection, @NotEmpty String databaseName, String schemaName, String tableName); + } \ No newline at end of file diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/SqlBuilder.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/SqlBuilder.java index 237b4de45..3fe7e9a08 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/SqlBuilder.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/SqlBuilder.java @@ -81,4 +81,56 @@ public interface SqlBuilder { */ String generateSqlBasedOnResults(String tableName, List
    headerList, List operations); + /** + * 构建导入SQL(参数化形式,返回SQL模板供PreparedStatement使用) + * + * @param tableName 表名 + * @param headerList 表头元数据 + * @param primaryKeyColumns 主键列名 + * @param mode 导入模式 + * @return SQL模板字符串(使用?占位符) + */ + String buildImportSql(String tableName, List
    headerList, List primaryKeyColumns, String mode); + + /** + * Generate add foreign key sql + */ + default String buildAddForeignKeySql(ForeignKey fk) { + return null; + } + + /** + * Generate drop foreign key sql + */ + default String buildDropForeignKeySql(ForeignKey fk) { + return null; + } + + /** + * Generate OPTIMIZE TABLE SQL + * Returns null if not supported by this database + */ + default String buildOptimizeTableSql(String databaseName, String schemaName, String tableName) { + return null; + } + + /** + * Generate ANALYZE TABLE SQL + * Returns null if not supported by this database + */ + default String buildAnalyzeTableSql(String databaseName, String schemaName, String tableName) { + return null; + } + + /** + * Generate EXPLAIN SQL for query execution plan + * Returns null if not supported by this database + * + * @param originalSql the SQL statement to explain + * @return EXPLAIN SQL statement + */ + default String buildExplainSql(String originalSql) { + return null; + } + } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultDBManage.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultDBManage.java index df7981d83..011fb8065 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultDBManage.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultDBManage.java @@ -11,6 +11,8 @@ import ai.chat2db.spi.sql.IDriverManager; import ai.chat2db.spi.sql.SQLExecutor; import ai.chat2db.spi.ssh.SSHManager; +import jakarta.validation.constraints.NotEmpty; + import com.jcraft.jsch.JSchException; import com.jcraft.jsch.Session; import org.apache.commons.lang3.StringUtils; @@ -99,8 +101,9 @@ public void createDatabase(Connection connection,String databaseName) { } @Override - public void dropDatabase(Connection connection,String databaseName) { - + public void dropDatabase(Connection connection, String databaseName) { + String sql = "DROP DATABASE " + databaseName; + SQLExecutor.getInstance().execute(connection, sql, resultSet -> null); } @Override @@ -138,4 +141,12 @@ public void dropTable(Connection connection,String databaseName, String schemaNa String sql = "DROP TABLE "+ tableName ; SQLExecutor.getInstance().execute(connection,sql, resultSet -> null); } + + @Override + public void truncate(Connection connection, @NotEmpty String databaseName, String schemaName, + @NotEmpty String tableName) { + String sql = "TRUNCATE TABLE "+ tableName; + SQLExecutor.getInstance().execute(connection,sql, resultSet -> null); + } + } \ No newline at end of file diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java index a3ba280bb..124e9de18 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java @@ -3,23 +3,36 @@ import java.sql.Connection; import java.util.Arrays; import java.util.List; -import java.util.Map; import java.util.stream.Collectors; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; + import ai.chat2db.spi.CommandExecutor; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.ValueHandler; -import ai.chat2db.spi.model.*; +import ai.chat2db.spi.model.ColumnType; +import ai.chat2db.spi.model.Database; +import ai.chat2db.spi.model.ForeignKey; +import ai.chat2db.spi.model.Function; +import ai.chat2db.spi.model.Procedure; +import ai.chat2db.spi.model.Schema; +import ai.chat2db.spi.model.Table; +import ai.chat2db.spi.model.TableColumn; +import ai.chat2db.spi.model.TableIndex; +import ai.chat2db.spi.model.TableMeta; +import ai.chat2db.spi.model.Trigger; +import ai.chat2db.spi.model.Type; +import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.sql.SQLExecutor; -import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang3.StringUtils; /** * @author jipengfei * @version : DefaultMetaService.java */ public class DefaultMetaService implements MetaData { + @Override public List databases(Connection connection) { return SQLExecutor.getInstance().databases(connection); @@ -28,9 +41,9 @@ public List databases(Connection connection) { @Override public List schemas(Connection connection, String databaseName) { List schemas = SQLExecutor.getInstance().schemas(connection, databaseName, null); - if(StringUtils.isNotBlank(databaseName) && CollectionUtils.isNotEmpty(schemas)){ - for ( Schema schema : schemas) { - if(StringUtils.isBlank(schema.getDatabaseName())){ + if (StringUtils.isNotBlank(databaseName) && CollectionUtils.isNotEmpty(schemas)) { + for (Schema schema : schemas) { + if (StringUtils.isBlank(schema.getDatabaseName())) { schema.setDatabaseName(databaseName); } } @@ -40,12 +53,22 @@ public List schemas(Connection connection, String databaseName) { @Override public String tableDDL(Connection connection, String databaseName, String schemaName, String tableName) { + List
    tables = this.tables(connection, StringUtils.isEmpty(databaseName) ? null : databaseName, StringUtils.isEmpty(schemaName) ? null : schemaName, tableName); + if(CollectionUtils.isEmpty(tables)){ + return null; + } + SqlBuilder sqlBuilder = Chat2DBContext.getSqlBuilder(); + for (Table table : tables) { + table.setColumnList(columns(connection, databaseName, schemaName, tableName)); + table.setIndexList(indexes(connection, databaseName, schemaName, tableName)); + return sqlBuilder.buildCreateTableSql(table); + } return null; } @Override public List
    tables(Connection connection, String databaseName, String schemaName, String tableName) { - return SQLExecutor.getInstance().tables(connection, StringUtils.isEmpty(databaseName) ? null : databaseName, StringUtils.isEmpty(schemaName) ? null : schemaName, tableName, new String[]{"TABLE","SYSTEM TABLE"}); + return SQLExecutor.getInstance().tables(connection, StringUtils.isEmpty(databaseName) ? null : databaseName, StringUtils.isEmpty(schemaName) ? null : schemaName, tableName, new String[]{"TABLE", "SYSTEM TABLE"}); } @Override @@ -61,10 +84,10 @@ public List
    views(Connection connection, String databaseName, String sche @Override public List functions(Connection connection, String databaseName, String schemaName) { List functions = SQLExecutor.getInstance().functions(connection, StringUtils.isEmpty(databaseName) ? null : databaseName, StringUtils.isEmpty(schemaName) ? null : schemaName); - if(CollectionUtils.isEmpty(functions)){ + if (CollectionUtils.isEmpty(functions)) { return functions; } - return functions.stream().filter(function -> StringUtils.isNotBlank(function.getFunctionName())).collect(Collectors.toList()); + return functions.stream().filter(function -> StringUtils.isNotBlank(function.getName())).collect(Collectors.toList()); } @Override @@ -74,12 +97,12 @@ public List triggers(Connection connection, String databaseName, String @Override public List procedures(Connection connection, String databaseName, String schemaName) { - List procedures = SQLExecutor.getInstance().procedures(connection, StringUtils.isEmpty(databaseName) ? null : databaseName, StringUtils.isEmpty(schemaName) ? null : schemaName); + List procedures = SQLExecutor.getInstance().procedures(connection, StringUtils.isEmpty(databaseName) ? null : databaseName, StringUtils.isEmpty(schemaName) ? null : schemaName); - if(CollectionUtils.isEmpty(procedures)){ + if (CollectionUtils.isEmpty(procedures)) { return procedures; } - return procedures.stream().filter(function -> StringUtils.isNotBlank(function.getProcedureName())).collect(Collectors.toList()); + return procedures.stream().filter(function -> StringUtils.isNotBlank(function.getName())).collect(Collectors.toList()); } @Override @@ -124,13 +147,32 @@ public SqlBuilder getSqlBuilder() { } @Override - public TableMeta getTableMeta(String databaseName, String schemaName, String tableName) { - return null; + public TableMeta getTableMeta(Connection connection, String databaseName, String schemaName) { + List types = SQLExecutor.getInstance().types(connection); + + return TableMeta.builder() + .columnTypes(types.stream() + .map(type -> new ColumnType( + type.getTypeName(), // 列类型名称 + true, // 假设支持长度 + true, // 假设支持小数位数 + type.getNullable() != 0, // 根据 nullable 字段判断是否支持可为空 + type.getAutoIncrement() != null && type.getAutoIncrement(), // 根据 autoIncrement 字段判断 + true, // 假设支持字符集 + true, // 假设支持排序规则 + true, // 假设支持注释 + true, // 假设支持默认值 + false, // 假设不支持扩展特性 + true, // 假设支持值的特性 + false // 假设不支持单位的特性 + )) + .collect(Collectors.toList())) + .build(); } @Override public String getMetaDataName(String... names) { - return Arrays.stream(names).filter(name -> StringUtils.isNotBlank(name)).collect(Collectors.joining(".")); + return Arrays.stream(names).filter(StringUtils::isNotBlank).collect(Collectors.joining(".")); } @Override @@ -142,4 +184,9 @@ public ValueHandler getValueHandler() { public CommandExecutor getCommandExecutor() { return SQLExecutor.getInstance(); } + + @Override + public List foreignKeys(Connection connection, String databaseName, String schemaName, String tableName) { + return SQLExecutor.getInstance().foreignKeys(connection, databaseName, schemaName, tableName); + } } \ No newline at end of file diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultSqlBuilder.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultSqlBuilder.java index 115a6ef8b..9288c238c 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultSqlBuilder.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultSqlBuilder.java @@ -1,37 +1,342 @@ package ai.chat2db.spi.jdbc; -import ai.chat2db.server.tools.base.wrapper.result.DataResult; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; + +import com.google.common.collect.Lists; + import ai.chat2db.spi.MetaData; import ai.chat2db.spi.SqlBuilder; -import ai.chat2db.spi.model.*; +import ai.chat2db.spi.model.Database; +import ai.chat2db.spi.model.ForeignKey; +import ai.chat2db.spi.model.Header; +import ai.chat2db.spi.model.OrderBy; +import ai.chat2db.spi.model.ResultOperation; +import ai.chat2db.spi.model.Schema; +import ai.chat2db.spi.model.Table; +import ai.chat2db.spi.model.TableColumn; +import ai.chat2db.spi.model.TableIndex; +import ai.chat2db.spi.model.TableIndexColumn; +import ai.chat2db.spi.model.VirtualForeignKey; import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.util.SqlUtils; -import com.google.common.collect.Lists; import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.select.OrderByElement; import net.sf.jsqlparser.statement.select.PlainSelect; import net.sf.jsqlparser.statement.select.Select; -import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang3.ObjectUtils; -import org.apache.commons.lang3.StringUtils; - -import java.util.ArrayList; -import java.util.List; public class DefaultSqlBuilder implements SqlBuilder { @Override public String buildCreateTableSql(Table table) { - return null; + StringBuilder script = new StringBuilder(); + if(StringUtils.isNotBlank(table.getAiComment())){ + script.append(" -- ").append(table.getAiComment()).append("\n"); + } + script.append("CREATE TABLE "); + + // 添加数据库名 + if (StringUtils.isNotBlank(table.getDatabaseName())) { + script.append("`").append(table.getDatabaseName()).append("`").append("."); + } + script.append("`").append(table.getName()).append("`").append(" (").append("\n"); + + // 添加列 + appendColumns(script, table.getColumnList()); + + // 添加索引 + appendIndexes(script, table.getIndexList()); + + // 添加外键约束 + appendForeignKeys(script, table.getForeignKeyList()); + + // 添加虚拟外键约束 + appendVirtualForeignKeys(script, table.getVirtualForeignKeyList()); + + // 移除最后的逗号 + if (script.length() > 2) { + script.setLength(script.length() - 2); + } + script.append("\n)"); + + // 添加表的其他属性 + appendTableAttributes(script, table); + + script.append(";"); + + return script.toString(); + } + + protected void appendTableAttributes(StringBuilder script, Table table) { + // 添加表的存储引擎 + if (table.getEngine() != null) { + script.append(" ENGINE = ").append(table.getEngine()); + } + + // 添加字符集 + if (table.getCharset() != null) { + script.append(" DEFAULT CHARSET = ").append(table.getCharset()); + } + + // 添加排序规则 + if (table.getCollate() != null) { + script.append(" COLLATE = ").append(table.getCollate()); + } + + // 添加分区信息 + if (table.getPartition() != null) { + script.append(" PARTITION BY ").append(table.getPartition()); + } + + // 添加表空间信息 + if (table.getTablespace() != null) { + script.append(" TABLESPACE = ").append(table.getTablespace()); + } + + // 添加注释 + if (table.getComment() != null) { + script.append(" COMMENT = '").append(table.getComment()).append("'"); + } + } + + protected void appendIndexes(StringBuilder script, List indexList) { + for (TableIndex index : indexList) { + script.append(" INDEX `").append(index.getName()).append("` ("); + + // 添加索引包含的列 + for (TableIndexColumn column : index.getColumnList()) { + script.append("`").append(column.getColumnName()).append("`, "); + } + + // 移除最后的逗号 + if (!script.isEmpty()) { + script.setLength(script.length() - 2); + } + + script.append(")"); + + // 添加索引的其他属性 + if (Boolean.TRUE.equals(index.getUnique())) { + script.append(" UNIQUE"); + } + + if (index.getComment() != null) { + script.append(" COMMENT '").append(index.getComment()).append("'"); + } + + script.append(",\n"); + } + } + + protected void appendForeignKeys(StringBuilder script, List foreignKeyList) { + if (CollectionUtils.isEmpty(foreignKeyList)) { + return; + } + for (ForeignKey fk : foreignKeyList) { + script.append(" ").append(buildForeignKeyClause(fk)).append(",\n"); + } + } + + protected void appendVirtualForeignKeys(StringBuilder script, List virtualForeignKeyList) { + if (CollectionUtils.isEmpty(virtualForeignKeyList)) { + return; + } + for (VirtualForeignKey fk : virtualForeignKeyList) { + script.append(" ").append(buildForeignKeyClause(fk)).append(",\n"); + } + } + + protected void appendColumns(StringBuilder script, List columnList) { + for (TableColumn column : columnList) { + script.append(" `").append(column.getName()).append("` ") + .append(column.getDataType()); + + // 添加列的大小(如果适用) + if (column.getColumnSize() != null && column.getColumnSize() > 0) { + script.append("(").append(column.getColumnSize()); + if (column.getDecimalDigits() != null && column.getDecimalDigits() > 0) { + script.append(", ").append(column.getDecimalDigits()); + } + script.append(")"); + } + + // 添加是否自增 + if (Boolean.TRUE.equals(column.getAutoIncrement())) { + script.append(" AUTO_INCREMENT"); + } + + // 添加默认值 + if (column.getDefaultValue() != null) { + script.append(" DEFAULT '").append(column.getDefaultValue()).append("'"); + } + + // 添加注释 + if (StringUtils.isNotBlank(column.getComment())) { + script.append(" COMMENT '").append(column.getComment()).append("'"); + } + + // 添加是否主键 + if (Boolean.TRUE.equals(column.getPrimaryKey())) { + script.append(" PRIMARY KEY"); + } + + // 添加注释 + if (StringUtils.isNotBlank(column.getAiComment())) { + script.append(" -- ").append(column.getAiComment()); + } + script.append(",\n"); + } } @Override public String buildModifyTaleSql(Table oldTable, Table newTable) { - return null; + if (oldTable.equals(newTable)) { + return ""; + } + StringBuilder script = new StringBuilder(); + script.append("ALTER TABLE "); + + // 添加数据库名 + if (StringUtils.isNotBlank(oldTable.getDatabaseName())) { + script.append("`").append(oldTable.getDatabaseName()).append("`").append("."); + } + script.append("`").append(oldTable.getName()).append("`").append("\n"); + + // 修改表名和注释 + modifyTableNameAndComment(script, oldTable, newTable); + + // 修改列 + modifyColumns(script, oldTable, newTable); + + // 修改索引 + modifyIndexes(script, oldTable, newTable); + + // 修改外键 + modifyForeignKeys(script, oldTable, newTable); + + // 添加列重排逻辑 + script.append(buildGenerateReorderColumnSql(oldTable, newTable)); + + // 移除最后的逗号 + if (script.length() > 2) { + script.setLength(script.length() - 2); + script.append(";"); + } + + return script.toString(); + } + + // 修改表名和注释的方法 + protected void modifyTableNameAndComment(StringBuilder script, Table oldTable, Table newTable) { + if (!StringUtils.equalsIgnoreCase(oldTable.getName(), newTable.getName())) { + script.append("\t").append("RENAME TO ").append("`").append(newTable.getName()).append("`").append(",\n"); + } + if (!StringUtils.equalsIgnoreCase(oldTable.getComment(), newTable.getComment())) { + script.append("\t").append("COMMENT=").append("'").append(newTable.getComment()).append("'").append(",\n"); + } + if (newTable.getIncrementValue() != null + && !Objects.equals(oldTable.getIncrementValue(), newTable.getIncrementValue())) { + script.append("\t").append("AUTO_INCREMENT=").append(newTable.getIncrementValue()).append(",\n"); + } + } + + // 修改列的方法 + protected void modifyColumns(StringBuilder script, Table oldTable, Table newTable) { + } + + // 修改索引的方法 + protected void modifyIndexes(StringBuilder script, Table oldTable, Table newTable) { + } + + protected void modifyForeignKeys(StringBuilder script, Table oldTable, Table newTable) { + List oldFKs = oldTable != null && oldTable.getForeignKeyList() != null + ? oldTable.getForeignKeyList() : Lists.newArrayList(); + List newFKs = newTable != null && newTable.getForeignKeyList() != null + ? newTable.getForeignKeyList() : Lists.newArrayList(); + + java.util.Map oldFKMap = oldFKs.stream() + .collect(java.util.stream.Collectors.toMap(this::buildFKKey, f -> f, (o1, o2) -> o1)); + java.util.Map newFKMap = newFKs.stream() + .collect(java.util.stream.Collectors.toMap(this::buildFKKey, f -> f, (o1, o2) -> o1)); + + for (ForeignKey newFK : newFKs) { + if (!oldFKMap.containsKey(buildFKKey(newFK))) { + script.append("\t").append("ADD ").append(buildForeignKeyClause(newFK)).append(",\n"); + } + } + + for (ForeignKey oldFK : oldFKs) { + if (!newFKMap.containsKey(buildFKKey(oldFK))) { + script.append("\t").append("DROP FOREIGN KEY `").append(oldFK.getName()).append("`,\n"); + } + } + } + + private String buildFKKey(ForeignKey fk) { + return StringUtils.defaultString(fk.getTableName()) + ":" + + StringUtils.defaultString(fk.getColumn()) + ":" + + StringUtils.defaultString(fk.getReferencedTable()) + ":" + + StringUtils.defaultString(fk.getReferencedColumn()); + } + + protected String buildForeignKeyClause(ForeignKey fk) { + StringBuilder script = new StringBuilder(); + script.append("CONSTRAINT `").append(fk.getName()).append("` FOREIGN KEY (`") + .append(fk.getColumn()).append("`) REFERENCES `") + .append(fk.getReferencedTable()).append("` (`") + .append(fk.getReferencedColumn()).append("`)"); + + if (fk.getDeleteRule() == 0) { + script.append(" ON DELETE CASCADE"); + } else if (fk.getDeleteRule() == 1) { + script.append(" ON DELETE RESTRICT"); + } else if (fk.getDeleteRule() == 2) { + script.append(" ON DELETE SET NULL"); + } + if (fk.getUpdateRule() == 0) { + script.append(" ON UPDATE CASCADE"); + } else if (fk.getUpdateRule() == 1) { + script.append(" ON UPDATE RESTRICT"); + } else if (fk.getUpdateRule() == 2) { + script.append(" ON UPDATE SET NULL"); + } + return script.toString(); + } + + public String buildAddForeignKeySql(ForeignKey fk) { + StringBuilder script = new StringBuilder(); + script.append("ALTER TABLE "); + if (StringUtils.isNotBlank(fk.getDatabaseName())) { + script.append("`").append(fk.getDatabaseName()).append("`.`"); + } + script.append("`").append(fk.getTableName()).append("` ADD ") + .append(buildForeignKeyClause(fk)).append(";"); + return script.toString(); + } + + public String buildDropForeignKeySql(ForeignKey fk) { + StringBuilder script = new StringBuilder(); + script.append("ALTER TABLE "); + if (StringUtils.isNotBlank(fk.getDatabaseName())) { + script.append("`").append(fk.getDatabaseName()).append("`.`"); + } + script.append("`").append(fk.getTableName()).append("` DROP FOREIGN KEY `") + .append(fk.getName()).append("`;"); + return script.toString(); + } + + protected String buildGenerateReorderColumnSql(Table oldTable, Table newTable) { + return ""; } + @Override public String pageLimit(String sql, int offset, int pageNo, int pageSize) { return null; @@ -101,13 +406,13 @@ public String generateSqlBasedOnResults(String tableName, List
    headerLis List odlRow = operation.getOldDataList(); String sql = ""; if ("UPDATE".equalsIgnoreCase(operation.getType())) { - sql = getUpdateSql(tableName,headerList, row, odlRow, metaSchema, keyColumns, false); + sql = getUpdateSql(tableName, headerList, row, odlRow, metaSchema, keyColumns, false); } else if ("CREATE".equalsIgnoreCase(operation.getType())) { - sql = getInsertSql(tableName,headerList, row, metaSchema); + sql = getInsertSql(tableName, headerList, row, metaSchema); } else if ("DELETE".equalsIgnoreCase(operation.getType())) { - sql = getDeleteSql(tableName,headerList, odlRow, metaSchema, keyColumns); + sql = getDeleteSql(tableName, headerList, odlRow, metaSchema, keyColumns); } else if ("UPDATE_COPY".equalsIgnoreCase(operation.getType())) { - sql = getUpdateSql(tableName,headerList, row, row, metaSchema, keyColumns, true); + sql = getUpdateSql(tableName, headerList, row, row, metaSchema, keyColumns, true); } stringBuilder.append(sql + ";\n"); @@ -178,7 +483,7 @@ private String buildWhere(List
    headerList, List row, MetaData me return script.toString(); } - private String getInsertSql(String tableName, List
    headerList, List row, MetaData metaSchema) { + private String getInsertSql(String tableName, List
    headerList, List row, MetaData metaSchema) { if (CollectionUtils.isEmpty(row) || ObjectUtils.allNull(row.toArray())) { return ""; } @@ -234,4 +539,101 @@ private String getUpdateSql(String tableName, List
    headerList, List headerList, List primaryKeyColumns, String mode) { + MetaData metaSchema = Chat2DBContext.getMetaData(); + switch (mode) { + case "INSERT": + return buildImportInsertSql(tableName, headerList, metaSchema); + case "UPDATE": + return buildImportUpdateSql(tableName, headerList, primaryKeyColumns, metaSchema); + case "UPSERT": + return buildImportUpsertSql(tableName, headerList, primaryKeyColumns, metaSchema); + case "INSERT_IGNORE": + return buildImportInsertIgnoreSql(tableName, headerList, metaSchema); + case "DELETE": + return buildImportDeleteSql(tableName, headerList, primaryKeyColumns, metaSchema); + default: + return buildImportInsertSql(tableName, headerList, metaSchema); + } + } + + protected String buildImportInsertSql(String tableName, List
    headerList, MetaData metaSchema) { + StringBuilder sql = new StringBuilder("INSERT INTO "); + sql.append(tableName).append(" ("); + for (int i = 0; i < headerList.size(); i++) { + if (i > 0) sql.append(","); + sql.append(metaSchema.getMetaDataName(headerList.get(i).getName())); + } + sql.append(") VALUES ("); + for (int i = 0; i < headerList.size(); i++) { + if (i > 0) sql.append(","); + sql.append("?"); + } + sql.append(")"); + return sql.toString(); + } + + protected String buildImportUpdateSql(String tableName, List
    headerList, List primaryKeyColumns, + MetaData metaSchema) { + StringBuilder sql = new StringBuilder("UPDATE "); + sql.append(tableName).append(" SET "); + boolean first = true; + for (Header header : headerList) { + if (primaryKeyColumns != null && primaryKeyColumns.contains(header.getName())) { + continue; + } + if (!first) sql.append(","); + sql.append(metaSchema.getMetaDataName(header.getName())).append("=?"); + first = false; + } + sql.append(" WHERE "); + first = true; + if (primaryKeyColumns != null && !primaryKeyColumns.isEmpty()) { + for (String pk : primaryKeyColumns) { + if (!first) sql.append(" AND "); + sql.append(metaSchema.getMetaDataName(pk)).append("=?"); + first = false; + } + } else { + // 无主键时使用所有列作为匹配条件(不推荐,但作为兜底) + for (Header header : headerList) { + if (!first) sql.append(" AND "); + sql.append(metaSchema.getMetaDataName(header.getName())).append("=?"); + first = false; + } + } + return sql.toString(); + } + + protected String buildImportUpsertSql(String tableName, List
    headerList, List primaryKeyColumns, + MetaData metaSchema) { + // 默认使用INSERT实现 + return buildImportInsertSql(tableName, headerList, metaSchema); + } + + protected String buildImportInsertIgnoreSql(String tableName, List
    headerList, MetaData metaSchema) { + // 默认使用INSERT实现 + return buildImportInsertSql(tableName, headerList, metaSchema); + } + + protected String buildImportDeleteSql(String tableName, List
    headerList, List primaryKeyColumns, + MetaData metaSchema) { + StringBuilder sql = new StringBuilder("DELETE FROM "); + sql.append(tableName).append(" WHERE "); + if (primaryKeyColumns != null && !primaryKeyColumns.isEmpty()) { + for (int i = 0; i < primaryKeyColumns.size(); i++) { + if (i > 0) sql.append(" AND "); + sql.append(metaSchema.getMetaDataName(primaryKeyColumns.get(i))).append("=?"); + } + } else { + // 无主键时使用所有列作为匹配条件 + for (int i = 0; i < headerList.size(); i++) { + if (i > 0) sql.append(" AND "); + sql.append(metaSchema.getMetaDataName(headerList.get(i).getName())).append("=?"); + } + } + return sql.toString(); + } } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/BaseModel.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/BaseModel.java new file mode 100644 index 000000000..d865cb3db --- /dev/null +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/BaseModel.java @@ -0,0 +1,49 @@ +package ai.chat2db.spi.model; + +public interface BaseModel { + /** + * 获取数据库名称 + * + * @return + */ + String getDatabaseName(); + + /* + * 获取schema名称 + */ + String getSchemaName(); + + /** + * 获取表名 + * + * @return + */ + String getTableName(); + + /** + * 获取类型 + * @return + */ + Class getClassType(); + + /** + * 设置数据库名称 + * + * @param databaseName + */ + void setDatabaseName(String databaseName); + + /** + * 设置schema名称 + * + * @param schemaName + */ + void setSchemaName(String schemaName); + + /** + * 设置表名称 + * + * @param tableName + */ + void setTableName(String tableName); +} diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ColumnType.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ColumnType.java index 3fd15c23a..5fd8529f1 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ColumnType.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ColumnType.java @@ -3,20 +3,36 @@ import lombok.AllArgsConstructor; import lombok.Data; +/** + * ColumnType类用于表示数据库列的类型属性 + * 该类提供了各种布尔类型的属性,以指示列类型是否支持特定的特性 + */ @Data @AllArgsConstructor public class ColumnType { + // 表示列类型的名称 private String typeName; + // 表示列类型是否支持指定的长度 private boolean supportLength; + // 表示列类型是否支持指定的小数位数 private boolean supportScale; + // 表示列类型是否支持是否可为空的特性 private boolean supportNullable; + // 表示列类型是否支持自动递增特性 private boolean supportAutoIncrement; + // 表示列类型是否支持指定字符集 private boolean supportCharset; + // 表示列类型是否支持排序规则 private boolean supportCollation; + // 表示列类型是否支持注释 private boolean supportComments; + // 表示列类型是否支持默认值 private boolean supportDefaultValue; + // 表示列类型是否支持扩展特性 private boolean supportExtent; + // 表示列类型是否支持值的特性 private boolean supportValue; + // 表示列类型是否支持单位的特性 private boolean supportUnit; - } + diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Command.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Command.java index cca272e23..41396b464 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Command.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Command.java @@ -12,6 +12,11 @@ public class Command { @NotNull private String script; + /** + * 原始编辑器中执行脚本的起始行(从1开始) + */ + private Integer scriptStartLine; + /** * 控制台id */ diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ErDiagram.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ErDiagram.java new file mode 100644 index 000000000..06bc4e148 --- /dev/null +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ErDiagram.java @@ -0,0 +1,93 @@ +package ai.chat2db.spi.model; + +import java.util.List; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * ER图模型,用于表示数据库表之间的关系图 + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class ErDiagram { + + /** + * 节点列表,每个节点代表一张表 + */ + private List nodes; + + /** + * 边列表,每条边代表表之间的外键关系 + */ + private List edges; + + /** + * ER图节点,代表一张数据库表 + */ + @Data + @SuperBuilder + @NoArgsConstructor + @AllArgsConstructor + public static class Node { + /** + * 节点唯一标识,使用表名 + */ + private String id; + /** + * 表名 + */ + private String name; + /** + * 表注释 + */ + private String comment; + /** + * 表的列数量 + */ + private Integer columnCount; + } + + /** + * ER图边,代表表之间的外键关系 + */ + @Data + @SuperBuilder + @NoArgsConstructor + @AllArgsConstructor + public static class Edge { + /** + * 边唯一标识,使用外键名或生成的ID + */ + private String id; + /** + * 源表名(拥有外键的表) + */ + private String source; + /** + * 目标表名(被引用的表) + */ + private String target; + /** + * 源表的外键列名 + */ + private String sourceColumn; + /** + * 目标表被引用的列名 + */ + private String targetColumn; + /** + * 关系描述,格式:sourceColumn -> targetColumn + */ + private String label; + /** + * 是否为虚拟外键(根据命名规范推断的外键) + */ + private Boolean virtual; + } + +} \ No newline at end of file diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ExecuteResult.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ExecuteResult.java index 1f28b2e2a..f999ff90e 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ExecuteResult.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ExecuteResult.java @@ -18,6 +18,31 @@ @AllArgsConstructor public class ExecuteResult { + /** + * 语句序号(从1开始) + */ + private Integer statementIndex; + + /** + * 语句在原始脚本中的起始行(从1开始) + */ + private Integer statementStartLine; + + /** + * 语句在原始脚本中的结束行(从1开始) + */ + private Integer statementEndLine; + + /** + * 错误在当前语句内的行号(从1开始,解析失败时为空) + */ + private Integer errorLineInStatement; + + /** + * 错误在整段脚本中的绝对行号(从1开始,解析失败时为空) + */ + private Integer errorLine; + /** * 是否成功标志位 */ @@ -105,4 +130,14 @@ public class ExecuteResult { * 表名 */ private String tableName; + + /** + * 虚拟外键建议列表(根据SQL解析自动生成) + */ + private List vkSuggestions; + + /** + * JSqlParser parsed AST statement (cached for reuse) + */ + private transient net.sf.jsqlparser.statement.Statement jsqlStatement; } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ForeignKey.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ForeignKey.java new file mode 100644 index 000000000..3c313a4fc --- /dev/null +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ForeignKey.java @@ -0,0 +1,90 @@ +package ai.chat2db.spi.model; + +import com.fasterxml.jackson.annotation.JsonAlias; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class ForeignKey implements IndexModel { + + /** + * 数据库存储的外键ID + */ + private Long id; + + // 外键名称 + @JsonAlias({"FK_NAME"}) + @LuceneField(name = "name", type = LuceneFieldType.TEXT, sort = true) + private String name; + // 当前表 + @JsonAlias({"FKTABLE_NAME"}) + @LuceneField(name = "tableName", type = LuceneFieldType.STRING) + private String tableName; + + /** + * 索引所属schema + */ + @JsonAlias({"PKTABLE_SCHEM"}) + @LuceneField(name = "schemaName", type = LuceneFieldType.STRING) + private String schemaName; + + /** + * 数据库名 + */ + @JsonAlias({"PKTABLE_CAT"}) + @LuceneField(name = "databaseName", type = LuceneFieldType.STRING) + private String databaseName; + // 当前表的列 + @JsonAlias({"FKCOLUMN_NAME"}) + private String column; + // 引用的表 + @JsonAlias({"PKTABLE_NAME"}) + private String referencedTable; + // 引用的列 + @JsonAlias({"PKCOLUMN_NAME"}) + private String referencedColumn; + + /** + * 更新规则 + * @see java.sql.DatabaseMetaData#importedKeyCascade + * @see java.sql.DatabaseMetaData#importedKeyRestrict + * @see java.sql.DatabaseMetaData#importedKeySetNull + * @see java.sql.DatabaseMetaData#importedKeyNoAction + */ + @JsonAlias({"UPDATE_RULE"}) + private int updateRule; + /** + * 删除规则 + * @see java.sql.DatabaseMetaData#importedKeyCascade + * @see java.sql.DatabaseMetaData#importedKeyRestrict + * @see java.sql.DatabaseMetaData#importedKeySetNull + * @see java.sql.DatabaseMetaData#importedKeyNoAction + */ + @JsonAlias({"DELETE_RULE"}) + private int deleteRule; + + // 备注(可选) + @JsonAlias({"COMMENT"}) + @LuceneField(name = "comment", type = LuceneFieldType.TEXT) + private String comment; + + + private String editStatus; + + /** + * AI生成的注释 + */ + @LuceneField(name = "aiComment", type = LuceneFieldType.TEXT) + private String aiComment; + + /** + * 版本 + */ + private Long version; + +} diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Function.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Function.java index b111ed8f1..a59759b2e 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Function.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Function.java @@ -15,7 +15,7 @@ @SuperBuilder @NoArgsConstructor @AllArgsConstructor -public class Function { +public class Function implements IndexModel { //FUNCTION_CAT String => function catalog (may be null) //FUNCTION_SCHEM String => function schema (may be null) //FUNCTION_NAME String => function name. This is the name used to invoke the function @@ -28,16 +28,25 @@ public class Function { // @JsonAlias({"FUNCTION_CAT"}) + @LuceneField(name = "databaseName", type = LuceneFieldType.STRING) private String databaseName; @JsonAlias({"FUNCTION_SCHEM"}) + @LuceneField(name = "schemaName", type = LuceneFieldType.STRING) private String schemaName; @JsonAlias({"FUNCTION_NAME"}) - private String functionName; + @LuceneField(name = "name", type = LuceneFieldType.TEXT, sort = true) + private String name; @JsonAlias({"REMARKS"}) - private String remarks; + @LuceneField(name = "comment", type = LuceneFieldType.TEXT) + private String comment; + + @LuceneField(name = "aiComment", type = LuceneFieldType.TEXT) + private String aiComment; + + private Long version; @JsonAlias({"FUNCTION_TYPE"}) private Short functionType; @@ -47,4 +56,12 @@ public class Function { private String functionBody; + @Override + public String getTableName() { + return null; + } + + @Override + public void setTableName(String tableName) { + } } \ No newline at end of file diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/IndexModel.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/IndexModel.java new file mode 100644 index 000000000..3bf90bb01 --- /dev/null +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/IndexModel.java @@ -0,0 +1,64 @@ +package ai.chat2db.spi.model; + +public interface IndexModel extends BaseModel { + /** + * 获取索引名称 + * + * @return + */ + String getName(); + + /** + * 获取索引备注 + * + * @return + */ + String getComment(); + + /** + * 获取索引备注 + * + * @return + */ + String getAiComment(); + + /** + * 获取版本 + * + * @return + */ + Long getVersion(); + + /** + * 设置版本 + * + * @param version + */ + void setVersion(Long version); + + /** + * 设置索引名称 + * + * @param name + */ + void setName(String name); + + /** + * 设置索引备注 + * + * @param comment + */ + void setComment(String comment); + + /** + * 设置索引备注 + * + * @param aiComment + */ + void setAiComment(String aiComment); + + @Override + default Class getClassType() { + return this.getClass(); + } +} diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/LuceneField.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/LuceneField.java new file mode 100644 index 000000000..c1285ed70 --- /dev/null +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/LuceneField.java @@ -0,0 +1,33 @@ +package ai.chat2db.spi.model; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Lucene 字段注解,用于声明字段在 Lucene 索引中的行为 + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface LuceneField { + /** + * Lucene 索引中的字段名 + */ + String name(); + + /** + * 字段类型 + */ + LuceneFieldType type() default LuceneFieldType.TEXT; + + /** + * 是否支持排序(需要添加 DocValues) + */ + boolean sort() default false; + + /** + * 是否存储原始值(Field.Store.YES) + */ + boolean store() default false; +} diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/LuceneFieldType.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/LuceneFieldType.java new file mode 100644 index 000000000..1976f266c --- /dev/null +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/LuceneFieldType.java @@ -0,0 +1,31 @@ +package ai.chat2db.spi.model; + +/** + * Lucene 字段类型枚举 + */ +public enum LuceneFieldType { + /** + * 文本类型,支持全文搜索 + */ + TEXT, + + /** + * 字符串类型,精确匹配,支持排序 + */ + STRING, + + /** + * 长整型,支持范围查询和排序 + */ + LONG, + + /** + * 整型,支持范围查询和排序 + */ + INTEGER, + + /** + * 双精度浮点型,支持范围查询和排序 + */ + DOUBLE +} diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Procedure.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Procedure.java index 3c48489d7..fd681b528 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Procedure.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Procedure.java @@ -15,7 +15,7 @@ @SuperBuilder @NoArgsConstructor @AllArgsConstructor -public class Procedure { +public class Procedure implements IndexModel { //PROCEDURE_CAT String => procedure catalog (may be null) //PROCEDURE_SCHEM String => procedure schema (may be null) //PROCEDURE_NAME String => procedure name @@ -28,24 +28,40 @@ public class Procedure { // @JsonAlias({"PROCEDURE_CAT"}) + @LuceneField(name = "databaseName", type = LuceneFieldType.STRING) private String databaseName; @JsonAlias({"PROCEDURE_SCHEM"}) - + @LuceneField(name = "schemaName", type = LuceneFieldType.STRING) private String schemaName; @JsonAlias({"PROCEDURE_NAME"}) - private String procedureName; + @LuceneField(name = "name", type = LuceneFieldType.TEXT, sort = true) + private String name; @JsonAlias({"REMARKS"}) - private String remarks; + @LuceneField(name = "comment", type = LuceneFieldType.TEXT) + private String comment; - @JsonAlias({"PROCEDURE_TYPE"}) + @LuceneField(name = "aiComment", type = LuceneFieldType.TEXT) + private String aiComment; + + private Long version; + @JsonAlias({"PROCEDURE_TYPE"}) private Short procedureType; @JsonAlias({"SPECIFIC_NAME"}) private String specificName; private String procedureBody; + + @Override + public String getTableName() { + return null; + } + + @Override + public void setTableName(String tableName) { + } } \ No newline at end of file diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Schema.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Schema.java index 2b3635f8b..5c91a0609 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Schema.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Schema.java @@ -37,4 +37,8 @@ public class Schema implements Serializable { private String owner; + + private String treeNodeType; + + private String keyType; } \ No newline at end of file diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Table.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Table.java index 640f2180c..bc16372b4 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Table.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Table.java @@ -1,9 +1,10 @@ package ai.chat2db.spi.model; +import java.util.Collections; import java.util.List; - import com.fasterxml.jackson.annotation.JsonAlias; + import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -18,48 +19,62 @@ @SuperBuilder @NoArgsConstructor @AllArgsConstructor -public class Table { +public class Table implements IndexModel { /** * 表名 */ - @JsonAlias({"TABLE_NAME"}) + @JsonAlias({ "TABLE_NAME" }) + @LuceneField(name = "name", type = LuceneFieldType.TEXT, sort = true) private String name; /** * 描述 */ - @JsonAlias({"REMARKS"}) - + @JsonAlias({ "REMARKS" }) + @LuceneField(name = "comment", type = LuceneFieldType.TEXT) private String comment; /** - * DB 名 + * 数据库名 */ - @JsonAlias({"TABLE_SCHEM"}) + @JsonAlias("TABLE_CAT") + @LuceneField(name = "databaseName", type = LuceneFieldType.STRING) + private String databaseName; + /** + * DB 名 + */ + @JsonAlias({ "TABLE_SCHEM" }) + @LuceneField(name = "schemaName", type = LuceneFieldType.STRING) private String schemaName; /** * 列列表 */ - private List columnList; + @lombok.Builder.Default + private List columnList = Collections.emptyList(); /** * 索引列表 */ - private List indexList; + @lombok.Builder.Default + private List indexList = Collections.emptyList(); /** - * DB类型 + * 外键列表 */ - private String dbType; + private List foreignKeyList; /** - * 数据库名 + * 虚拟外键 */ - @JsonAlias("TABLE_CAT") - private String databaseName; + private List virtualForeignKeyList; + + /** + * DB类型 + */ + private String dbType; /** * 表类型 @@ -72,27 +87,58 @@ public class Table { */ private boolean pinned; + /** + * 是否已废弃 + */ + private boolean deprecated; + /** * ddl */ private String ddl; - + // 数据表的存储引擎信息 private String engine; - + // 数据表的字符集信息 private String charset; - + // 数据表的排序规则信息 private String collate; - + // 数据表中自增字段的增量值 private Long incrementValue; - + // 数据表的分区信息 private String partition; - + // 数据表所属的表空间信息 private String tablespace; -} + /** + * AI生成的注释 + */ + @LuceneField(name = "aiComment", type = LuceneFieldType.TEXT) + private String aiComment; + + /** + * 版本 + */ + private Long version; + + /** + * 预估行数 + */ + @LuceneField(name = "rowCount", type = LuceneFieldType.LONG, sort = true) + private Long rowCount; + + @Override + public String getTableName() { + return this.name; + } + + @Override + public void setTableName(String tableName) { + this.name = tableName; + } +} diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/TableColumn.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/TableColumn.java index 215f17344..fcb935d76 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/TableColumn.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/TableColumn.java @@ -1,13 +1,10 @@ package ai.chat2db.spi.model; -import com.fasterxml.jackson.annotation.JsonAlias; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; -import java.util.Objects; - /** * 列信息 * @@ -17,7 +14,7 @@ @SuperBuilder @NoArgsConstructor @AllArgsConstructor -public class TableColumn { +public class TableColumn implements IndexModel { /** * Old column, when modifying a column, you need this parameter @@ -32,41 +29,35 @@ public class TableColumn { /** * 列名 */ - @JsonAlias({"COLUMN_NAME","column_name"}) + @LuceneField(name = "name", type = LuceneFieldType.TEXT, sort = true) private String name; /** * 表名 */ - @JsonAlias({"TABLE_NAME","table_name"}) + @LuceneField(name = "tableName", type = LuceneFieldType.STRING) private String tableName; /** * 列的类型 * 比如 varchar(100) ,double(10,6) */ - - @JsonAlias({"TYPE_NAME","type_name"}) + @LuceneField(name = "columnType", type = LuceneFieldType.TEXT) private String columnType; /** * 列的数据类型 * 比如 varchar ,double */ - - @JsonAlias({"DATA_TYPE","data_type"}) - private Integer dataType; + private String dataType; /** * 默认值 */ - - @JsonAlias({"COLUMN_DEF","column_def"}) private String defaultValue; - /** * 是否自增 * 为空 代表没有值 数据库的实际语义是 false @@ -76,7 +67,7 @@ public class TableColumn { /** * 注释 */ - @JsonAlias({"REMARKS","remarks"}) + @LuceneField(name = "comment", type = LuceneFieldType.TEXT) private String comment; /** @@ -99,13 +90,13 @@ public class TableColumn { /** * 空间名 */ - @JsonAlias({"TABLE_SCHEM","table_schem"}) + @LuceneField(name = "schemaName", type = LuceneFieldType.STRING) private String schemaName; /** * 数据库名 */ - @JsonAlias({"TABLE_CAT","table_cat"}) + @LuceneField(name = "databaseName", type = LuceneFieldType.STRING) private String databaseName; // /** @@ -117,7 +108,6 @@ public class TableColumn { * column size. */ - @JsonAlias({"COLUMN_SIZE","column_size"}) private Integer columnSize; /** @@ -129,14 +119,12 @@ public class TableColumn { * the number of fractional digits. Null is returned for data types where DECIMAL_DIGITS is not applicable. */ - @JsonAlias({"DECIMAL_DIGITS","decimal_digits"}) private Integer decimalDigits; /** * Radix (typically either 10 or 2) */ - @JsonAlias({"NUM_PREC_RADIX","num_prec_radix"}) private Integer numPrecRadix; @@ -160,14 +148,11 @@ public class TableColumn { * index of column in table (starting at 1) */ - @JsonAlias({"ORDINAL_POSITION","ordinal_position"}) private Integer ordinalPosition; /** * ISO rules are used to determine the nullability for a column. */ - - @JsonAlias({"NULLABLE","nullable"}) private Integer nullable; /** @@ -199,16 +184,15 @@ public class TableColumn { // sqlserver private String defaultConstraintName; - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - TableColumn that = (TableColumn) o; - return Objects.equals(name, that.name) && Objects.equals(tableName, that.tableName) && Objects.equals(columnType, that.columnType) && Objects.equals(defaultValue, that.defaultValue) && Objects.equals(autoIncrement, that.autoIncrement) && Objects.equals(comment, that.comment) && Objects.equals(columnSize, that.columnSize) && Objects.equals(decimalDigits, that.decimalDigits) && Objects.equals(numPrecRadix, that.numPrecRadix) && Objects.equals(sqlDataType, that.sqlDataType) && Objects.equals(ordinalPosition, that.ordinalPosition) && Objects.equals(nullable, that.nullable) && Objects.equals(extent, that.extent) && Objects.equals(charSetName, that.charSetName) && Objects.equals(collationName, that.collationName) && Objects.equals(value, that.value) && Objects.equals(unit, that.unit) && Objects.equals(sparse, that.sparse) && Objects.equals(defaultConstraintName, that.defaultConstraintName); - } - - @Override - public int hashCode() { - return Objects.hash(name, tableName, columnType, defaultValue, autoIncrement, comment, columnSize, decimalDigits, numPrecRadix, sqlDataType, ordinalPosition, nullable, extent, charSetName, collationName, value, unit, sparse, defaultConstraintName); - } + /** + * AI生成的注释 + */ + @LuceneField(name = "aiComment", type = LuceneFieldType.TEXT) + private String aiComment; + + /** + * 版本 + */ + private Long version; + } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/TableMeta.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/TableMeta.java index 6ae0c2d7f..9ca5c0798 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/TableMeta.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/TableMeta.java @@ -5,20 +5,39 @@ import java.util.List; + +/** + * 表元数据类,用于描述表的列类型、字符集、排序规则和索引类型等信息 + */ @Data @Builder public class TableMeta { + /** + * 列类型列表,描述表中各列的数据类型信息 + */ private List columnTypes; + /** + * 字符集列表,定义了表中各列可能使用的字符集信息 + */ private List charsets; + /** + * 排序规则列表,描述了表中各列的排序规则信息 + */ private List collations; + /** + * 索引类型列表,定义了表中可能的索引类型信息 + */ private List indexTypes; + /** + * 默认值列表,包含表中各列的默认值信息 + */ private List defaultValues; } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Trigger.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Trigger.java index 472ef555a..82f090499 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Trigger.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Trigger.java @@ -14,16 +14,35 @@ @SuperBuilder @NoArgsConstructor @AllArgsConstructor -public class Trigger { +public class Trigger implements IndexModel { + @LuceneField(name = "databaseName", type = LuceneFieldType.STRING) private String databaseName; + @LuceneField(name = "schemaName", type = LuceneFieldType.STRING) private String schemaName; - private String triggerName; + @LuceneField(name = "name", type = LuceneFieldType.TEXT, sort = true) + private String name; + + @LuceneField(name = "comment", type = LuceneFieldType.TEXT) + private String comment; + + @LuceneField(name = "aiComment", type = LuceneFieldType.TEXT) + private String aiComment; + + private Long version; private String eventManipulation; private String triggerBody; + @Override + public String getTableName() { + return null; + } + + @Override + public void setTableName(String tableName) { + } } \ No newline at end of file diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Type.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Type.java index cb7568c17..ffd164fbd 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Type.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Type.java @@ -6,66 +6,120 @@ import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; +/** + * 表示数据库支持的数据类型信息。 + */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class Type { + /** + * 数据类型的名称(如 VARCHAR, INTEGER 等)。 + */ @JsonAlias("TYPE_NAME") private String typeName; + /** + * 对应的 SQL 类型(如 12 对应 VARCHAR, 4 对应 INTEGER 等)。 + */ @JsonAlias("DATA_TYPE") private Integer dataType; + /** + * 数据类型的精度(对于数值类型,表示数字的总位数;对于字符类型,表示字符的最大长度)。 + */ @JsonAlias("PRECISION") private Integer precision; + /** + * 用于表示该类型的字面量前缀(如 N' 用于 Unicode 字符串)。 + */ @JsonAlias("LITERAL_PREFIX") private String literalPrefix; + /** + * 用于表示该类型的字面量后缀(如 ')。 + */ @JsonAlias("LITERAL_SUFFIX") private String literalSuffix; + /** + * 创建该类型时所需的参数(如长度)。 + */ @JsonAlias("CREATE_PARAMS") private String createParams; + /** + * 指示该类型是否可以为 NULL。 + */ @JsonAlias("NULLABLE") private Short nullable; - + /** + * 指示该类型是否区分大小写。 + */ @JsonAlias("CASE_SENSITIVE") private Boolean caseSensitive; + /** + * 指示该类型是否可以用于搜索。 + */ @JsonAlias("SEARCHABLE") private Short searchable; + /** + * 指示该类型是否支持无符号值。 + */ @JsonAlias("UNSIGNED_ATTRIBUTE") private Boolean unsignedAttribute; - + /** + * 指示该类型是否具有固定的精度和标度。 + */ @JsonAlias("FIXED_PREC_SCALE") private Boolean fixedPrecScale; + /** + * 指示该类型是否支持自动递增。 + */ @JsonAlias("AUTO_INCREMENT") private Boolean autoIncrement; + /** + * 本地数据类型的名称。 + */ @JsonAlias("LOCAL_TYPE_NAME") private String localTypeName; + /** + * 数据类型的最小标度。 + */ @JsonAlias("MINIMUM_SCALE") private Short minimumScale; + /** + * 数据类型的最大标度。 + */ @JsonAlias("MAXIMUM_SCALE") private Short maximumScale; + /** + * SQL 数据类型的值。 + */ @JsonAlias("SQL_DATA_TYPE") private Integer sqlDataType; + /** + * SQL 日期时间子类型的值。 + */ @JsonAlias("SQL_DATETIME_SUB") private Integer sqlDatetimeSub; - + /** + * 数值精度的基数。 + */ @JsonAlias("NUM_PREC_RADIX") private Integer numPrecRadix; diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/View.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/View.java new file mode 100644 index 000000000..b7c2685df --- /dev/null +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/View.java @@ -0,0 +1,30 @@ +package ai.chat2db.spi.model; + +import org.springframework.beans.BeanUtils; + +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * View metadata uses the same fields as Table, but must have an independent Lucene type. + */ +@SuperBuilder +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class View extends Table { + + public static View from(Table table) { + if (table == null) { + return null; + } + View view = new View(); + BeanUtils.copyProperties(table, view); + return view; + } + + @Override + public Class getClassType() { + return View.class; + } +} diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/VirtualForeignKey.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/VirtualForeignKey.java new file mode 100644 index 000000000..e78cb3784 --- /dev/null +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/VirtualForeignKey.java @@ -0,0 +1,18 @@ +package ai.chat2db.spi.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class VirtualForeignKey extends ForeignKey { + + /** + * 额外的虚拟外键属性 + */ + private String virtualProperty; +} diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/VirtualForeignKeySuggestion.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/VirtualForeignKeySuggestion.java new file mode 100644 index 000000000..5c6211bd7 --- /dev/null +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/VirtualForeignKeySuggestion.java @@ -0,0 +1,41 @@ +package ai.chat2db.spi.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 虚拟外键推断建议 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class VirtualForeignKeySuggestion { + + /** + * 来源表名 + */ + private String sourceTable; + + /** + * 来源列名 + */ + private String sourceColumn; + + /** + * 目标表名 + */ + private String targetTable; + + /** + * 目标列名 + */ + private String targetColumn; + + /** + * 推断原因 (e.g., "JOIN condition", "WHERE clause") + */ + private String reason; +} diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/redis/RedisCommandMonitor.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/redis/RedisCommandMonitor.java new file mode 100644 index 000000000..9d11c127f --- /dev/null +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/redis/RedisCommandMonitor.java @@ -0,0 +1,9 @@ +package ai.chat2db.spi.redis; + +import java.util.function.BooleanSupplier; +import java.util.function.Consumer; + +public interface RedisCommandMonitor { + + void monitor(String databaseName, Consumer lineConsumer, BooleanSupplier running); +} diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/redis/RedisKeyBrowser.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/redis/RedisKeyBrowser.java new file mode 100644 index 000000000..146ef67f2 --- /dev/null +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/redis/RedisKeyBrowser.java @@ -0,0 +1,23 @@ +package ai.chat2db.spi.redis; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; + +public interface RedisKeyBrowser { + + CompletableFuture streamKeys(String databaseName, String searchKey, String cursor, int count, + Consumer> batchConsumer); + + RedisKeyInfo queryKey(String databaseName, String keyName); + + void createKey(String databaseName, String keyName, String keyType, Object value, Long ttl); + + void updateKey(String databaseName, String originalKey, String updateKey, String keyType, Object value, Long ttl); + + void deleteKey(String databaseName, String keyName); + + void partialUpdateKey(String databaseName, String keyName, String keyType, + Map addedFields, List removedFields, Long ttl); +} diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/redis/RedisKeyInfo.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/redis/RedisKeyInfo.java new file mode 100644 index 000000000..e908d4b6b --- /dev/null +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/redis/RedisKeyInfo.java @@ -0,0 +1,39 @@ +package ai.chat2db.spi.redis; + +import lombok.Builder; +import lombok.Data; + +/** + * Redis键信息 + * + * @author chat2db + */ +@Data +@Builder +public class RedisKeyInfo { + + /** + * 键名称 + */ + private String name; + + /** + * 键值 + */ + private Object value; + + /** + * 数据类型(如 string、hash、list、set、zset) + */ + private String type; + + /** + * 过期时间(单位:秒),-1 表示永不过期,-2 表示已过期 + */ + private Long ttl; + + /** + * 键值大小(单位:字节) + */ + private Long size; +} diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/redis/RedisKeyScanResult.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/redis/RedisKeyScanResult.java new file mode 100644 index 000000000..9e9381ca1 --- /dev/null +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/redis/RedisKeyScanResult.java @@ -0,0 +1,18 @@ +package ai.chat2db.spi.redis; + +import lombok.Builder; +import lombok.Data; + +/** + * Redis SCAN final state. + */ +@Data +@Builder +public class RedisKeyScanResult { + + private String cursor; + + private Boolean hasMore; + + private Integer total; +} diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/Chat2DBContext.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/Chat2DBContext.java index 9e6fce81a..88183d9ad 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/Chat2DBContext.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/Chat2DBContext.java @@ -142,6 +142,7 @@ public static void removeContext() { try { if (connection != null && !connection.isClosed()) { connection.close(); + connectInfo.setConnection(null); } } catch (SQLException e) { log.error("close connection error", e); diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/IDriverManager.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/IDriverManager.java index 32173357a..c2f7a6a2b 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/IDriverManager.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/IDriverManager.java @@ -1,14 +1,7 @@ package ai.chat2db.spi.sql; -import ai.chat2db.server.tools.common.exception.ConnectionException; -import ai.chat2db.spi.config.DriverConfig; -import ai.chat2db.spi.model.DriverEntry; -import ai.chat2db.spi.util.JdbcJarUtils; -import com.alibaba.fastjson2.JSON; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import static ai.chat2db.spi.util.JdbcJarUtils.*; import java.io.File; import java.net.MalformedURLException; @@ -23,12 +16,23 @@ import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; -import static ai.chat2db.spi.util.JdbcJarUtils.getFullPath; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import com.alibaba.fastjson2.JSON; + +import ai.chat2db.server.tools.common.exception.ConnectionException; +import ai.chat2db.spi.config.DriverConfig; +import ai.chat2db.spi.model.DriverEntry; +import ai.chat2db.spi.util.JdbcJarUtils; /** * @author jipengfei * @version : IsolationDriverManager.java */ +@Component public class IDriverManager { private static final Logger log = LoggerFactory.getLogger(IDriverManager.class); private static final Map CLASS_LOADER_MAP = new ConcurrentHashMap(); @@ -55,7 +59,7 @@ public static Connection getConnection(String url, String user, String password, } public static Connection getConnection(String url, String user, String password, DriverConfig driver, - Map properties) + Map properties) throws SQLException { Properties info = new Properties(); if (StringUtils.isNotEmpty(user)) { @@ -89,15 +93,16 @@ public static Connection getConnection(String url, Properties info, DriverConfig try { connection = driverEntry.getDriver().connect(url, info); if (Objects.isNull(connection)) { - throw new SQLException(String.format("driver.connect return null , No suitable driver found for url %s", url), SQL_STATE_CODE); - + throw new SQLException( + String.format("driver.connect return null , No suitable driver found for url %s", url), + SQL_STATE_CODE); } return connection; - } catch (SQLException sqlException) { + } catch (Exception sqlException) { Connection con = tryConnectionAgain(driverEntry, url, info); - if (Objects.isNull(con)) { - throw new SQLException(String.format("Cannot create connection (%s)", sqlException.getMessage()), SQL_STATE_CODE, + throw new SQLException(String.format("Cannot create connection (%s)", sqlException.getMessage()), + SQL_STATE_CODE, sqlException); } @@ -122,14 +127,27 @@ public static DriverPropertyInfo[] getProperty(DriverConfig driver) } } - private static Connection tryConnectionAgain(DriverEntry driverEntry, String url, - Properties info) throws SQLException { + Properties info) throws SQLException { if (url.contains("mysql")) { if (!info.containsKey("useSSL")) { info.put("useSSL", "false"); } return driverEntry.getDriver().connect(url, info); + } else if (url.contains("phoenix")) { + ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + try { + // 设置临时的classloader + Thread.currentThread().setContextClassLoader(getClassLoader(driverEntry.getDriverConfig())); + info.put("phoenix.schema.isNamespaceMappingEnabled","true"); + return driverEntry.getDriver().connect(url, info); + } catch (Exception e) { + throw new SQLException(String.format("Cannot create connection (%s)", e.getMessage()), SQL_STATE_CODE, + e); + } finally { + // 还原 + Thread.currentThread().setContextClassLoader(contextClassLoader); + } } return null; } @@ -153,7 +171,8 @@ private static DriverEntry getJDBCDriver(DriverConfig driver) } - public static ClassLoader getClassLoader(DriverConfig driverConfig) throws MalformedURLException, ClassNotFoundException { + public static ClassLoader getClassLoader(DriverConfig driverConfig) + throws MalformedURLException, ClassNotFoundException { String jarPath = driverConfig.getJdbcDriver(); if (CLASS_LOADER_MAP.containsKey(jarPath)) { return CLASS_LOADER_MAP.get(jarPath); @@ -168,21 +187,28 @@ public static ClassLoader getClassLoader(DriverConfig driverConfig) throws Malfo File driverFile = new File(getFullPath(jarPaths[i])); urls[i] = driverFile.toURI().toURL(); } - //urls[jarPaths.length] = new File(JdbcJarUtils.getFullPath("HikariCP-4.0.3.jar")).toURI().toURL(); - - URLClassLoader cl = new URLClassLoader(urls, ClassLoader.getSystemClassLoader()); + // urls[jarPaths.length] = new + // File(JdbcJarUtils.getFullPath("HikariCP-4.0.3.jar")).toURI().toURL(); + ClassLoader bootLoader = Thread.currentThread().getContextClassLoader(); + if (StringUtils.contains(ClassLoader.class.getName(),"springframework")) { + log.error("BootLoader class:{}", bootLoader.getClass().getName()); + bootLoader = ClassLoader.getSystemClassLoader(); + } + log.info("BootLoader class:{}", bootLoader.getClass().getName()); + URLClassLoader cl = new URLClassLoader(urls, bootLoader); log.info("ClassLoader class:{}", cl.hashCode()); log.info("ClassLoader URLs:{}", JSON.toJSONString(cl.getURLs())); try { cl.loadClass(driverConfig.getJdbcDriverClass()); } catch (Exception e) { - //如果报错删除目录重试一次 + // 如果报错删除目录重试一次 for (int i = 0; i < jarPaths.length; i++) { File driverFile = new File(JdbcJarUtils.getNewFullPath(jarPaths[i])); urls[i] = driverFile.toURI().toURL(); } - //urls[jarPaths.length] = new File(JdbcJarUtils.getFullPath("HikariCP-4.0.3.jar")).toURI().toURL(); + // urls[jarPaths.length] = new + // File(JdbcJarUtils.getFullPath("HikariCP-4.0.3.jar")).toURI().toURL(); cl = new URLClassLoader(urls, ClassLoader.getSystemClassLoader()); cl.loadClass(driverConfig.getJdbcDriverClass()); } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java index 9278629b4..aa2ba6c3f 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java @@ -6,11 +6,31 @@ import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Statement; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; import java.util.function.Consumer; -import java.util.function.Function; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.stream.Collectors; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.bson.Document; +import org.springframework.util.Assert; + +import com.alibaba.druid.DbType; +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; +import com.alibaba.druid.sql.parser.ParserException; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import ai.chat2db.server.tools.base.enums.DataSourceTypeEnum; import ai.chat2db.server.tools.base.excption.BusinessException; @@ -21,23 +41,24 @@ import ai.chat2db.spi.enums.DataTypeEnum; import ai.chat2db.spi.enums.SqlTypeEnum; import ai.chat2db.spi.jdbc.DefaultValueHandler; -import ai.chat2db.spi.model.*; +import ai.chat2db.spi.model.Command; +import ai.chat2db.spi.model.Database; +import ai.chat2db.spi.model.ExecuteResult; +import ai.chat2db.spi.model.ForeignKey; +import ai.chat2db.spi.model.Header; +import ai.chat2db.spi.model.Procedure; +import ai.chat2db.spi.model.Schema; +import ai.chat2db.spi.model.Table; +import ai.chat2db.spi.model.TableColumn; +import ai.chat2db.spi.model.TableIndex; +import ai.chat2db.spi.model.TableIndexColumn; +import ai.chat2db.spi.model.Type; import ai.chat2db.spi.util.JdbcUtils; import ai.chat2db.spi.util.ResultSetUtils; import ai.chat2db.spi.util.SqlUtils; import cn.hutool.core.date.TimeInterval; -import com.alibaba.druid.DbType; -import com.alibaba.druid.sql.SQLUtils; -import com.alibaba.druid.sql.ast.SQLStatement; -import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; -import com.alibaba.druid.sql.parser.ParserException; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang3.StringUtils; -import org.bson.Document; -import org.springframework.util.Assert; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; /** * Dbhub 统一数据库连接管理 @@ -46,6 +67,7 @@ */ @Slf4j public class SQLExecutor implements CommandExecutor { + private static final Pattern ERROR_LINE_PATTERN = Pattern.compile("(?i)\\bline\\s+(\\d+)\\b"); /** * Singleton instance of SQLExecutor. @@ -100,10 +122,10 @@ public void execute(Connection connection, String sql, Consumer> he List
    headerList = Lists.newArrayListWithExpectedSize(col); for (int i = 1; i <= col; i++) { headerList.add(Header.builder() - .dataType(JdbcUtils.resolveDataType( - resultSetMetaData.getColumnTypeName(i), resultSetMetaData.getColumnType(i)).getCode()) - .name(ResultSetUtils.getColumnName(resultSetMetaData, i)) - .build()); + .dataType(JdbcUtils.resolveDataType( + resultSetMetaData.getColumnTypeName(i), resultSetMetaData.getColumnType(i)).getCode()) + .name(ResultSetUtils.getColumnName(resultSetMetaData, i)) + .build()); } headerConsumer.accept(headerList); @@ -131,13 +153,13 @@ public void execute(Connection connection, String sql, Consumer> he * @throws SQLException */ public ExecuteResult execute(final String sql, Connection connection, ValueHandler valueHandler) - throws SQLException { + throws SQLException { return execute(sql, connection, true, null, null, valueHandler); } @Override public ExecuteResult executeUpdate(String sql, Connection connection, int n) - throws SQLException { + throws SQLException { Assert.notNull(sql, "SQL must not be null"); log.info("execute:{}", sql); // connection.setAutoCommit(false); @@ -147,8 +169,8 @@ public ExecuteResult executeUpdate(String sql, Connection connection, int n) if (affectedRows != n) { executeResult.setSuccess(false); executeResult.setMessage("Update error " + sql + " update affectedRows = " + affectedRows - + ", Each SQL statement should update no more than one record. Please use a unique key for " - + "updates."); + + ", Each SQL statement should update no more than one record. Please use a unique key for " + + "updates."); // connection.rollback(); } } @@ -158,18 +180,20 @@ public ExecuteResult executeUpdate(String sql, Connection connection, int n) /** * Executes the given SQL query using the provided connection. - * @param sql The SQL query to be executed. - * @param connection The database connection to use for the query. + * + * @param sql The SQL query to be executed. + * @param connection The database connection to use for the query. * @param limitRowSize Flag to indicate if row size should be limited. - * @param offset The starting point of rows to fetch in the result set. - * @param count The number of rows to fetch from the result set. + * @param offset The starting point of rows to fetch in the result set. + * @param count The number of rows to fetch from the result set. * @param valueHandler Handles the processing of the result set values. * @return ExecuteResult containing the result of the execution. * @throws SQLException If there is any SQL related error. */ + @Override public ExecuteResult execute(final String sql, Connection connection, boolean limitRowSize, Integer offset, - Integer count, ValueHandler valueHandler) - throws SQLException { + Integer count, ValueHandler valueHandler) + throws SQLException { Assert.notNull(sql, "SQL must not be null"); log.info("execute:{}", sql); @@ -214,11 +238,11 @@ public ExecuteResult execute(final String sql, Connection connection, boolean li continue; } String dataType = JdbcUtils.resolveDataType( - resultSetMetaData.getColumnTypeName(i), resultSetMetaData.getColumnType(i)).getCode(); + resultSetMetaData.getColumnTypeName(i), resultSetMetaData.getColumnType(i)).getCode(); headerList.add(Header.builder() - .dataType(dataType) - .name(name) - .build()); + .dataType(dataType) + .name(name) + .build()); } // 获取数据信息 @@ -258,16 +282,16 @@ public ExecuteResult execute(final String sql, Connection connection, boolean li if (o instanceof Document document) { for (String string : document.keySet()) { headerListMap.computeIfAbsent(string, k -> Header.builder() - .dataType("string") - .name(string) - .build()); + .dataType("string") + .name(string) + .build()); row.put(string, Objects.toString(document.get(string))); } } else { headerListMap.computeIfAbsent("_unknown", k -> Header.builder() - .dataType("string") - .name("_unknown") - .build()); + .dataType("string") + .name("_unknown") + .build()); row.put("_unknown", Objects.toString(o)); } } @@ -380,33 +404,29 @@ public List schemas(Connection connection, String databaseName, String s * @return */ public List
    tables(Connection connection, String databaseName, String schemaName, String tableName, - String types[]) { - + String types[]) { try { DatabaseMetaData metadata = connection.getMetaData(); - ResultSet resultSet = metadata.getTables(databaseName, schemaName, tableName, - types); + ResultSet resultSet = metadata.getTables(databaseName, schemaName, tableName, types); + // 如果connection为mysql if ("MySQL".equalsIgnoreCase(metadata.getDatabaseProductName())) { - // 获取mysql表的comment + // 获取数据库版本 + String version = metadata.getDatabaseProductVersion(); + String[] versionParts = version.split("\\."); + int majorVersion = Integer.parseInt(versionParts[0]); + List
    tables = ResultSetUtils.toObjectList(resultSet, Table.class); - if (CollectionUtils.isNotEmpty(tables)) { - for (Table table : tables) { - String sql = "show table status where name = '" + table.getName() + "'"; - try (Statement stmt = connection.createStatement()) { - boolean query = stmt.execute(sql); - if (query) { - try (ResultSet rs = stmt.getResultSet();) { - while (rs.next()) { - table.setComment(rs.getString("Comment")); - } - } - } + // 只有在版本小于8时才执行查询表注释的SQL + if (majorVersion < 8) { + if (CollectionUtils.isNotEmpty(tables)) { + for (Table table : tables) { + setTableComment(connection, databaseName, table); } + return tables; } - - return tables; } + return tables; } return ResultSetUtils.toObjectList(resultSet, Table.class); } catch (SQLException e) { @@ -414,6 +434,36 @@ public List
    tables(Connection connection, String databaseName, String sch } } + private void setTableComment(Connection connection, String databaseName, Table table) { + // VIEW 不支持通过 SHOW TABLE STATUS 获取注释,直接跳过 + if (table == null || !"TABLE".equalsIgnoreCase(table.getType())) { + return; + } + String targetDatabase = StringUtils.defaultIfBlank(databaseName, table.getDatabaseName()); + StringBuilder sqlBuilder = new StringBuilder("SHOW TABLE STATUS"); + if (StringUtils.isNotBlank(targetDatabase)) { + sqlBuilder.append(" FROM `").append(targetDatabase).append("`"); + } + sqlBuilder.append(" WHERE Name = '").append(table.getName()).append("'"); + String sql = sqlBuilder.toString(); + try (Statement stmt = connection.createStatement()) { + boolean query = stmt.execute(sql); + if (query) { + try (ResultSet rs = stmt.getResultSet()) { + while (rs.next()) { + table.setComment(rs.getString("Comment")); + } + } + } + } catch (SQLException e) { + // 注释是补充信息,失败不应影响元数据主流程 + log.warn("[Table] load comment failed, databaseName={}, tableName={}, sql={}", + targetDatabase, table.getName(), sql, e); + } + } + + + /** * 获取所有的数据库表列 * @@ -425,10 +475,10 @@ public List
    tables(Connection connection, String databaseName, String sch * @return */ public List columns(Connection connection, String databaseName, String schemaName, String - tableName, - String columnName) { + tableName, + String columnName) { try (ResultSet resultSet = connection.getMetaData().getColumns(databaseName, schemaName, tableName, - columnName)) { + columnName)) { return ResultSetUtils.toObjectList(resultSet, TableColumn.class); } catch (Exception e) { throw new RuntimeException(e); @@ -447,22 +497,22 @@ public List columns(Connection connection, String databaseName, Str public List indexes(Connection connection, String databaseName, String schemaName, String tableName) { List tableIndices = Lists.newArrayList(); try (ResultSet resultSet = connection.getMetaData().getIndexInfo(databaseName, schemaName, tableName, - false, - false)) { + false, + false)) { List tableIndexColumns = ResultSetUtils.toObjectList(resultSet, TableIndexColumn.class); tableIndexColumns.stream().filter(c -> c.getIndexName() != null).collect( - Collectors.groupingBy(TableIndexColumn::getIndexName)).entrySet() - .stream().forEach(entry -> { - TableIndex tableIndex = new TableIndex(); - TableIndexColumn column = entry.getValue().get(0); - tableIndex.setName(entry.getKey()); - tableIndex.setTableName(column.getTableName()); - tableIndex.setSchemaName(column.getSchemaName()); - tableIndex.setDatabaseName(column.getDatabaseName()); - tableIndex.setUnique(!column.getNonUnique()); - tableIndex.setColumnList(entry.getValue()); - tableIndices.add(tableIndex); - }); + Collectors.groupingBy(TableIndexColumn::getIndexName)).entrySet() + .stream().forEach(entry -> { + TableIndex tableIndex = new TableIndex(); + TableIndexColumn column = entry.getValue().get(0); + tableIndex.setName(entry.getKey()); + tableIndex.setTableName(column.getTableName()); + tableIndex.setSchemaName(column.getSchemaName()); + tableIndex.setDatabaseName(column.getDatabaseName()); + tableIndex.setUnique(!column.getNonUnique()); + tableIndex.setColumnList(entry.getValue()); + tableIndices.add(tableIndex); + }); } catch (SQLException e) { throw new RuntimeException(e); } @@ -478,7 +528,7 @@ public List indexes(Connection connection, String databaseName, Stri * @return List */ public List functions(Connection connection, String databaseName, - String schemaName) { + String schemaName) { try (ResultSet resultSet = connection.getMetaData().getFunctions(databaseName, schemaName, null);) { return ResultSetUtils.toObjectList(resultSet, ai.chat2db.spi.model.Function.class); } catch (Exception e) { @@ -547,35 +597,183 @@ public List execute(Command command) { throw new BusinessException("dataSource.sqlAnalysisError"); } List result = new ArrayList<>(); + List statementPositions = buildStatementPositions(command.getScript(), sqlList); // 执行sql - for (String originalSql : sqlList) { + for (int i = 0; i < sqlList.size(); i++) { + String originalSql = sqlList.get(i); ExecuteResult executeResult = executeSQL(originalSql, dbType, command); + applyStatementPosition(executeResult, statementPositions, i, command.getScriptStartLine()); result.add(executeResult); } return result; } + private void applyStatementPosition(ExecuteResult executeResult, List statementPositions, + int index, Integer scriptStartLine) { + executeResult.setStatementIndex(index + 1); + if (CollectionUtils.isEmpty(statementPositions) || index < 0 || index >= statementPositions.size()) { + return; + } + StatementPosition position = statementPositions.get(index); + int lineOffset = Math.max(Optional.ofNullable(scriptStartLine).orElse(1) - 1, 0); + int statementStartLine = position.startLine() + lineOffset; + executeResult.setStatementStartLine(statementStartLine); + executeResult.setStatementEndLine(position.endLine() + lineOffset); + if (Boolean.TRUE.equals(executeResult.getSuccess()) || StringUtils.isBlank(executeResult.getMessage())) { + return; + } + Matcher matcher = ERROR_LINE_PATTERN.matcher(executeResult.getMessage()); + if (!matcher.find()) { + return; + } + Integer lineInStatement = Integer.valueOf(matcher.group(1)); + executeResult.setErrorLineInStatement(lineInStatement); + executeResult.setErrorLine(statementStartLine + Math.max(lineInStatement - 1, 0)); + } + + private List buildStatementPositions(String script, List sqlList) { + if (StringUtils.isBlank(script) || CollectionUtils.isEmpty(sqlList)) { + return Collections.emptyList(); + } + List rawPositions = splitStatementPositionsFromScript(script); + if (CollectionUtils.isEmpty(rawPositions)) { + return Collections.emptyList(); + } + int size = Math.min(rawPositions.size(), sqlList.size()); + return new ArrayList<>(rawPositions.subList(0, size)); + } + + private List splitStatementPositionsFromScript(String script) { + List positions = new ArrayList<>(); + int start = 0; + Character quote = null; + boolean inLineComment = false; + boolean inBlockComment = false; + int len = script.length(); + + for (int i = 0; i < len; i++) { + char ch = script.charAt(i); + char next = i + 1 < len ? script.charAt(i + 1) : '\0'; + + if (inLineComment) { + if (ch == '\n' || ch == '\r') { + inLineComment = false; + } + continue; + } + if (inBlockComment) { + if (ch == '*' && next == '/') { + inBlockComment = false; + i++; + } + continue; + } + if (quote != null) { + if (quote == '\'' && ch == '\'' && next == '\'') { + i++; + continue; + } + if (quote == '"' && ch == '"' && next == '"') { + i++; + continue; + } + if (quote == '`' && ch == '`' && next == '`') { + i++; + continue; + } + if ((quote == '\'' && ch == '\'') || (quote == '"' && ch == '"') || (quote == '`' && ch == '`') + || (quote == '[' && ch == ']')) { + quote = null; + } + continue; + } + if (ch == '-' && next == '-') { + inLineComment = true; + i++; + continue; + } + if (ch == '/' && next == '*') { + inBlockComment = true; + i++; + continue; + } + if (ch == '\'' || ch == '"' || ch == '`' || ch == '[') { + quote = ch; + continue; + } + if (ch == ';') { + addTrimmedStatementPosition(positions, script, start, i); + start = i + 1; + } + } + addTrimmedStatementPosition(positions, script, start, len); + return positions; + } + + private void addTrimmedStatementPosition(List positions, String script, int start, int endExclusive) { + int realStart = start; + int realEndExclusive = Math.max(start, endExclusive); + while (realStart < realEndExclusive && Character.isWhitespace(script.charAt(realStart))) { + realStart++; + } + while (realEndExclusive > realStart && Character.isWhitespace(script.charAt(realEndExclusive - 1))) { + realEndExclusive--; + } + if (realStart >= realEndExclusive) { + return; + } + positions.add(new StatementPosition( + getLineNumber(script, realStart), + getLineNumber(script, realEndExclusive - 1) + )); + } + + private int getLineNumber(String script, int indexInclusive) { + int line = 1; + int max = Math.min(Math.max(indexInclusive, 0), script.length()); + for (int i = 0; i < max; i++) { + if (script.charAt(i) == '\n') { + line++; + } + } + return line; + } + + private record StatementPosition(int startLine, int endLine) { + } + private ExecuteResult executeSQL(String originalSql, DbType dbType, Command param) { int pageNo = 1; int pageSize = 0; Integer offset = null; Integer count = null; String sqlType = SqlTypeEnum.UNKNOWN.getCode(); - // 解析sql String type = Chat2DBContext.getConnectInfo().getDbType(); boolean supportDruid = !DataSourceTypeEnum.MONGODB.getCode().equals(type); - // 解析sql分页 - SQLStatement sqlStatement = null; + boolean supportJsqlParser = supportDruid; + + // Parse Druid AST once + SQLStatement druidSqlStatement = null; if (supportDruid) { try { - sqlStatement = SQLUtils.parseSingleStatement(originalSql, dbType); + druidSqlStatement = SQLUtils.parseSingleStatement(originalSql, dbType); } catch (ParserException e) { - log.warn("解析sql失败:{}", originalSql, e); + log.warn("Druid parse sql error:{}", originalSql, e); } } - // Mongodb is currently unable to recognize it, so every time a page is transmitted - if (!supportDruid || (sqlStatement instanceof SQLSelectStatement)) { + // Parse JSqlParser AST once + net.sf.jsqlparser.statement.Statement jsqlStatement = null; + if (supportJsqlParser) { + try { + jsqlStatement = CCJSqlParserUtil.parse(originalSql); + } catch (Exception e) { + log.warn("JSqlParser parse sql error:{}", originalSql, e); + } + } + + // Determine SQL type using Druid AST + if (!supportDruid || (druidSqlStatement instanceof SQLSelectStatement)) { pageNo = Optional.ofNullable(param.getPageNo()).orElse(1); pageSize = Optional.ofNullable(param.getPageSize()).orElse(EasyToolsConstant.MAX_PAGE_SIZE); offset = (pageNo - 1) * pageSize; @@ -583,8 +781,9 @@ private ExecuteResult executeSQL(String originalSql, DbType dbType, Command para sqlType = SqlTypeEnum.SELECT.getCode(); } + // Check pagination using JSqlParser AST ExecuteResult executeResult = null; - if (SqlTypeEnum.SELECT.getCode().equals(sqlType) && !SqlUtils.hasPageLimit(originalSql, dbType)) { + if (SqlTypeEnum.SELECT.getCode().equals(sqlType) && jsqlStatement != null && !SqlUtils.hasPageLimit(jsqlStatement, dbType)) { String pageLimit = Chat2DBContext.getSqlBuilder().pageLimit(originalSql, offset, pageNo, pageSize); if (StringUtils.isNotBlank(pageLimit)) { executeResult = execute(pageLimit, 0, count); @@ -597,10 +796,10 @@ private ExecuteResult executeSQL(String originalSql, DbType dbType, Command para executeResult.setSqlType(sqlType); executeResult.setOriginalSql(originalSql); - boolean supportJsqlParser = !DataSourceTypeEnum.MONGODB.getCode().equals(type); - if (supportJsqlParser) { + // Build editable result using JSqlParser AST + if (supportJsqlParser && SqlTypeEnum.SELECT.getCode().equals(sqlType) && jsqlStatement != null) { try { - SqlUtils.buildCanEditResult(originalSql, dbType, executeResult); + SqlUtils.buildCanEditResultFromStatement(jsqlStatement, originalSql, dbType, executeResult); } catch (Exception e) { log.warn("buildCanEditResult error", e); } @@ -618,10 +817,6 @@ private ExecuteResult executeSQL(String originalSql, DbType dbType, Command para } List
    headers = executeResult.getHeaderList(); -// if (executeResult.getSuccess() && executeResult.isCanEdit() && CollectionUtils.isNotEmpty(headers)) { -// headers = setColumnInfo(headers, executeResult.getTableName(), param.getSchemaName(), -// param.getDatabaseName()); -// } Header rowNumberHeader = Header.builder() .name(I18nUtils.getMessage("sqlResult.rowNumber")) .dataType(DataTypeEnum.CHAT2DB_ROW_NUMBER @@ -638,8 +833,8 @@ private ExecuteResult executeSQL(String originalSql, DbType dbType, Command para executeResult.getDataList().set(i, newRow); } } - // Total number of fuzzy rows executeResult.setFuzzyTotal(calculateFuzzyTotal(pageNo, pageSize, executeResult)); + executeResult.setJsqlStatement(jsqlStatement); return executeResult; } @@ -671,4 +866,27 @@ private ExecuteResult execute(String sql, Integer offset, Integer count) { } return executeResult; } + + + /** + * 获取指定数据库和表的外键列表 + * + * @param connection 数据库连接对象,用于执行元数据查询 + * @param databaseName 数据库名称,指定要查询外键的数据库 + * @param schemaName 架构名称,用于限定查询范围 + * @param tableName 表名称,指定要查询外键的表 + * @return 返回一个包含表中外键信息的列表 + * @throws RuntimeException 如果查询过程中发生错误,将抛出运行时异常 + * + * 此方法通过数据库连接对象的元数据方法获取外键信息, + * 并将结果集转换为ForeignKey对象的列表返回 + */ + public List foreignKeys(Connection connection, String databaseName, + String schemaName, String tableName) { + try (ResultSet resultSet = connection.getMetaData().getImportedKeys(databaseName, schemaName, tableName);) { + return ResultSetUtils.toObjectList(resultSet, ai.chat2db.spi.model.ForeignKey.class); + } catch (Exception e) { + throw new RuntimeException(e); + } + } } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SqlUtils.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SqlUtils.java index aa5e8e0c5..c2261b468 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SqlUtils.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SqlUtils.java @@ -1,65 +1,63 @@ - package ai.chat2db.spi.util; -import ai.chat2db.server.tools.base.excption.BusinessException; -import ai.chat2db.spi.enums.DataTypeEnum; -import ai.chat2db.spi.model.ExecuteResult; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; + import com.alibaba.druid.DbType; import com.alibaba.druid.sql.SQLUtils; import com.alibaba.druid.sql.ast.SQLStatement; import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; import com.alibaba.druid.sql.ast.statement.SQLJoinTableSource; import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; +import com.alibaba.druid.sql.ast.statement.SQLShowVariantsStatement; import com.alibaba.druid.sql.ast.statement.SQLTableSource; import com.alibaba.druid.sql.parser.SQLParserUtils; -import net.sf.jsqlparser.expression.BinaryExpression; + +import ai.chat2db.server.tools.base.excption.BusinessException; +import ai.chat2db.spi.enums.DataTypeEnum; +import ai.chat2db.spi.model.ExecuteResult; import net.sf.jsqlparser.expression.Function; -import net.sf.jsqlparser.parser.CCJSqlParser; import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.Statements; -import net.sf.jsqlparser.statement.select.*; -import org.apache.commons.lang3.StringUtils; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; +import net.sf.jsqlparser.statement.select.PlainSelect; +import net.sf.jsqlparser.statement.select.Select; +import net.sf.jsqlparser.statement.select.SelectBody; +import net.sf.jsqlparser.statement.select.SelectExpressionItem; +import net.sf.jsqlparser.statement.select.SelectItem; /** * @author jipengfei * @version : SqlUtils.java */ +@Slf4j public class SqlUtils { public static final String DEFAULT_TABLE_NAME = "table1"; - public static void buildCanEditResult(String sql, DbType dbType, ExecuteResult executeResult) { + + public static void buildCanEditResultFromStatement(Statement statement, String sql, DbType dbType, ExecuteResult executeResult) { try { - Statement statement ; - if (DbType.sqlserver.equals(dbType)) { - statement = CCJSqlParserUtil.parse(sql, ccjSqlParser -> ccjSqlParser.withSquareBracketQuotation(true)); - } else { - statement = CCJSqlParserUtil.parse(sql); - } if (statement instanceof Select) { Select select = (Select) statement; - PlainSelect plainSelect = (PlainSelect) select.getSelectBody(); - if (plainSelect.getJoins() == null && plainSelect.getFromItem() != null) { - for (SelectItem item : plainSelect.getSelectItems()) { - if (item instanceof SelectExpressionItem) { - SelectExpressionItem expressionItem = (SelectExpressionItem) item; - if (expressionItem.getAlias() != null) { - //canEdit = false; // 找到了一个别名 - executeResult.setCanEdit(false); - return; - } + SelectBody selectBody = select.getSelectBody(); + if (selectBody instanceof PlainSelect plainSelect) { + if (plainSelect.getJoins() == null && plainSelect.getFromItem() != null) { + for (SelectItem item : plainSelect.getSelectItems()) { if (item instanceof SelectExpressionItem) { + SelectExpressionItem expressionItem = (SelectExpressionItem) item; + if (expressionItem.getAlias() != null) { + executeResult.setCanEdit(false); + return; + } SelectExpressionItem selectExpressionItem = (SelectExpressionItem) item; - // 如果表达式是一个函数 if (selectExpressionItem.getExpression() instanceof Function) { Function function = (Function) selectExpressionItem.getExpression(); - // 检查函数是否为 "COUNT" if ("COUNT".equalsIgnoreCase(function.getName())) { executeResult.setCanEdit(false); return; @@ -67,20 +65,20 @@ public static void buildCanEditResult(String sql, DbType dbType, ExecuteResult e } } } + executeResult.setCanEdit(true); + SQLStatement sqlStatement = SQLUtils.parseSingleStatement(sql, dbType); + if ((sqlStatement instanceof SQLSelectStatement sqlSelectStatement)) { + SQLExprTableSource sqlExprTableSource = (SQLExprTableSource) getSQLExprTableSource( + sqlSelectStatement.getSelect().getFirstQueryBlock().getFrom()); + executeResult.setTableName(getMetaDataTableName(sqlExprTableSource.getCatalog(), sqlExprTableSource.getSchema(), sqlExprTableSource.getTableName())); + } + } else { + executeResult.setCanEdit(false); } - executeResult.setCanEdit(true); - SQLStatement sqlStatement = SQLUtils.parseSingleStatement(sql, dbType); - if ((sqlStatement instanceof SQLSelectStatement sqlSelectStatement)) { - SQLExprTableSource sqlExprTableSource = (SQLExprTableSource) getSQLExprTableSource( - sqlSelectStatement.getSelect().getFirstQueryBlock().getFrom()); - executeResult.setTableName(getMetaDataTableName(sqlExprTableSource.getCatalog(), sqlExprTableSource.getSchema(), sqlExprTableSource.getTableName())); - } - } else { - executeResult.setCanEdit(false); } } } catch (Exception e) { - e.printStackTrace(); + log.error("buildCanEditResultFromStatement error:", e); executeResult.setCanEdit(false); } } @@ -94,16 +92,27 @@ public static String formatSQLString(Object para) { } public static String getTableName(String sql, DbType dbType) { - SQLStatement sqlStatement = SQLUtils.parseSingleStatement(sql, dbType); - if (!(sqlStatement instanceof SQLSelectStatement sqlSelectStatement)) { - throw new BusinessException("dataSource.sqlAnalysisError"); - } - SQLExprTableSource sqlExprTableSource = (SQLExprTableSource) getSQLExprTableSource( - sqlSelectStatement.getSelect().getFirstQueryBlock().getFrom()); - if (sqlExprTableSource == null) { + try { + SQLStatement sqlStatement = SQLUtils.parseSingleStatement(sql, dbType); + if (sqlStatement instanceof SQLSelectStatement sqlSelectStatement) { + SQLExprTableSource sqlExprTableSource = (SQLExprTableSource) getSQLExprTableSource( + sqlSelectStatement.getSelect().getFirstQueryBlock().getFrom()); + if (sqlExprTableSource == null) { + return DEFAULT_TABLE_NAME; + } + return sqlExprTableSource.getTableName(); + } else if (sqlStatement instanceof SQLShowVariantsStatement) { + // 对于 SHOW VARIABLES 语句,返回一个默认表名 + return "VARIABLES"; + } + log.error("sqlStatement error:{}", sqlStatement.getClass().getName()); + } catch (Exception e) { + log.error("getTableName error:", e); + // 当SQL解析失败时,返回默认表名而不是抛出异常 return DEFAULT_TABLE_NAME; } - return sqlExprTableSource.getTableName(); + // 对于不支持的语句类型,返回默认表名 + return DEFAULT_TABLE_NAME; } private static SQLTableSource getSQLExprTableSource(SQLTableSource sqlTableSource) { @@ -112,6 +121,7 @@ private static SQLTableSource getSQLExprTableSource(SQLTableSource sqlTableSourc } else if (sqlTableSource instanceof SQLJoinTableSource sqlJoinTableSource) { return getSQLExprTableSource(sqlJoinTableSource.getLeft()); } + log.error("getSQLExprTableSource error:{}", sqlTableSource.getClass().getName()); return null; } @@ -119,16 +129,22 @@ public static List parse(String sql, DbType dbType) { List list = new ArrayList<>(); try { Statements statements = CCJSqlParserUtil.parseStatements(sql); - // 遍历每个语句 - for (Statement stmt : statements.getStatements()) { - list.add(stmt.toString()); - } + return parseFromStatements(statements); } catch (Exception e) { + log.error("parse error:", e); list = SQLParserUtils.splitAndRemoveComment(sql, dbType); } return list; } + public static List parseFromStatements(Statements statements) { + List list = new ArrayList<>(); + for (Statement stmt : statements.getStatements()) { + list.add(stmt.toString()); + } + return list; + } + private static final String DEFAULT_VALUE = "CHAT2DB_UPDATE_TABLE_DATA_USER_FILLED_DEFAULT"; public static String getSqlValue(String value, String dataType) { @@ -145,25 +161,27 @@ public static String getSqlValue(String value, String dataType) { public static boolean hasPageLimit(String sql, DbType dbType) { try { Statement statement = CCJSqlParserUtil.parse(sql); - if (statement instanceof Select) { - Select selectStatement = (Select) statement; - SelectBody selectBody = selectStatement.getSelectBody(); - // 检查常见的分页方法 - if (selectBody instanceof PlainSelect) { - PlainSelect plainSelect = (PlainSelect) selectBody; - // 检查 LIMIT - if (plainSelect.getLimit() != null || plainSelect.getOffset() != null || plainSelect.getTop() != null || plainSelect.getFetch() != null) { - return true; - } - if (DbType.oracle.equals(dbType)) { - return sql.contains("ROWNUM") || sql.contains("rownum"); - } - } - } + return hasPageLimit(statement, dbType); } catch (Exception e) { + log.error("hasPageLimit error:", e); return false; } + } + + public static boolean hasPageLimit(Statement statement, DbType dbType) { + if (statement instanceof Select selectStatement) { + SelectBody selectBody = selectStatement.getSelectBody(); + if (selectBody instanceof PlainSelect plainSelect) { + if (plainSelect.getLimit() != null || plainSelect.getOffset() != null || plainSelect.getTop() != null || plainSelect.getFetch() != null) { + return true; + } + if (DbType.oracle.equals(dbType)) { + String sql = statement.toString(); + return sql.contains("ROWNUM") || sql.contains("rownum"); + } + } + } return false; } -} \ No newline at end of file +} diff --git a/chat2db-server/pom.xml b/chat2db-server/pom.xml index 16c693477..68461c696 100644 --- a/chat2db-server/pom.xml +++ b/chat2db-server/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 3.1.0 + 3.2.5 4.0.0 @@ -17,7 +17,7 @@ - 2.0.0-SNAPSHOT + 2.1.16-SNAPSHOT 17 17 17 @@ -25,6 +25,9 @@ UTF-8 true + 1.1.4 + + 2.15.1 @@ -40,6 +43,13 @@ + + org.springframework.ai + spring-ai-bom + ${spring-ai.version} + pom + import + ai.chat2db @@ -222,7 +232,7 @@ com.unfbx chatgpt-java - 1.0.8 + 1.1.5 org.slf4j @@ -230,11 +240,6 @@ - - com.theokanning.openai-gpt3-java - service - 0.12.0 - @@ -318,6 +323,16 @@ sql-formatter 2.0.4 + + io.micrometer + context-propagation + 1.1.1 + + + io.lettuce + lettuce-core + 6.3.2.RELEASE + @@ -344,12 +359,27 @@ maven-compiler-plugin 3.11.0 - + - - -Amapstruct.disableBuilders=true - + -Amapstruct.disableBuilders=true + + + org.projectlombok + lombok + 1.18.30 + + + org.mapstruct + mapstruct-processor + 1.5.5.Final + + + org.projectlombok + lombok-mapstruct-binding + 0.2.0 + + @@ -380,6 +410,40 @@ + + + + com.github.spotbugs + spotbugs-maven-plugin + 4.8.3.1 + + Max + High + false + true + true + spotbugs-exclude.xml + + + + com.github.spotbugs + spotbugs + 4.8.3 + + + + + org.apache.maven.plugins diff --git a/chat2db-server/spotbugs-exclude.xml b/chat2db-server/spotbugs-exclude.xml new file mode 100644 index 000000000..9713c35f6 --- /dev/null +++ b/chat2db-server/spotbugs-exclude.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/docker/Dockerfile b/docker/Dockerfile index ff6fcddef..5a7346095 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -9,4 +9,4 @@ ADD chat2db-server/chat2db-server-web-start/target/chat2db-server-web-start.jar # 让当前容器暴露10824 EXPOSE 10824 # 运行jar包 -ENTRYPOINT ["java","-Dloader.path=lib","-Dspring.profiles.active=release","-jar","chat2db-server-web-start.jar"] +ENTRYPOINT ["java", "--add-opens", "java.base/java.nio=ALL-UNNAMED", "-Dloader.path=lib", "-Dspring.profiles.active=release", "-jar", "chat2db-server-web-start.jar"] diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 9311f506c..3d9b24c5c 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -1,9 +1,21 @@ version: '3' services: chat2db: - image: chat2db/chat2db:latest + image: g-erqx7877-docker.pkg.coding.net/chat2db/hejianjun/chat2db:${CHAT_VERSION:-latest} container_name: chat2db-latest volumes: - - ~/.chat2db-docker:/root/.chat2db + - /home/chat2db:/root/.chat2db ports: - - "10824:10824" \ No newline at end of file + - "10824:10824" + networks: + - chat2db-network + restart: unless-stopped + extra_hosts: + - "hadoop1:10.99.101.1" + - "hadoop2:10.99.101.2" + - "hadoop3:10.99.101.3" + - "hadoop4:10.99.101.4" + - "hadoop5:10.99.101.5" +networks: + chat2db-network: + driver: bridge \ No newline at end of file diff --git a/docs/AI_CONVERSATION_PERSISTENCE_PLAN.md b/docs/AI_CONVERSATION_PERSISTENCE_PLAN.md new file mode 100644 index 000000000..d90914e76 --- /dev/null +++ b/docs/AI_CONVERSATION_PERSISTENCE_PLAN.md @@ -0,0 +1,904 @@ +# AI 连续对话重构方案 + +> **状态**: 待审核 +> **范围**: 前端 AiChat 面板 + 后端 AI 聊天全链路 +> **目标**: 把"仅内存、单会话、刷新即失"的 AI 聊天改造成"多会话、按用户隔离、跨刷新可恢复"的 ChatGPT 风格连续对话 + +--- + +## 目录 + +1. [现状分析](#一-现状分析) +2. [核心痛点](#二-核心痛点) +3. [设计决策](#三-设计决策) +4. [整体架构](#四-整体架构) +5. [后端实现](#五-后端实现) +6. [前端实现](#六-前端实现) +7. [关键边界与设计决策](#七-关键边界与设计决策) +8. [实施步骤](#八-实施步骤) +9. [时序图](#九-时序图) +10. [待拍板的开放问题](#十-待拍板的开放问题) + +--- + +## 一、现状分析 + +### 1.1 前端 + +| 模块 | 文件 | 关键发现 | +|---|---|---| +| AI 面板主组件 | `chat2db-client/src/components/AiChat/index.tsx` (850 行) | 单一巨型文件,内含 ThinkingBlock / ExplainPanel / SqlActionButtons / InputArea / TableSelector | +| AI Store | `chat2db-client/src/pages/main/workspace/store/aiChatStore.ts` (190 行) | `sessions: Map`,**未接入 persist 中间件**,刷新即失 | +| 触发点 | 6 处 `setPendingAiChat({...})` 调用 | `ConsoleEditor` / `SearchResult` / `ImportDataModal` / `DataGenerationModal` / `ViewAllTable` / `DatabaseTableEditor` | +| 持久化 | `chat2db-client/src/indexedDB/table.ts` (48 行) | 仅 1 张表 `workspaceConsoleDDL`,无 AI 相关表 | +| Store 持久化范式 | `pages/main/workspace/store/index.ts` | 已用 `zustand/middleware/persist` 包装 `useWorkspaceStore` (只 persist layout/currentConnectionDetails) | +| 接入位置 | `pages/main/workspace/components/WorkspaceExtend/config.tsx` | AI 作为右侧全局组件,以图标切换 | + +### 1.2 后端 + +| 模块 | 文件 | 关键发现 | +|---|---|---| +| 控制器 | `chat2db-server/.../controller/ai/ChatController.java` (229 行) | 3 个端点:`/api/ai/chat/payload` (POST)、`/api/ai/chat` (GET SSE)、`/api/ai/chat/{uid}` (DELETE 取消) | +| 状态机 | `controller/ai/statemachine/ChatStateMachineConfig.java` | Spring Statemachine 4.0.0,6 状态 16 事件,所有 Action 在 web-api 层 | +| 会话缓存 | `controller/ai/service/AiConversationCache.java` (118 行) | **Guava 5MB / 10min TTL,纯内存,重启即失**;仅保留最近 6 条用于 prompt 组装 | +| 请求 DTO | `controller/ai/request/ChatQueryRequest.java` (71 行) | 已含 `conversationId / history / previousSql / isRevision` 字段,前端已传但后端只在 prompt 中用 | +| 配置 | `config/AiChatConfig.java` (200 行) | Spring AI ChatClient 工厂,支持 OpenAI / Anthropic / Azure OpenAI | +| Prompt 模板 | `resources/prompt-templates.yml` (319 行) | 12 种 PromptType,**已有 TITLE_GENERATION**,可直接复用 | +| 数据库 | Flyway 25 次迁移 + 25 张表 | **0 张 AI 表**;无 `ai_conversation` / `ai_message` | +| MyBatis | 25 个 Mapper | **0 个 AI Mapper** | + +### 1.3 关键代码锚点 + +| 位置 | 用途 | +|---|---| +| `aiChatStore.ts:46-62` | `Map` 结构定义 | +| `aiChatStore.ts:69-81` | `createSession` action | +| `AiChat/index.tsx:358-600` | `sendAiChatInternal` 发送逻辑 | +| `AiChat/index.tsx:375-400` | "是否复用当前会话"判定 | +| `AiChat/index.tsx:715-826` | 面板 layout 边界 (statusBar / contentArea / inputFormArea) | +| `ChatController.java:60-105` | SSE 入口 | +| `ChatController.java:64-74` | `PendingChatPayload` 长 URL 兜底 | +| `ChatQueryRequest.java:47-70` | `conversationId / history / previousSql / isRevision` 字段 | +| `AiConversationCache.java:36-46` | `MAX_MESSAGES = 6` 滑动窗 | +| `prompt-templates.yml` | 全部 PromptType 模板 | +| `store/index.ts:13-37` | Zustand persist 中间件范式 | +| `indexedDB/table.ts:36-48` | 现有表定义 + `tableList` | +| `indexedDB/index.ts:7-15` | `createDB` 与 `onupgradeneeded` | +| `layouts/init/initIndexedDB.ts:5-11` | DB 名 `chat2db` 版本 1 | + +--- + +## 二、核心痛点 + +| 维度 | 现状 | 影响 | +|---|---|---| +| 持久化 | 后端:Guava 5MB/10min;前端:仅内存 | 刷新/重启/超时即丢全部历史 | +| 多会话 | Store 有 `Map` 但 UI 只暴露 `currentSessionId` | 无法切换、回看、对比多个对话 | +| 新对话 | 无显式按钮,仅当 promptType 变化或切换数据源时隐式新建 | 用户无"重开一个话题"的体验 | +| 用户隔离 | `uid` 即客户端 UUID,无 `userId` 字段 | 多人共用 DB 会串台;无法做权限校验 | +| 标题 | 无 | 会话列表只能显示 sessionId,识别度低 | +| 状态机持久 | `activeSessions` / `activeContexts` 纯内存 | 服务重启后 SSE 连接全部断开,无 resume 能力 | +| 容量 | 无限制 | 长期使用后内存/DB 可能膨胀 | +| 后端 API | 仅有 chat 流,缺 list / detail / rename / delete | 前端想做历史侧栏无数据源 | + +--- + +## 三、设计决策 + +经需求确认,采用以下方案: + +| 决策点 | 选择 | +|---|---| +| 持久化位置 | **后端 H2 + 前端 IndexedDB 缓存** | +| UI 布局 | **面板内左侧新增会话列表侧边栏** (~240px) | +| 标题生成 | **首条消息时异步调用 AI 生成** (复用 TITLE_GENERATION) | +| 用户隔离 | **增加 userId 字段,按登录用户隔离** | +| 失败降级 | 标题生成失败 → 截取首条消息前 20 字 | +| 删除 | 软删 (`status='DELETED'`),可恢复 | +| 列表分页 | 每页 20 条,按 `gmt_modified DESC` | +| 流式中切换 | Modal 二次确认,不丢消息 | +| 离线 | IndexedDB 兜底,后端不可用时仍可浏览历史,新建/删除/重命名按钮禁用 | +| 桌面端无登录 | `userId=0` (与 `RoleCodeEnum.DESKTOP.getDefaultUserId()` 一致) | + +--- + +## 四、整体架构 + +``` +┌───────────────────────────── 前端 (React) ─────────────────────────────┐ +│ │ +│ useAiChatStore (Zustand + persist → IndexedDB aiChatStore) │ +│ ├─ conversations: Record ← 列表元数据 │ +│ ├─ currentConversationId │ +│ └─ messagesByConv: Record ← 消息内容 │ +│ │ +│ AiChat/index.tsx (主面板,改造成两栏) │ +│ ├─ LeftSidebar (~240px) ConversationList │ +│ │ ├─ [+] 新建对话 │ +│ │ ├─ 搜索框 (server-side) │ +│ │ └─ 会话列表 (title · 预览 · 时间 · 删除) │ +│ └─ MainArea │ +│ ├─ Header (currentTitle + 数据源 + 状态标签) │ +│ ├─ MessageList (思考块 / SQL动作按钮 / 加载中 / 错误重试) │ +│ └─ InputArea (CascaderDB + 多行输入 + 发送) │ +│ │ +│ service/aiConversation.ts (后端 API 包装) │ +│ service/aiChatHistory.ts (IndexedDB 读 / 写) │ +└───────────────────────────────┬───────────────────────────────────────┘ + │ HTTPS / SSE +┌───────────────────────────────▼───────────────────────────────────────┐ +│ 后端 (Spring Boot) │ +│ │ +│ AiConversationController (新) │ +│ ├─ POST /api/ai/conversation/create ← 新建空会话 │ +│ ├─ GET /api/ai/conversation/list?pageNo&pageSize&searchKey │ +│ ├─ GET /api/ai/conversation/{id} ← 完整消息列表 │ +│ ├─ POST /api/ai/conversation/{id}/rename ← 重命名 │ +│ └─ DELETE /api/ai/conversation/{id} ← 软删 │ +│ │ +│ ChatController (现有改造) │ +│ └─ 流式完成后异步落库:ai_conversation / ai_message │ +│ │ +│ AiConversationTitleTask (新) │ +│ └─ 首条 user 消息触发,@Async 用 TITLE_GENERATION 生成标题 │ +│ │ +│ AiConversationService / AiConversationServiceImpl │ +│ └─ 按 userId 隔离;带"6 条滑动窗"用于 prompt 组装 │ +└───────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 五、后端实现 + +### 5.1 Flyway 迁移 + +**文件:** `chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V2_1_19__ai_chat.sql` + +```sql +CREATE TABLE IF NOT EXISTS `ai_conversation` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + `conversation_id` varchar(64) NOT NULL COMMENT '客户端会话UUID', + `user_id` bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT '用户id', + `title` varchar(256) DEFAULT NULL COMMENT '会话标题(异步AI生成)', + `data_source_id` bigint(20) unsigned DEFAULT NULL COMMENT '关联数据源', + `database_name` varchar(128) DEFAULT NULL COMMENT '数据库名', + `schema_name` varchar(128) DEFAULT NULL COMMENT 'schema名', + `message_count` int(11) NOT NULL DEFAULT 0 COMMENT '消息数量', + `last_message_preview` varchar(512) DEFAULT NULL COMMENT '最后一条消息预览', + `status` varchar(32) NOT NULL DEFAULT 'ACTIVE' COMMENT 'ACTIVE/ARCHIVED/DELETED', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_conv_id` (`conversation_id`), + KEY `idx_user_modified` (`user_id`, `gmt_modified`), + KEY `idx_user_ds` (`user_id`, `data_source_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AI 会话表'; + +CREATE TABLE IF NOT EXISTS `ai_message` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `conversation_id` varchar(64) NOT NULL COMMENT '会话ID', + `user_id` bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT '用户id', + `message_id` varchar(64) NOT NULL COMMENT '客户端消息UUID', + `role` varchar(16) NOT NULL COMMENT 'user/assistant', + `content` longtext NOT NULL COMMENT '消息内容', + `thinking` longtext DEFAULT NULL COMMENT '思考过程', + `prompt_type` varchar(32) DEFAULT NULL COMMENT 'PromptType', + `sql_extracted` longtext DEFAULT NULL COMMENT '提取的SQL(用于revision续接)', + `sequence_no` int(11) NOT NULL COMMENT '消息序号(0,1,2...)', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_conv_msg` (`conversation_id`, `message_id`), + KEY `idx_conv_seq` (`conversation_id`, `sequence_no`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AI 消息表'; +``` + +### 5.2 完整垂直切片 + +| 层 | 文件 | +|---|---| +| Migration | `db/migration/V2_1_19__ai_chat.sql` | +| DO | `repository/entity/AiConversationDO.java` | +| DO | `repository/entity/AiMessageDO.java` | +| Mapper | `repository/mapper/AiConversationMapper.java` | +| Mapper | `repository/mapper/AiMessageMapper.java` | +| XML | `resources/mapper/AiConversationMapper.xml` | +| XML | `resources/mapper/AiMessageMapper.xml` | +| DTO | `domain/api/model/AiConversation.java` | +| DTO | `domain/api/model/AiMessage.java` | +| Param | `domain/api/param/ai/AiConversationQueryParam.java` (extends PageQueryParam) | +| Param | `domain/api/param/ai/AiConversationCreateParam.java` | +| Param | `domain/api/param/ai/AiConversationRenameParam.java` | +| Service | `domain/api/service/AiConversationService.java` | +| ServiceImpl | `domain/core/impl/AiConversationServiceImpl.java` | +| Converter | `domain/core/converter/AiConversationConverter.java` (MapStruct) | +| Web Req | `web/api/controller/ai/conversation/request/AiConversationCreateRequest.java` | +| Web Req | `web/api/controller/ai/conversation/request/AiConversationQueryRequest.java` | +| Web Req | `web/api/controller/ai/conversation/request/AiConversationRenameRequest.java` | +| Web VO | `web/api/controller/ai/conversation/vo/AiConversationVO.java` | +| Web VO | `web/api/controller/ai/conversation/vo/AiMessageVO.java` | +| Web VO | `web/api/controller/ai/conversation/vo/AiConversationDetailVO.java` | +| Web Conv | `web/api/controller/ai/conversation/converter/AiConversationWebConverter.java` | +| Web Ctrl | `web/api/controller/ai/conversation/AiConversationController.java` | +| Task | `web/api/controller/ai/task/AiConversationTitleTask.java` (@Component, @Async) | + +> **删除**: `controller/ai/service/AiConversationCache.java` 与 `domain/core/cache/MemoryCacheManage.java` 中 AI 相关用法(若该缓存被其他模块使用则保留通用类)。 + +### 5.3 Controller 端点 + +```java +@Slf4j +@RequestMapping("/api/ai/conversation") +@RestController +public class AiConversationController { + + @Autowired private AiConversationService aiConversationService; + @Autowired private AiConversationWebConverter webConverter; + + @PostMapping("/create") + public DataResult create(@RequestBody AiConversationCreateRequest request) { + AiConversationCreateParam param = webConverter.req2param(request); + param.setUserId(ContextUtils.getUserId()); + return DataResult.of(aiConversationService.create(param)); + } + + @GetMapping("/list") + public WebPageResult list(AiConversationQueryRequest request) { + AiConversationQueryParam param = webConverter.req2param(request, ContextUtils.getUserId()); + ServicePage page = aiConversationService.list(param); + return WebPageResult.of(webConverter.dto2vo(page.getData()), page.getTotal(), + request.getPageNo(), request.getPageSize()); + } + + @GetMapping("/{conversationId}") + public DataResult get(@PathVariable String conversationId) { + AiConversationDetail detail = aiConversationService.getDetail(conversationId, ContextUtils.getUserId()); + return DataResult.of(webConverter.detail2vo(detail)); + } + + @PostMapping("/{conversationId}/rename") + public ActionResult rename(@PathVariable String conversationId, + @RequestBody AiConversationRenameRequest request) { + aiConversationService.rename(conversationId, request.getTitle(), ContextUtils.getUserId()); + return ActionResult.isSuccess(); + } + + @DeleteMapping("/{conversationId}") + public ActionResult delete(@PathVariable String conversationId) { + aiConversationService.delete(conversationId, ContextUtils.getUserId()); + return ActionResult.isSuccess(); + } +} +``` + +### 5.4 SSE 流程改造 (`ChatController` / `StreamAction`) + +在 `StreamAction` 流式完成后,**追加一次 DB 落库** (用 `Mono.fromRunnable(...)` 异步,失败不影响响应): + +```java +// StreamAction.java (改造片段) +Mono.fromRunnable(() -> + aiConversationService.appendMessageTurn( + conversationId, + userId, + userMessage, + fullAssistantContent, // 流式累加 + fullThinking, // 流式累加 + promptType, + extractedSql // 用于下次 isRevision + ) +).subscribeOn(Schedulers.boundedElastic()) + .subscribe(); +``` + +新增状态机事件 `ChatEvent.PERSIST_DONE` 触发 `PersistMessagesAction`,保持状态机一致性。 + +### 5.5 标题生成任务 + +```java +@Slf4j +@Component +public class AiConversationTitleTask { + @Autowired private AiChatConfig aiChatConfig; + @Autowired private AiConversationService conversationService; + + @Async("aiChatExecutor") + public void generateTitleAsync(String conversationId, String firstUserMessage) { + try { + ChatClient client = aiChatConfig.createChatClient(PromptType.TITLE_GENERATION); + String title = client.call().content(); + if (StringUtils.isNotBlank(title) && title.length() <= 50) { + conversationService.updateTitle(conversationId, title.trim()); + } else { + conversationService.updateTitle(conversationId, fallbackTitle(firstUserMessage)); + } + } catch (Exception e) { + log.warn("Generate AI conversation title failed: {}", e.getMessage()); + conversationService.updateTitle(conversationId, fallbackTitle(firstUserMessage)); + } + } + + private String fallbackTitle(String msg) { + if (msg == null) return "新对话"; + return msg.length() > 20 ? msg.substring(0, 20) + "..." : msg; + } +} +``` + +**触发点**: `ChatController.chat()` 入口处,如果 `request.getHistory() == null && previousSql == null` (即"该会话首条消息"),调用 `titleTask.generateTitleAsync(uid, message)`。 + +需在 `chat2db-server-start` 启用 `@EnableAsync` 并配置线程池 `aiChatExecutor` (core=2, max=4, queue=100)。 + +### 5.6 移除 `AiConversationCache` (直接读 H2) + +**决策**: 删除 `AiConversationCache` 与 `MemoryCacheManage`,`BuildPromptAction` 组装 prompt 时**直接查 H2** 读取最近 6 条消息。 + +**理由**: +- AI 响应耗时(秒级)远大于 H2 单表主键查询(毫秒级),缓存收益可忽略 +- 减少一层 Guava 缓存依赖,简化状态机与重启恢复 +- 避免缓存与服务端 DB 数据不一致(用户删除/重命名时需清理缓存) + +**`BuildPromptAction` 改造**: + +```java +// 直接通过 AiMessageMapper 读取 +List recent = aiMessageMapper.selectList( + new LambdaQueryWrapper() + .eq(AiMessage::getConversationId, request.getConversationId()) + .orderByDesc(AiMessage::getSequenceNo) + .last("LIMIT " + MAX_HISTORY_MESSAGES) +); +Collections.reverse(recent); +String historyJson = buildHistoryJson(recent); +String previousSql = recent.stream() + .filter(m -> "assistant".equals(m.getRole())) + .reduce((first, second) -> second) // 最后一条 assistant + .map(AiMessage::getSqlExtracted) + .orElse(null); +``` + +`MAX_HISTORY_MESSAGES = 6` 常量迁移至 `BuildPromptAction` 内部。 + +### 5.7 历史会话 100 条上限 + +**策略**: 单用户最多保留 100 条 `ACTIVE` 状态会话,超出时**自动将最旧的归档为 `ARCHIVED`**。 + +**触发点**: `AiConversationServiceImpl.create()` 新建会话前,先统计当前 `ACTIVE` 数量,若 ≥ 100 则把最旧的若干条 `UPDATE` 为 `ARCHIVED`。 + +```java +private static final int MAX_ACTIVE_CONVERSATIONS = 100; + +private void enforceQuota(Long userId) { + Long activeCount = conversationMapper.selectCount( + new LambdaQueryWrapper() + .eq(AiConversationDO::getUserId, userId) + .eq(AiConversationDO::getStatus, "ACTIVE") + ); + if (activeCount < MAX_ACTIVE_CONVERSATIONS) return; + + long toArchive = activeCount - MAX_ACTIVE_CONVERSATIONS + 1; // +1 给新建的腾位置 + List oldest = conversationMapper.selectList( + new LambdaQueryWrapper() + .eq(AiConversationDO::getUserId, userId) + .eq(AiConversationDO::getStatus, "ACTIVE") + .orderByAsc(AiConversationDO::getGmtModified) + .last("LIMIT " + toArchive) + ); + if (!oldest.isEmpty()) { + List ids = oldest.stream().map(AiConversationDO::getId).collect(Collectors.toList()); + conversationMapper.update(null, + new LambdaUpdateWrapper() + .in(AiConversationDO::getId, ids) + .set(AiConversationDO::getStatus, "ARCHIVED")); + } +} +``` + +`list()` 端点默认只查 `status='ACTIVE'`,不返回 `ARCHIVED` / `DELETED`。归档会话不计入 100 上限,不显示,但**保留数据可恢复**。 + +--- + +## 六、前端实现 + +### 6.1 Zustand Store 重构 + +**文件:** `chat2db-client/src/pages/main/workspace/store/aiChatStore.ts` + +```typescript +interface IAiChatStore { + // 会话元数据列表(侧边栏用) + conversationList: IConversationMeta[]; + + // 当前激活会话 + currentConversationId: string | null; + + // 每个会话的完整消息(按 conversationId 索引) + messagesByConv: Record; + + // 流式进行中的临时状态(不入 IndexedDB) + activeSession: AiChatSession | null; + + // 触发重渲染的 epoch + messageEpoch: number; +} +``` + +**持久化策略**: `zustand/middleware/persist`,**只持久化** `conversationList` / `currentConversationId` / `messagesByConv`: + +```typescript +export const useAiChatStore = create()( + devtools( + persist( + (set, get) => ({ + conversationList: [], + currentConversationId: null, + messagesByConv: {}, + activeSession: null, + messageEpoch: 0, + // ...actions + }), + { + name: 'ai-chat-store', + storage: createJSONStorage(() => idbStorage), + partialize: (state) => ({ + conversationList: state.conversationList, + currentConversationId: state.currentConversationId, + messagesByConv: state.messagesByConv, + }), + version: 1, + } + ) + ) +); +``` + +### 6.2 新增 service 模块 + +**文件:** `chat2db-client/src/service/aiConversation.ts` + +```typescript +import createRequest from './base'; + +export interface IConversationMeta { + id: string; + title: string; + dataSourceId?: number; + databaseName?: string; + schemaName?: string; + messageCount: number; + lastMessagePreview?: string; + gmtCreate: string; + gmtModified: string; +} + +export interface IMessage { + id: string; + role: 'user' | 'assistant'; + content: string; + thinking?: string; + promptType?: string; + sequenceNo: number; + gmtCreate: string; +} + +export interface IConversationDetail { + conversation: IConversationMeta; + messages: IMessage[]; +} + +const create = createRequest<{ dataSourceId?: number; databaseName?: string; schemaName?: string }, string>( + '/api/ai/conversation/create', + { method: 'post' } +); + +const list = createRequest<{ pageNo: number; pageSize: number; searchKey?: string; dataSourceId?: number }, + IPageResponse>( + '/api/ai/conversation/list' +); + +const get = createRequest<{ conversationId: string }, IConversationDetail>( + '/api/ai/conversation/:conversationId' +); + +const rename = createRequest<{ conversationId: string; title: string }, void>( + '/api/ai/conversation/:conversationId/rename', + { method: 'post' } +); + +const remove = createRequest<{ conversationId: string }, void>( + '/api/ai/conversation/:conversationId', + { method: 'delete' } +); + +export default { create, list, get, rename, remove }; +``` + +### 6.3 AiChat 组件拆分 + +``` +src/components/AiChat/ +├── index.tsx ← 仅做 layout + 状态编排 (<200 行) +├── index.less +├── ConversationSidebar.tsx ← 左侧列表 +├── ConversationItem.tsx ← 单条会话 +├── MessageList.tsx ← 中间消息流 +├── InputArea.tsx ← 输入框 + 发送 +├── ThinkingBlock.tsx ← 已有,移出 +├── ExplainPanel.tsx ← 已有,移出 +├── SqlActionButtons.tsx ← 已有,移出 +└── useAiChatController.ts ← 业务 hook +``` + +**主面板结构:** + +```tsx +return ( +
    + +
    + + + +
    +
    +); +``` + +**`ConversationSidebar.tsx` 关键功能:** + +```tsx + +``` + +**`useAiChatController.ts` 关键流程:** + +```typescript +const switchTo = async (conversationId: string) => { + const cached = messagesByConv[conversationId]; + if (!cached) { + const detail = await aiConversationService.get({ conversationId }); + setMessagesByConv(conversationId, detail.messages); + } + setCurrentConversationId(conversationId); +}; + +const handleNewChat = async () => { + const conversationId = await aiConversationService.create({ + dataSourceId: boundInfo.dataSourceId, + databaseName: boundInfo.databaseName, + schemaName: boundInfo.schemaName, + }); + addConversationMeta({ id: conversationId, title: '新对话', messageCount: 0, gmtCreate: new Date().toISOString() }); + setCurrentConversationId(conversationId); +}; +``` + +### 6.4 IndexedDB 升级 + +**文件:** `chat2db-client/src/indexedDB/table.ts` (新增表) + +```typescript +export const aiChatStore = { + name: 'aiChatStore', + primaryKey: { keyPath: 'key' }, + column: [ + { name: 'key', isIndex: true, keyPath: 'key', options: { unique: true } }, + { name: 'value', isIndex: false, keyPath: 'value' }, + ], +}; + +export const tableList = [ + { tableDetails: workspaceConsoleDDL }, + { tableDetails: aiChatStore }, +]; +``` + +**文件:** `chat2db-client/src/indexedDB/index.ts` (扩展 TableType) + +```typescript +type TableType = 'workspaceConsoleDDL' | 'aiChatStore'; +``` + +**文件:** `chat2db-client/src/layouts/init/initIndexedDB.ts` (版本 1 → 2) + +```typescript +indexedDB.createDB('chat2db', 2).then((db) => { + window._indexedDB = { chat2db: db }; +}); +``` + +### 6.5 i18n 补充 + +**文件:** `chat2db-client/src/i18n/{en-us,zh-cn,ja-jp,tr-tr}/chat.ts` + +```typescript +'chat.sidebar.newChat': '新建对话', +'chat.sidebar.search.placeholder': '搜索历史对话', +'chat.sidebar.empty': '暂无历史对话', +'chat.sidebar.delete.confirm': '确认删除该对话?', +'chat.sidebar.delete.success': '已删除', +'chat.header.title.placeholder': '未命名对话', +'chat.header.rename': '重命名', +'chat.loadHistory.failed': '加载历史失败,请重试', +'chat.network.offline.cache': '当前为离线缓存,部分功能不可用', +'chat.switch.confirm': '当前对话未完成,确认离开?', +``` + +--- + +## 七、关键边界与设计决策 + +| 决策点 | 选择 | 原因 | +|---|---|---| +| 软删 vs 硬删 | `status='DELETED'` 软删 | 误删可恢复;按 userId 过滤,不影响他人 | +| 历史列表分页 | 每页 20 条,游标用 `gmtModified DESC` | 长列表不爆 IndexedDB;符合用户翻页习惯 | +| 标题生成失败 | 降级为"对话 {前 20 字}" | 不能让一个标题阻塞整条流式响应 | +| 切换会话时正在流式 | **Modal 二次确认** | 不能丢消息,符合编辑器历史习惯 | +| SSE 中途断网 | IndexedDB 保留 partial;下次启动可恢复 | `activeSession` 暂存,`complete()` 后才入库 | +| 多设备登录 | 不做实时同步;每次进面板主动拉后端列表 | 复杂度可控,符合大多数 AI 客户端 | +| 跨数据源会话隔离 | 会话元数据中带 `dataSourceId`;侧边栏分组显示 | 可选增强,不在 MVP 必做 | +| 容量限制 | 单用户最多 100 个 `ACTIVE` 会话(超出 LRU 自动归档为 `ARCHIVED`,数据保留) | 防 DB 膨胀 | +| Prompt 历史缓存 | **不再用** `AiConversationCache`,`BuildPromptAction` 直接查 H2 | AI 耗时远大于查询耗时,缓存收益忽略;简化依赖 | +| 桌面端无登录 | `userId=0` (与 `RoleCodeEnum.DESKTOP.getDefaultUserId()` 一致) | 复用 `PermissionUtils` 已有逻辑 | +| 性能:大数据量 | 单会话 100+ 消息时,只渲染可视区(`react-window` 虚拟列表) | 防御性,先在简单列表验证 | + +--- + +## 八、实施步骤 + +| 步骤 | 内容 | 验证 | +|---|---|---| +| **1. 后端持久化骨架** | Flyway V2_1_19 + 2 个 DO + 2 个 Mapper + 2 个 XML | `mvn compile` 通过 | +| **2. 后端 Domain 层** | AiConversation DTO + Param + Service + ServiceImpl + Converter | `mvn test` 通过 | +| **3. 后端 Web 层** | Request + VO + WebConverter + Controller 全部端点 | `curl` 5 个端点 | +| **4. 集成到 ChatController** | 流式完成时 `appendMessageTurn` 落库;首条消息触发 `generateTitleAsync` | 端到端:发消息 → DB 有数据 → 标题异步回填 | +| **5. 前端 service** | `aiConversation.ts` + 5 个端点 | TypeScript 编译过 | +| **6. 前端 store 重构** | `aiChatStore.ts` 改用 Record + persist + IndexedDB 适配器 | 刷新页面消息仍在 | +| **7. 拆分 AiChat 组件** | 抽出 ConversationSidebar / MessageList / InputArea / useAiChatController | `yarn lint` 通过 | +| **8. UI 集成** | 接入 sidebar + newChat 按钮 + 切换/重命名/删除 | 浏览器测全套流程 | +| **9. 标题异步更新** | SSE 完成 → 5 秒内标题自动出现 | 浏览器观察 | +| **10. 离线兜底** | 网络失败时降级用 IndexedDB 缓存 | DevTools 断网测试 | +| **11. 兼容旧触发点** | 6 处 `pendingAiChat` 调用全部走新流程 | 触发每个 case 测一遍 | +| **12. i18n + 文案** | 4 种语言补齐 | 切换语言验证 | + +--- + +## 九、时序图 + +### 9.1 新建会话 + 发送首条消息 + +```mermaid +sequenceDiagram + autonumber + actor U as 用户 + participant V as AiChat UI + participant CS as useAiChatStore
    (Zustand+persist) + participant IDB as IndexedDB + participant SVC as aiConversationService + participant CC as AiConversationController + participant CD as AiChatController
    (SSE) + participant DB as H2 + MyBatis + participant AI as Spring AI
    ChatClient + participant TT as AiConversationTitleTask
    @Async + + U->>V: 点击 [新建对话] 或触发 pendingAiChat + V->>SVC: create({dataSourceId, db, schema}) + SVC->>CC: POST /api/ai/conversation/create + CC->>DB: INSERT ai_conversation (status=ACTIVE, title=NULL) + CC-->>SVC: {conversationId: "uuid-xxx"} + SVC-->>V: conversationId + V->>CS: addConversationMeta({id, title:"新对话"})
    setCurrentConversationId(id) + CS->>IDB: persist(partialize) → aiChatStore + + U->>V: 输入问题并发送 + V->>CS: get().currentConversationId + V->>CD: GET /api/ai/chat?conversationId=xxx&message=... + activate CD + CD->>DB: INSERT ai_message (role=user, seq=0) + CD->>AI: 流式调用 + activate AI + CD->>CD: 触发 TITLE_GENERATION 异步任务 + CD-->>TT: generateTitleAsync(convId, firstUserMsg) + deactivate CD + TT->>AI: (并行) 调用 TITLE_GENERATION + AI-->>TT: "查询最近一周订单" + TT->>DB: UPDATE ai_conversation.title='查询最近一周订单' + + loop SSE 流式 + AI-->>CD: token + CD-->>V: event:message {content/thinking} + V->>CS: appendContent + 渲染 + end + + AI-->>CD: [DONE] + CD->>DB: INSERT ai_message (role=assistant, seq=1,
    content, thinking, sql_extracted) + CD->>DB: UPDATE ai_conversation
    SET message_count=2,
    last_message_preview=... + CD-->>V: SSE complete + deactivate AI + V->>CS: 整理 activeSession → messagesByConv[id] + CS->>IDB: persist +``` + +### 9.2 继续对话 (revision 模式) + +```mermaid +sequenceDiagram + autonumber + actor U as 用户 + participant V as AiChat UI + participant CS as useAiChatStore + participant CD as AiChatController + participant BP as BuildPromptAction + participant ACC as AiConversationCache + participant DB as H2 + participant AI as Spring AI + + U->>V: 在当前会话输入追问 + V->>CS: 检查 currentSession + isRevision 判定 + Note right of V: 已存在 assistant 消息 +
    promptType=NL_2_SQL →
    isRevision=true + V->>CD: GET /api/ai/chat
    conversationId=xxx
    isRevision=true
    (不传 history/previousSql) + CD->>BP: 进入 BUILDING_PROMPT + BP->>ACC: getHistoryJson(convId) + ACC->>DB: SELECT 最近 6 条 ai_message + ACC-->>BP: historyJson + BP->>ACC: getPreviousSql(convId) + ACC->>DB: 取最近 assistant.sql_extracted + ACC-->>BP: previousSql + BP->>AI: 组装 prompt (含历史 + 上一条 SQL) + AI-->>V: 流式输出 + CD->>DB: INSERT ai_message (user seq=2, assistant seq=3) +``` + +### 9.3 切换到历史会话 + +```mermaid +sequenceDiagram + autonumber + actor U as 用户 + participant SB as ConversationSidebar + participant CS as useAiChatStore + participant IDB as IndexedDB + participant SVC as aiConversationService + participant CC as AiConversationController + participant DB as H2 + + Note over SB: 侧边栏初始化 + SB->>SVC: list({pageNo:1, pageSize:20}) + SVC->>CC: GET /api/ai/conversation/list + CC->>DB: SELECT * FROM ai_conversation
    WHERE user_id=? AND status='ACTIVE'
    ORDER BY gmt_modified DESC
    LIMIT 20 + CC-->>SB: {total, data:[...]} + SB->>CS: setConversationList(data) + + U->>SB: 点击某条历史会话 + SB->>CS: messagesByConv[convId] ? + alt IndexedDB 命中 + CS-->>SB: 缓存的消息列表 + else 未命中 + SB->>SVC: get({conversationId}) + SVC->>CC: GET /api/ai/conversation/{id} + CC->>DB: SELECT conv + messages + CC-->>SVC: {conversation, messages} + SVC-->>SB: 详情 + SB->>CS: setMessagesByConv(convId, messages) + end + SB->>CS: setCurrentConversationId(convId) + CS->>IDB: persist + Note over SB: 触发流式中断检查 + alt 当前有 activeSession 流式中 + SB-->>U: Modal "当前对话未完成,确认离开?" + end +``` + +### 9.4 删除会话 + +```mermaid +sequenceDiagram + autonumber + actor U as 用户 + participant SB as ConversationSidebar + participant CS as useAiChatStore + participant SVC as aiConversationService + participant CC as AiConversationController + participant DB as H2 + + U->>SB: hover → 点击删除图标 → 确认 + SB->>SVC: remove({conversationId}) + SVC->>CC: DELETE /api/ai/conversation/{id} + CC->>DB: SELECT user_id FROM ai_conversation
    WHERE conversation_id=? + CC->>CC: PermissionUtils.checkOperationPermission(userId) + CC->>DB: UPDATE ai_conversation
    SET status='DELETED', gmt_modified=NOW() + CC-->>SB: 200 OK + SB->>CS: removeConversationMeta(convId) + alt 删除的是当前会话 + SB->>CS: setCurrentConversationId(下一条/空) + end + CS->>IDB: persist (清除该 conv 的 messages) +``` + +### 9.5 离线/弱网降级 + 启动时数据修复 + +```mermaid +sequenceDiagram + autonumber + actor U as 用户 + participant V as AiChat UI + participant CS as useAiChatStore + participant IDB as IndexedDB + participant SVC as aiConversationService + participant CC as Backend + + Note over V: 页面刷新 / 启动 + V->>CS: zustand persist rehydrate + CS->>IDB: 读取 aiChatStore 缓存 + IDB-->>CS: {conversationList, messagesByConv} + CS-->>V: 立即渲染侧边栏与当前消息(零延迟) + V->>SVC: list({pageNo:1}) [后台静默] + SVC->>CC: GET /api/ai/conversation/list + + alt 网络正常 + CC-->>SVC: 最新列表 + SVC->>CS: 合并更新 (本地优先 + 后端补全 title/preview) + else 网络失败 + CC--xSVC: timeout / error + SVC-->>V: 标记"离线模式" Badge + V->>U: 列表仍可浏览(来自 IndexedDB),
    但"新建/删除/重命名"按钮禁用 + end + + Note over V: SSE 流式过程中断网 + V->>CS: activeSession.partialAssistant 保留在内存 + V->>IDB: 定期 (debounce 2s) 持久化 activeSession + Note over V: 下次启动或网络恢复
    展示"上次未完成"标记,
    点击可重发 +``` + +### 9.6 时序要点 + +1. **乐观本地优先**:任何"切换/查看"操作都先读 IndexedDB,后端只做补全和权威写入。前端 UI 永不"等"网络。 +2. **SSE 落库异步化**:`appendMessageTurn` 与 `generateTitleAsync` 都走独立线程池,失败不影响响应;标题生成失败时降级用首条消息前 20 字;`BuildPromptAction` 直接查 H2,不再走 `AiConversationCache`。 +3. **权限检查在 Service 层**:所有 `*WithPermission` 方法在 ServiceImpl 内部用 `PermissionUtils.checkOperationPermission(creatorId)` 拦截,Controller 永远只看 `ContextUtils.getUserId()`。 +4. **流式中切换会话**:用 Modal 二次确认,避免"已发的 user 消息消失"的认知断层;确认后**不丢消息**——该 user 消息已落入 DB,下次回来仍可见。 +5. **IndexedDB Schema 升级**:版本从 `1` 升到 `2`,在 `onupgradeneeded` 中新增 `aiChatStore` 表(单条 key='current')。 +6. **State Machine 整合**:新增一个 `ChatEvent.PERSIST_DONE` 事件,在 `StreamAction` 末尾(complete 之前)发出,触发一个新的 `PersistMessagesAction` 负责落库 —— 这样既保持状态机一致性,又便于将来替换为消息队列。 + +--- + +## 十、待拍板的开放问题 + +1. **历史列表的虚拟滚动**:是否使用 `react-window` / `react-virtuoso`?MVP 阶段是否可先简单列表 + 分页? +2. **跨数据源会话分组**:侧边栏是否需要按数据源分组(像 Navicat 那样)?还是简单按时间倒序? +3. **会话导入/导出**:是否需要把某个会话导出为 Markdown / JSON?(ChatGPT 已支持) +4. **多模态(图片/文件输入)**:重构时是否预留?目前只有纯文本,但 schema 建议把 `ai_message.content` 留作 `longtext` 兼容 markdown 已 OK +5. **服务器重启后,session 恢复机制**:是否需要支持?目前 `activeSessions` 是 in-memory,SSE 断开后无法续接 +6. **`message_count` 字段是否冗余**:可以用 SQL 聚合替代,但保留便于列表展示"X 条消息" +7. **`sql_extracted` 字段是否需要**:为 revision 模式服务,但每次都要重算一次提取;是否要缓存? + +--- + +**审核要点请关注**: +- §5.1 DDL 设计(索引/字段) +- §5.5 标题生成的降级策略 +- §6.1 Zustand Store 拆分边界 +- §6.3 组件拆分粒度 +- §7 容量与隔离策略 +- §8 实施顺序是否合理 +- §10 开放问题答案 diff --git a/docs/AI_TITLE_GENERATION_DESIGN.md b/docs/AI_TITLE_GENERATION_DESIGN.md new file mode 100644 index 000000000..e31867fcd --- /dev/null +++ b/docs/AI_TITLE_GENERATION_DESIGN.md @@ -0,0 +1,317 @@ +# AI 生成标题功能设计文档 + +## 概述 + +本文档描述了 Chat2DB 中 AI 生成标题功能的后端状态机流转和前端交互时序设计。 + +## 1. 后端状态机流转图 + +``` + ┌──────────┐ + │ IDLE │ ← 初始状态 + └─────┬────┘ + │ 接收 /api/ai/chat 请求 + │ promptType=TITLE_GENERATION + ▼ + ┌───────────────────┐ + │ BUILDING_PROMPT │ + │ (构建提示词) │ + └─────────┬─────────┘ + │ action: BuildPromptAction + │ - 加载 title_generation 模板 + │ - 填充 SQL 内容 + ▼ + ┌───────────────────┐ + │ STREAMING │◀─────────────────┐ + │ (AI 流式响应) │ │ + └─────────┬─────────┘ │ + │ │ + ┌───────────────┼───────────────┐ │ + │ │ │ │ + [流式返回片段] [AI 完成响应] [发生错误] │ + │ │ │ │ + ▼ ▼ ▼ │ + ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ + │ SSE message │ │ SSE [DONE] │ │ SSE error │ │ + │ content片段 │ │ + 结束连接 │ │ + 结束连接 │ │ + └──────────────┘ └──────────────┘ └──────────────┘ │ + │ │ │ + │ ▼ │ + │ ┌──────────────┐ │ + └────────────────────────────│ FAILED │ │ + │ (失败状态) │ │ + └──────────────┘ │ + │ │ + │ [重试请求] │ + └──────────────┘ +``` + +### 状态说明 + +| 状态 | 说明 | +|------|------| +| IDLE | 空闲状态,等待请求 | +| BUILDING_PROMPT | 构建提示词:从 `prompt-templates.yml` 加载模板,填充 SQL 内容 | +| STREAMING | 调用 AI 模型(OpenAI/Anthropic),流式返回内容 | +| FAILED | AI 调用失败或网络错误 | + +### 事件说明 + +| 事件 | 说明 | +|------|------| +| REQUEST_GENERATE_TITLE | 接收标题生成请求 | +| PROMPT_BUILT | 提示词构建完成 | +| STREAM_FINISHED | AI 流式响应完成,发送 [DONE] | +| AI_CALL_FAILED | AI 调用失败 | +| CANCEL | 用户取消操作 | +| RETRY | 重试请求 | + +### 状态机动作(Actions) + +| Action | 职责 | +|--------|------| +| `BuildPromptAction` | 根据模板构建发送给 AI 的提示词 | +| `StreamAction` | 调用 AI 模型进行流式输出 | + +## 2. 前端交互时序图 + +``` +┌──────────┐ ┌──────────┐ ┌───────────┐ ┌──────────┐ ┌──────────────┐ +│ 用户 │ │ 前端组件 │ │ 前端服务 │ │ 后端 │ │ AI 模型 │ +│ │ │ Workspace │ │ indexedDB │ │ Spring │ │ (OpenAI/ │ +│ │ │ Tabs │ │ │ │ Boot │ │ Anthropic) │ +└────┬─────┘ └────┬─────┘ └─────┬─────┘ └────┬─────┘ └──────┬───────┘ + │ │ │ │ │ + │ 1. 右键点击 Tab │ │ │ │ + │ 选择"AI 生成标题" │ │ │ │ + ├────────────────>│ │ │ │ + │ │ │ │ │ + │ │ 2. 获取 SQL 内容 │ │ │ + │ │ (从 tabData 或 │ │ │ + │ │ indexedDB 获取未保存 │ │ │ + │ │ 的修改) │ │ │ + │ ├─────────────────>│ │ │ + │ │<─────────────────│ │ │ + │ │ 返回 SQL 内容 │ │ │ + │ │ │ │ │ + │ │ 3. 设置生成中标记 │ │ │ + │ │ setGeneratingTitleKey │ │ │ + │ │ │ │ │ + │ │ 4. 创建 SSE 连接 │ │ │ + │ │ connectToEventSource │ │ │ + │ │ GET /api/ai/chat? │ │ │ + │ │ message={sql} │ │ │ + │ │ promptType= │ │ │ + │ │ TITLE_GENERATION │ │ │ + │ ├───────────────────────────────────>│ │ + │ │ │ │ │ + │ │ │ │ 5. 创建 StateMachine │ + │ │ │ │ ChatContext │ + │ │ │ │ │ + │ │ │ │ 6. 状态流转: │ + │ │ │ │ IDLE → │ + │ │ │ │ BUILDING_PROMPT │ + │ │ │ │ │ + │ │ │ │ 7. BuildPromptAction │ + │ │ │ │ - 加载模板 │ + │ │ │ │ - 填充 SQL 内容 │ + │ │ │ │ │ + │ │ │ │ 8. 状态流转: │ + │ │ │ │ → STREAMING │ + │ │ │ │ │ + │ │ │ ├──────────────────>│ + │ │ │ │ 9. 调用 AI 模型 │ + │ │ │ │ (流式请求) │ + │ │ │ │<──────────────────┤ + │ │ │ │ 10. 流式响应 │ + │ │ │ │───────────────────> + │ │ │ │ │ + │ │<───────────────────────────────────│ │ + │ │ 11. SSE event: state │ │ + │ │ data: {"state":"STREAMING"} │ │ + │ │ │ │ │ + │ │<───────────────────────────────────│ │ + │ │ 12. SSE event: message (多次) │ │ + │ │ data: {"content":"..."} │ │ + │ │ │ │ │ + │ │ 13. 累积内容 │ │ │ + │ │ handleMessage │ │ │ + │ │ 更新 UI 显示加载中 │ │ │ + │ │ │ │ │ + │ │<───────────────────────────────────│ │ + │ │ 14. SSE event: [DONE] │ │ + │ │ (AI 响应完成,连接关闭) │ │ + │ │ │ │ │ + │ │ 15. 解析标题 │ │ │ + │ │ handleComplete │ │ │ + │ │ - 清理引号和空白 │ │ │ + │ │ - 调用 │ │ │ + │ │ historyService│ │ │ + │ │ .updateSavedConsole │ │ │ + │ │ │ │ │ + │ │ 16. 更新标题到 │ │ │ + │ │ indexedDB │ │ │ + │ ├─────────────────>│ │ │ + │ │ │ │ │ + │ │ 17. 清除生成中标记 │ │ │ + │ │ clearGeneratingTitleKey│ │ │ + │ │ │ │ │ + │<────────────────│ │ │ │ + │ 18. Tab 标题已更新 │ │ │ │ + │ │ │ │ │ +``` + +### 错误处理分支 + +``` + │ │<───────────────────────────────────│ │ + │ │ SSE event: error │ │ │ + │ │ data: {"error":"..."} │ │ + │ │ │ │ │ + │ │ handleError │ │ │ + │ │ message.error( │ │ │ + │ │ 'AI 生成标题失败' │ │ │ + │ │ ) │ │ │ + │ │ │ │ │ +``` + +## 3. 关键数据结构 + +### 3.1 前端请求参数 + +```typescript +interface AIChatParams { + message: string; // SQL 内容 + promptType: string; // 'TITLE_GENERATION' + dataSourceId?: number; // 数据源 ID + databaseName?: string; // 数据库名 + schemaName?: string; // Schema 名 +} +``` + +### 3.2 后端 SSE 响应格式 + +```sse +// 状态事件 +event: state +data: {"state":"STREAMING"} + +// 内容事件(流式返回,可能多次) +event: message +data: {"content":"查询"} + +event: message +data: {"content":"用户"} + +event: message +data: {"content":"订单"} + +// 完成事件(AI 响应结束,直接发送 [DONE]) +event: [DONE] +data: {"name":"set_title","arguments":{"title_name":"查询用户订单"}} + +// 错误事件 +event: error +data: {"error":"AI 调用失败:网络超时"} +``` + +### 3.3 状态机上下文 + +```java +public class ChatContext { + private String conversationId; // 会话 ID + private PromptType promptType; // TITLE_GENERATION + private String message; // SQL 内容 + private Long dataSourceId; // 数据源 ID + private String databaseName; // 数据库名 + private String schemaName; // Schema 名 + private List
    selectedTables; // 选中的表 + private String schemaDDL; // 表结构 DDL + private String generatedTitle; // 生成的标题 +} +``` + +## 4. 异常场景处理 + +| 场景 | 前端处理 | 后端状态 | +|------|---------|---------| +| AI 调用超时 | `message.error('AI 生成标题失败,请重试')` | FAILED | +| 用户关闭 Tab | 关闭 SSE 连接,发送取消请求 | FAILED (CANCEL 事件) | +| 网络中断 | 重连机制(可选) | FAILED | +| SQL 内容为空 | 提示用户先编写 SQL | 不发起请求 | +| AI 返回格式错误 | 正则提取标题,失败则使用默认标题 | 发送 [DONE],前端处理 | + +## 5. 代码位置参考 + +### 前端代码 + +| 文件路径 | 功能说明 | +|---------|---------| +| `chat2db-client/src/pages/main/workspace/components/WorkspaceTabs/index.tsx` | 核心实现:`handleGenerateTitle` 函数(第 161-256 行) | +| `chat2db-client/src/components/Tabs/index.tsx` | Tab 右键菜单中的"AI 生成标题"选项(第 215-228 行) | +| `chat2db-client/src/utils/eventSource.ts` | SSE 连接工具(`connectToEventSource`) | +| `chat2db-client/src/i18n/zh-cn/common.ts` | 国际化:`'common.text.generateTitle': 'AI 生成标题'` | +| `chat2db-client/src/i18n/zh-cn/workspace.ts` | 国际化:`'workspace.tips.generateTitleFailed': 'AI 生成标题失败,请重试'` | + +### 后端代码 + +| 文件路径 | 功能说明 | +|---------|---------| +| `chat2db-server/.../controller/ai/ChatController.java` | AI 聊天控制器:`/api/ai/chat` 接口 | +| `chat2db-server/.../statemachine/ChatStateMachineConfig.java` | 状态机配置:定义 AI 聊天的状态流转 | +| `chat2db-server/.../statemachine/ChatState.java` | 状态枚举(IDLE, STREAMING, COMPLETED, FAILED 等) | +| `chat2db-server/.../statemachine/ChatEvent.java` | 事件枚举(含 `REQUEST_GENERATE_TITLE`) | +| `chat2db-server/.../statemachine/actions/BuildPromptAction.java` | 构建 AI 提示词动作 | +| `chat2db-server/.../statemachine/actions/StreamAction.java` | AI 流式输出动作 | +| `chat2db-server/.../enums/PromptType.java` | 提示类型枚举(含 `TITLE_GENERATION`) | +| `chat2db-server/.../prompt/PromptBuilderImpl.java` | 提示词构建器实现 | +| `chat2db-server/.../prompt/PromptTemplateRegistry.java` | 提示词模板注册表 | +| `chat2db-server/.../resources/prompt-templates.yml` | 提示词模板配置文件 | +| `chat2db-server/.../config/AiChatConfig.java` | AI 客户端配置(OpenAI/Anthropic) | + +## 6. AI 提供商配置 + +### 支持的 AI 提供商 + +| 提供商 | 配置项前缀 | 默认模型 | +|--------|-----------|---------| +| OpenAI | `ai.openai.` | `gpt-4o-mini` | +| Anthropic | `ai.anthropic.` | `claude-3-5-sonnet-20241022` | + +### 可配置参数 + +- `apiKey` - API 密钥 +- `apiHost` - API 主机(仅 OpenAI) +- `model` - 模型名称 +- `temperature` - 温度(默认 0.7) +- `maxTokens` - 最大 token 数(默认 4096) + +## 7. 提示词模板配置 + +标题生成的提示词模板位于 `prompt-templates.yml`: + +```yaml +title_generation: + name: "title_generation" + description: "生成标题" + template: | + 请为以下 SQL 查询生成一个简洁的中文标题(不超过 20 字): + {message} + + 输出格式:"你的标题" +``` + +## 8. 扩展新 AI 功能 + +如需添加新的 AI 功能类型: + +1. 在 `PromptType.java` 添加新枚举 +2. 在 `ChatEvent.java` 添加对应事件 +3. 在 `prompt-templates.yml` 添加模板 +4. 在前端调用时传入新的 `promptType` + +--- + +**文档版本**: 1.0 +**最后更新**: 2026-04-11 +**维护者**: Chat2DB Team \ No newline at end of file diff --git a/docs/FIX_DUPLICATE_AND_SORT.md b/docs/FIX_DUPLICATE_AND_SORT.md new file mode 100644 index 000000000..0093cd342 --- /dev/null +++ b/docs/FIX_DUPLICATE_AND_SORT.md @@ -0,0 +1,264 @@ +# SQL 补全排序和重复问题修复 + +## 问题描述 + +### 问题 1: 字段提示重复 +``` +[SQL 补全] 过滤前字段数量: 3 +[SQL 补全] 过滤后字段数量: 3 +``` +同一个字段出现多次。 + +### 问题 2: 字段没有排在表名前面 +补全列表中表名排在字段前面,不符合使用习惯。 + +## 原因分析 + +### 重复原因 +`getFieldsFromStatement` 内部遍历所有表源时,可能多次添加同一个字段。 + +### 排序原因 +默认排序规则: +- 表名:`sortText: A*` +- 字段:`sortText: B*` +- 函数:`sortText: C*` +- 分组:`sortText: D*` +- 关键字:`sortText: W*` + +Monaco 按 `sortText` 字母顺序排序,A 在 B 前面,所以表名排在字段前面。 + +## 解决方案 + +### 1. 去重字段 + +**文件**: `index.ts` + +```typescript +// 表字段补全模式 +const cursorRootStatementFields = await reader.getFieldsFromStatement(...); + +// 去重字段(避免重复) +const uniqueFields = _.uniqBy(cursorRootStatementFields, 'label'); + +// 使用去重后的字段 +const result = uniqueFields + .concat(functionNames) + .concat(parserSuggestion) + .concat(groups...); +``` + +### 2. 调整排序 + +**目标**: 字段 > 函数 > 关键字 > 表名 + +**修改**: +```typescript +// default-opts.ts 和 sql-autocomplete.ts +onSuggestTableNames = () => { + return tables.map(table => ({ + label: table.name, + sortText: `Z${table.name}`, // Z 开头,排在最后 + })); +} +``` + +**排序规则**: +- `B*` - 字段 (最前) +- `C*` - 函数 +- `W*` - SQL 关键字 +- `Z*` - 表名 (最后) + +## 修改文件 + +### 1. index.ts +- 添加 `_.uniqBy` 去重 +- 调整合并顺序 +- 移除表名分组的重复添加 + +### 2. default-opts.ts +- 表名 `sortText` 从 `A*` 改为 `Z*` + +### 3. sql-autocomplete.ts +- 表名 `sortText` 从 `A*` 改为 `Z*` + +## 预期效果 + +### 补全顺序 +``` +t. +├─ option_value_id (字段) +├─ option_name (字段) +├─ created_at (字段) +├─ COUNT (函数) +├─ SELECT (关键字) +├─ FROM (关键字) +└─ WHERE (关键字) +``` + +**注意**: 表名不会出现在 `t.` 后面的补全中,因为这是表名限定字段模式。 + +### 无表名前缀时的补全 +```sql +SELECT * FROM | +``` +此时补全: +``` +├─ option_value_id (字段) +├─ option_name (字段) +├─ COUNT (函数) +├─ SELECT (关键字) +├─ base_option_name (表名) ← Z 开头,排在后面 +└─ other_table (表名) +``` + +## 测试验证 + +### 测试 1: 字段去重 + +```sql +select * from base_option_name t where t. +``` + +**预期**: 每个字段只出现一次 + +**日志**: +``` +[SQL 补全] 获取到字段数量: 3 +[SQL 补全] 去重后字段数量: 3 ← 无变化说明没有重复 +[SQL 补全] 过滤后字段数量: 3 +``` + +### 测试 2: 字段排在前面 + +**预期**: 字段在补全列表顶部 + +**检查**: +1. 打开补全列表 +2. 确认字段在最上面 +3. 表名在列表底部(如果有) + +### 测试 3: 多表 JOIN + +```sql +select * from table1 t1 join table2 t2 on t1.id = t2. +``` + +**预期**: +- 在 `t2.` 后只看到 table2 的字段 +- 字段不重复 +- 字段在最前面 + +## 性能优化 + +### 去重性能 +```typescript +_.uniqBy(array, 'label') +``` +时间复杂度: O(n) +空间复杂度: O(n) + +对于典型的表字段数(10-50 个),性能影响可忽略。 + +### 排序性能 +Monaco Editor 内部处理排序,性能优秀。 + +## 注意事项 + +### 1. 分组显示 +保留 `groupPickerName` 用于 Monaco 的分组显示功能: +```typescript +groups ? Object.keys(groups).map(groupName => + opts.onSuggestFieldGroup(groupName) +) +``` + +这会在补全列表中显示一个可展开的分组: +``` +t +├─ field1 +├─ field2 +└─ field3 +``` + +### 2. 字段上下文 +在不同上下文中,补全策略不同: + +**tableFieldAfterGroup** (`t.`): +- 只显示对应表的字段 +- 字段排在最前 +- 不显示表名 + +**tableField** (普通字段位置): +- 显示所有表的字段 +- 显示表名分组 +- 字段优先 + +## 调试日志 + +### 去重日志 +``` +[SQL 补全] 获取到字段数量: 6 +[SQL 补全] 去重后字段数量: 3 ← 说明有 3 个重复字段被去除 +``` + +### 排序日志 +``` +[SQL 补全] 最终补全项总数: 15 + - 字段:10 个 (sortText: B*) + - 函数:3 个 (sortText: C*) + - 关键字:2 个 (sortText: W*) + - 表名:5 个 (sortText: Z*) ← 排在最后 +``` + +## 常见问题 + +### Q1: 为什么还会看到重复字段? + +**A**: 检查是否: +1. 刷新了页面(确保新代码生效) +2. 清除了浏览器缓存 +3. 重启了开发服务器 + +### Q2: 表名仍然排在字段前面? + +**A**: 可能原因: +1. 修改未生效(需要刷新) +2. Monaco 缓存(清除缓存) +3. 其他地方的 `sortText` 未修改 + +检查文件: +- `default-opts.ts` - 默认实现 +- `sql-autocomplete.ts` - 实际使用 + +### Q3: 分组显示正常吗? + +**A**: 分组显示依赖 Monaco 的功能: +```typescript +{ + label: 'field', + groupPickerName: 't', // ← 关键 +} +``` + +Monaco 会自动将相同 `groupPickerName` 的字段分组。 + +## 相关文档 + +- 原理说明:`PRINCIPLE_AND_LOGS.md` +- 调试指南:`DEBUG_GUIDE.md` +- HMR 修复:`HMR_FIX.md` +- 实现说明:`README.md` + +## 总结 + +**问题**: 字段重复 + 排序错误 +**修复**: +1. ✅ 使用 `_.uniqBy` 去重 +2. ✅ 调整 `sortText` 排序 +3. ✅ 优化合并顺序 + +**效果**: +- ✅ 字段不重复 +- ✅ 字段排在最前面 +- ✅ 表名排在最后 +- ✅ 分组显示正常 diff --git a/docs/IMPLEMENTATION_SUMMARY.md b/docs/IMPLEMENTATION_SUMMARY.md new file mode 100644 index 000000000..34c9ec7ea --- /dev/null +++ b/docs/IMPLEMENTATION_SUMMARY.md @@ -0,0 +1,270 @@ +# SQL 智能补全实现总结 + +## 问题描述 + +原问题:`select * from \`AKMG_APP\` t where t.` 只能补全表名,不能补全字段名。 + +**根本原因:** +1. 原有的 IntelliSense 系统只支持简单的上下文判断,不支持表别名补全 +2. syntax-parser 系统虽然支持表别名补全,但使用的是硬编码的假数据 + +## 解决方案 + +采用**方案二:启用 syntax-parser 的真实数据补全** + +### 实现思路 + +1. 利用 syntax-parser 的 SQL 语法解析能力,正确识别表别名和字段的上下文关系 +2. 实现真实的 `onSuggestTableFields` 回调,调用后端 API 获取字段列表 +3. 在 MonacoEditor 组件中初始化 SQL 智能补全 +4. 通过 boundInfo 传递数据库连接信息 + +## 修改文件清单 + +### 新增文件 + +1. **src/components/MonacoEditor/syntax-parser/plugin/monaco-plugin/sql-autocomplete.ts** + - 核心实现文件 + - 192 行代码 + - 实现了完整的 SQL 智能补全功能 + +2. **src/components/MonacoEditor/syntax-parser/plugin/monaco-plugin/README.md** + - 功能说明文档 + - 包含技术实现、使用场景、性能优化等 + +3. **src/components/MonacoEditor/syntax-parser/plugin/monaco-plugin/TEST.md** + - 测试用例文档 + - 包含详细的测试步骤和预期结果 + +### 修改文件 + +1. **src/components/MonacoEditor/index.tsx** + - 添加 `boundInfo?: IBoundInfo` 属性 + - 添加 `sqlAutocompleteDisposable` 引用 + - 初始化 SQL 智能补全 + - 在 boundInfo 变化时重新初始化 + +2. **src/components/ConsoleEditor/index.tsx** + - 传递 `boundInfo` 参数给 MonacoEditor + +## 核心功能 + +### 1. 表别名补全(核心功能) + +```typescript +// 当用户输入 "select * from AKMG_APP t where t." 时 +// syntax-parser 解析 SQL AST,识别出: +// - t 是 AKMG_APP 的别名 +// - 光标在表别名后面,需要补全字段 +// - 调用 onSuggestTableFields 获取 AKMG_APP 的字段列表 + +onSuggestTableFields: async (tableInfo, cursorValue, rootStatement) => { + const tableName = tableInfo?.tableName?.value; + + // 调用后端 API 获取字段 + const data = await sqlService.getColumnList({ + dataSourceId: boundInfo.dataSourceId, + databaseName: boundInfo.databaseName, + schemaName: boundInfo.schemaName, + tableName, + }); + + // 返回补全项 + return data.map(column => ({ + label: column.name, + insertText: column.name, + kind: monaco.languages.CompletionItemKind.Field, + detail: column.columnType, + documentation: column.comment, + })); +} +``` + +### 2. 表名补全 + +```typescript +onSuggestTableNames: async (cursorInfo) => { + const data = await sqlService.getAllTableList({ + dataSourceId: boundInfo.dataSourceId, + databaseName: boundInfo.databaseName, + schemaName: boundInfo.schemaName, + }); + + return data.map(table => ({ + label: table.name, + insertText: table.name, + kind: monaco.languages.CompletionItemKind.Folder, + detail: table.comment, + })); +} +``` + +### 3. SQL 关键字补全 + +提供常用的 SQL 关键字和函数补全。 + +### 4. Hover 提示 + +鼠标悬停时显示字段和表的详细信息。 + +## 技术亮点 + +### 1. 基于 AST 的上下文识别 + +使用 syntax-parser 解析 SQL 生成 AST(抽象语法树),准确识别: +- 光标位置 +- 上下文类型(表名/字段/关键字) +- 表别名映射关系 + +### 2. 异步数据加载 + +使用 async/await 处理异步 API 调用,保证补全数据的实时性。 + +### 3. 错误处理 + +完善的错误处理机制,API 失败不影响编辑器使用。 + +### 4. 生命周期管理 + +在组件卸载时正确清理补全注册,避免内存泄漏。 + +## 使用示例 + +### 示例 1:单表查询 + +```sql +select * from AKMG_APP t where t.id = 1 +-- ^ 输入 t. 后自动补全 AKMG_APP 的字段 +``` + +### 示例 2:多表 JOIN + +```sql +select u.username, o.amount +from users u +join orders o on u.id = o.user_id +-- ^ 输入 o. 后补全 orders 表的字段 +``` + +### 示例 3:复杂查询 + +```sql +select t1.name, t2.count +from table1 t1 +left join table2 t2 on t1.id = t2.table1_id +where t1.status = 1 + and t2. -- 输入 t2. 后补全 table2 的字段 +order by t1.created_at desc +``` + +## 对比分析 + +| 特性 | 原 IntelliSense | 新 syntax-parser | +|------|----------------|------------------| +| 表别名补全 | ❌ 不支持 | ✅ 完全支持 | +| 上下文理解 | ⚠️ 简单正则 | ✅ AST 语法树 | +| 多表支持 | ❌ 有限 | ✅ 完整支持 | +| 数据来源 | ✅ 后端 API | ✅ 后端 API | +| 响应速度 | ✅ 快 | ⚠️ 需解析 SQL | +| 准确性 | ⚠️ 一般 | ✅ 高 | + +## 兼容性 + +- ✅ 支持所有已配置的数据库类型(MySQL, PostgreSQL, Oracle 等) +- ✅ 兼容现有的 IntelliSense 系统(可共存) +- ✅ 向后兼容,不影响现有功能 + +## 性能考虑 + +### 当前实现 + +- 每次 boundInfo 变化时重新初始化 +- 每次补全时调用后端 API(无缓存) + +### 优化建议 + +1. **缓存机制** + ```typescript + const fieldCache = new Map(); + + // 使用前检查缓存 + const cacheKey = `${dataSourceId}_${databaseName}_${schemaName}_${tableName}`; + if (fieldCache.has(cacheKey)) { + return fieldCache.get(cacheKey); + } + ``` + +2. **防抖处理** + ```typescript + // 使用 lodash debounce + const debouncedSuggest = debounce(async (tableInfo) => { + // 补全逻辑 + }, 300); + ``` + +3. **懒加载** + - 只在需要时获取字段 + - 预加载常用表的字段 + +## 测试验证 + +### 测试环境 + +- 前端:`yarn run start` +- 后端:`mvn spring-boot:run` + +### 测试步骤 + +1. 打开 SQL 编辑器 +2. 选择数据源和数据库 +3. 输入:`select * from 表名 t where t.` +4. 验证字段补全列表 + +详细测试用例见 `TEST.md` + +## 未来改进方向 + +1. **智能缓存** + - 表结构缓存 + - 增量更新 + +2. **个性化排序** + - 根据使用频率 + - 根据上下文相关性 + +3. **性能优化** + - Web Worker 解析 SQL + - 懒加载和预加载 + +4. **功能增强** + - 支持存储过程 + - 支持视图 + - 支持索引 + +## 相关文档 + +- 实现文档:`README.md` +- 测试文档:`TEST.md` +- 原理解析:`src/components/MonacoEditor/syntax-parser/plugin/monaco-plugin/index.ts` + +## 总结 + +本次实现成功解决了 `select * from \`AKMG_APP\` t where t.` 无法补全字段名的问题。 + +**关键成果:** +1. ✅ 实现了基于 syntax-parser 的真实数据补全 +2. ✅ 支持表别名补全(核心需求) +3. ✅ 支持多表 JOIN 场景 +4. ✅ 完整的错误处理和生命周期管理 +5. ✅ 向后兼容,不影响现有功能 + +**代码统计:** +- 新增代码:~200 行 +- 修改代码:~20 行 +- 新增文件:3 个 +- 修改文件:2 个 + +**技术价值:** +- 提升了 SQL 编辑器的用户体验 +- 建立了可扩展的智能补全架构 +- 为后续优化奠定了基础 diff --git a/docs/INDEX.md b/docs/INDEX.md new file mode 100644 index 000000000..4355ba399 --- /dev/null +++ b/docs/INDEX.md @@ -0,0 +1,89 @@ +# SQL 智能补全文档索引 + +本目录包含 Chat2DB SQL 智能补全功能的完整文档。 + +## 核心文档 + +### 1. 实现说明 +- **README.md** - SQL 智能补全功能实现说明 + - 实现方案 + - 修改文件清单 + - 功能特性 + - 使用场景 + +### 2. 原理与调试 +- **PRINCIPLE_AND_LOGS.md** - SQL 补全原理和日志说明 + - 整体架构 + - 核心流程详解 + - AST 解析流程 + - 表别名识别机制 + - 完整流程时序图 + - 调试日志示例 + +### 3. 调试指南 +- **DEBUG_GUIDE.md** - 调试日志使用指南 + - 快速开始 + - 日志级别说明 + - 常见场景日志分析 + - 日志过滤技巧 + - 性能分析 + +### 4. 测试用例 +- **TEST.md** - 测试用例文档 + - 测试环境准备 + - 测试步骤 + - 测试数据准备 + - 测试 SQL 示例 + - 问题排查 + +### 5. 问题修复 +- **FIX_DUPLICATE_AND_SORT.md** - 字段重复和排序问题修复 + - 问题描述 + - 原因分析 + - 解决方案 + - 修改文件 + +## 快速开始 + +### 使用 SQL 智能补全 + +1. 打开 SQL 编辑器 +2. 选择数据源和数据库 +3. 输入 SQL,例如: + ```sql + select * from table_name t where t. + ``` +4. 自动触发字段补全 + +### 查看调试日志 + +打开浏览器 Console,查看以 `[SQL 补全]` 开头的日志。 + +## 文档位置 + +- **前端文档**: `chat2db-client/src/components/MonacoEditor/docs/` +- **项目文档**: `docs/` + +## 相关代码位置 + +- **核心实现**: `sql-autocomplete.ts` +- **Monaco 插件**: `monaco-plugin/index.ts` +- **语法解析**: `sql-parser/` +- **语义分析**: `sql-parser/base/reader.ts` + +## 版本信息 + +- 实现日期:2024 +- Monaco Editor 版本:0.15.6 +- 支持的数据库:MySQL, PostgreSQL, Oracle, SQL Server 等 + +## 维护说明 + +如需修改补全逻辑,请参考: +1. `PRINCIPLE_AND_LOGS.md` - 了解整体架构 +2. `DEBUG_GUIDE.md` - 使用日志调试 +3. `TEST.md` - 运行测试验证 + +## 常见问题 + +详见各文档中的"常见问题"章节。 diff --git a/docs/NL_2_COMMENT_REFACTORING_PLAN.md b/docs/NL_2_COMMENT_REFACTORING_PLAN.md new file mode 100644 index 000000000..9b649f5ae --- /dev/null +++ b/docs/NL_2_COMMENT_REFACTORING_PLAN.md @@ -0,0 +1,1655 @@ +# NL_2_COMMENT(猜一猜)功能重构方案 + +> 版本:v1.0 +> 日期:2026-04-12 +> 状态:待实施 + +--- + +## 一、功能概述 + +### 1.1 功能定义 + +**NL_2_COMMENT** 是 Chat2DB 的"猜一猜"功能,用于 AI 自动生成表和字段的中文注释,帮助用户快速理解数据库结构。 + +### 1.2 当前问题 + +| 问题 | 严重程度 | 影响 | +|------|----------|------| +| **Function Call 机制缺失** | 🔴 高 | 前端期望的 `set_table_comment`/`set_column_comment` 无法触发,注释无法自动更新 | +| **前端重复实现** | 🔴 高 | DatabaseTableEditor 没有使用 AiChat 组件,独立实现 EventSource 逻辑 | +| **类型定义分散** | 🟡 中 | DatabaseTableEditor 本地定义 IPromptType,与后端不一致 | +| **后端分类混乱** | 🟡 中 | NL_2_COMMENT 被归类为 `skipAutoSelect`,但 schema 获取逻辑不一致 | + +--- + +## 二、当前架构分析 + +### 2.1 前端架构 + +#### 两个独立的 AI 功能入口 + +**1. AiChat 组件** (`src/components/AiChat/index.tsx`) + +- 通用 AI 聊天界面 +- 支持状态机状态显示(选表、获取 schema、构建 prompt、流式输出) +- 通过 `pendingAiChat` 机制从其他组件触发 +- 支持的 PromptType:`NL_2_SQL` | `SQL_EXPLAIN` | `SQL_OPTIMIZER` | `SQL_2_SQL` +- **没有 tool_calls 处理逻辑** + +**2. DatabaseTableEditor 内置 guess()** (`src/blocks/DatabaseTableEditor/index.tsx`) + +- 编辑表时的"猜一猜"按钮 +- 独立实现 EventSource 连接 +- 有 `onCallback` 期望处理 `set_table_comment` 和 `set_column_comment` +- **但后端不支持 Function Call,此逻辑永不触发** + +#### 相关文件 + +``` +前端文件清单: +├── src/components/AiChat/index.tsx # AI 聊天组件(通用) +├── src/blocks/DatabaseTableEditor/index.tsx # 表编辑器 + 独立 guess() +├── src/pages/main/workspace/store/common.ts # IAiChatPromptType 类型定义 +├── src/pages/main/workspace/store/aiChatStore.ts # AI Chat 状态管理 +├── src/utils/eventSource.ts # SSE 连接工具 +└── src/service/sql.ts # API 服务层 +``` + +### 2.2 后端架构 + +#### 状态机流程 + +```mermaid +stateDiagram-v2 + [*] --> IDLE + + note right of IDLE + NL_2_COMMENT 触发时: + - 有表名:TABLES_PROVIDED + - 无表名:skipAutoSelect=true + end note + + IDLE --> FETCHING_TABLE_SCHEMA: TABLES_PROVIDED + IDLE --> AUTO_SELECTING_TABLES: TABLES_NOT_PROVIDED + + state 自动选表子流程 { + AUTO_SELECTING_TABLES --> FETCHING_TABLE_SCHEMA: AUTO_SELECT_DONE + } + + state 核心处理流程 { + FETCHING_TABLE_SCHEMA --> BUILDING_PROMPT: SCHEMA_FETCHED + BUILDING_PROMPT --> STREAMING: PROMPT_BUILT + STREAMING --> COMPLETED: STREAM_FINISHED + } + + AUTO_SELECTING_TABLES --> FAILED: AUTO_SELECT_FAILED + FETCHING_TABLE_SCHEMA --> FAILED: FETCH_SCHEMA_FAILED + BUILDING_PROMPT --> FAILED: PROMPT_BUILD_FAILED + STREAMING --> FAILED: AI_CALL_FAILED + + COMPLETED --> [*] + FAILED --> [*] +``` + +#### 关键代码分析 + +**1. PromptType.java** - 枚举定义 +```java +NL_2_COMMENT("猜测表和字段注释") +``` + +**2. ChatController.java** - 初始事件决策 +```java +private ChatEvent determineInitialEvent(ChatQueryRequest request) { + boolean skipAutoSelect = PromptType.NL_2_COMMENT.getCode().equals(request.getPromptType()) + || PromptType.TITLE_GENERATION.getCode().equals(request.getPromptType()) + || PromptType.TEXT_GENERATION.getCode().equals(request.getPromptType()); + return (hasTables || skipAutoSelect) ? ChatEvent.TABLES_PROVIDED : ChatEvent.TABLES_NOT_PROVIDED; +} +``` + +**3. FetchSchemaAction.java** - Schema 获取逻辑 +```java +private boolean isTextGeneration(ChatQueryRequest request) { + return PromptType.TEXT_GENERATION.getCode().equals(request.getPromptType()) + || PromptType.TITLE_GENERATION.getCode().equals(request.getPromptType()); +} +// 注意:NL_2_COMMENT 不在 isTextGeneration 中,会正常获取 schema +``` + +**4. prompt-templates.yml** - 提示词模板 +```yaml +nl_2_comment: + name: "nl_2_comment" + description: "猜测表和字段注释" + template: | + ### 请根据以下 table properties 和 SQL input{description}. {ext} + # + ### {db_type} SQL tables, with their properties: + # + # {schema} + # + ### 猜测表和字段注释 +``` + +#### 相关文件 + +``` +后端文件清单: +├── chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ +│ ├── ChatController.java # 聊天入口 +│ ├── enums/PromptType.java # Prompt 类型枚举 +│ ├── request/ChatQueryRequest.java # 请求参数 +│ ├── prompt/ +│ │ ├── PromptTemplate.java # 模板值对象 +│ │ ├── PromptContext.java # 构建上下文 +│ │ ├── PromptBuilderImpl.java # 提示词构建器 +│ │ └── PromptTemplateRegistry.java # 模板注册表 +│ └── statemachine/ +│ ├── ChatState.java # 状态枚举 +│ ├── ChatEvent.java # 事件枚举 +│ ├── ChatContext.java # 状态机上下文 +│ ├── ChatStateMachineConfig.java # 状态机配置 +│ └── actions/ +│ ├── BaseChatAction.java # Action 基类 +│ ├── SelectTablesAction.java # 自动选表 +│ ├── FetchSchemaAction.java # 获取 schema +│ ├── BuildPromptAction.java # 构建 prompt +│ └── StreamAction.java # 流式输出 +├── chat2db-server-web-api/src/main/resources/ +│ └── prompt-templates.yml # 提示词模板配置 +└── chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ + ├── TableService.java # 表服务接口 + └── DatabaseService.java # 数据库服务接口 +``` + +--- + +### 2.3 状态机优化(已实施) + +#### 问题分析 + +原有的状态机初始事件决策逻辑存在问题: + +- `NL_2_COMMENT` 被归类为 `skipAutoSelect`(与 `TEXT_GENERATION`、`TITLE_GENERATION` 混在一起) +- `TEXT_GENERATION` 和 `TITLE_GENERATION` 真正不需要表信息,却仍会进入 `FETCHING_TABLE_SCHEMA` 状态 +- `FetchSchemaAction` 中有 `isTextGeneration` 判断来处理不需要 schema 的情况,增加了复杂度 + +#### 优化方案 + +新增 `TABLES_NOT_NEEDED` 事件,让 `TEXT_GENERATION` 和 `TITLE_GENERATION` 直接跳过选表和获取 schema 的流程: + +```mermaid +stateDiagram-v2 + [*] --> IDLE + + note right of IDLE + 根据 PromptType 和 tables 参数决定初始事件: + - TEXT_GENERATION/TITLE_GENERATION: TABLES_NOT_NEEDED + - NL_2_COMMENT + tables有效: TABLES_PROVIDED + - NL_2_COMMENT + tables无效: TABLES_NOT_PROVIDED + - 其他类型: 同上 + end note + + IDLE --> BUILDING_PROMPT: TABLES_NOT_NEEDED + IDLE --> FETCHING_TABLE_SCHEMA: TABLES_PROVIDED + IDLE --> AUTO_SELECTING_TABLES: TABLES_NOT_PROVIDED +``` + +#### 已完成的修改 + +| 文件 | 修改内容 | +|------|----------| +| `ChatEvent.java` | 新增 `TABLES_NOT_NEEDED` 事件 | +| `ChatStateMachineConfig.java` | 新增 `IDLE -> BUILDING_PROMPT` 状态转换 | +| `ChatController.java` | 重构 `determineInitialEvent` 方法 | +| `FetchSchemaAction.java` | 移除 `isTextGeneration` 判断 | + +#### 修改后的代码 + +**ChatController.java - determineInitialEvent 方法:** + +```java +private ChatEvent determineInitialEvent(ChatQueryRequest request) { + boolean hasTables = CollectionUtils.isNotEmpty(request.getTableNames()); + + String promptType = request.getPromptType(); + + // TEXT_GENERATION 和 TITLE_GENERATION 不需要表信息 + if (PromptType.TEXT_GENERATION.getCode().equals(promptType) + || PromptType.TITLE_GENERATION.getCode().equals(promptType)) { + return ChatEvent.TABLES_NOT_NEEDED; + } + + // NL_2_COMMENT 需要表结构信息 + if (PromptType.NL_2_COMMENT.getCode().equals(promptType)) { + return hasTables ? ChatEvent.TABLES_PROVIDED : ChatEvent.TABLES_NOT_PROVIDED; + } + + // 其他类型根据是否有表名决定 + return hasTables ? ChatEvent.TABLES_PROVIDED : ChatEvent.TABLES_NOT_PROVIDED; +} +``` + +**FetchSchemaAction.java - 简化后的逻辑:** + +```java +private String fetchSchemaDdl(ChatContext ctx) { + ChatQueryRequest request = ctx.getRequest(); + + // TEXT_GENERATION/TITLE_GENERATION 已经不会进入此状态了 + if (hasTableNames(request)) { + return queryTablesWithNames(request); + } else { + return queryAllTables(request); + } +} +``` + +--- + +## 三、重构方案详解(修订版) + +### 方案选择:结构化 JSON 输出 + 用户确认流程 + +#### 方案优势 + +✅ **不需要 Function Call** - 后端改动最小 +✅ **用户先查看再确认** - 更安全,避免误操作 +✅ **与现有流程一致** - 使用 `/modify/sql` + `updateAiComment` +✅ **前端改动小** - 只需解析 JSON 并调用 `setTableComment`/`setColumnComment` + +#### 流程说明 + +```mermaid +sequenceDiagram + participant User as 用户 + participant Editor as DatabaseTableEditor + participant AiChat as AiChat 组件 + participant Backend as 后端 AI + participant Database as 数据库 + + User->>Editor: 点击"猜一猜"按钮 + Editor->>AiChat: setPendingAiChat(NL_2_COMMENT) + AiChat->>Backend: SSE 请求 /api/ai/chat + Backend->>Backend: 获取表结构 Schema + Backend->>Backend: 构建 Prompt + Backend->>AiChat: 流式返回 JSON 内容 + AiChat->>AiChat: 解析 JSON (table_comment + column_comments) + AiChat->>Editor: onCommentGenerated 回调 + Editor->>Editor: setTableComment / setColumnComment + Note over Editor: 用户查看 AI 推荐的注释 + User->>Editor: 点击"保存" + Editor->>Backend: POST /api/rdb/table/modify/sql + Backend->>Backend: updateAiComment (更新 Lucene 索引) + Backend->>Editor: 返回 ALTER TABLE SQL + Editor->>User: 显示 SQL Preview Modal + User->>Editor: 确认执行 + Backend->>Database: 执行 SQL + Database-->>Editor: 执行成功 + Editor->>Editor: 刷新表结构 +``` + +--- + +## 四、已完成的修改 + +### 4.1 状态机优化 + +新增 `TABLES_NOT_NEEDED` 事件,优化初始路由决策: + +| PromptType | 初始事件 | 流程 | +|------------|----------|------| +| `TEXT_GENERATION` / `TITLE_GENERATION` | `TABLES_NOT_NEEDED` | 直接构建 Prompt | +| `NL_2_COMMENT` + 有表名 | `TABLES_PROVIDED` | 获取 Schema → 构建 Prompt | +| `NL_2_COMMENT` + 无表名 | `TABLES_NOT_PROVIDED` | 自动选表 → 获取 Schema → 构建 Prompt | + +### 4.2 Prompt 模板修改 + +**文件:** `prompt-templates.yml` + +修改 `NL_2_COMMENT` 模板,要求 AI 返回 JSON 格式: + +```yaml +nl_2_comment: + template: | + ### 任务:为表和字段生成合适的中文注释 + + **输出格式要求(严格遵守 JSON 格式):** + + ```json + { + "table_comment": "表的注释内容", + "column_comments": [ + { + "column_name": "字段名1", + "comment": "字段1的注释内容" + } + ] + } + ``` + + 只输出 JSON 内容,不要包含其他解释文字 +``` + +### 4.3 前端修改 + +#### 修改的文件 + +| 文件 | 修改内容 | +|------|----------| +| `common.ts` | 扩展 `IAiChatPromptType` 添加 `NL_2_COMMENT`,新增 `ITableCommentResult` 接口,`IPendingAiChat` 添加 `onCommentGenerated` 回调 | +| `AiChat/index.tsx` | 在 COMPLETED 状态解析 JSON,调用 `onCommentGenerated` 回调 | +| `DatabaseTableEditor/index.tsx` | 删除本地 guess() 函数,改用 pendingAiChat 机制触发 AiChat | + +#### 关键代码片段 + +**common.ts - 类型定义:** + +```typescript +export type IAiChatPromptType = 'NL_2_SQL' | 'SQL_EXPLAIN' | 'SQL_OPTIMIZER' | 'SQL_2_SQL' | 'NL_2_COMMENT'; + +export interface ITableCommentResult { + table_comment: string; + column_comments: IColumnComment[]; +} + +export interface IPendingAiChat { + dataSourceId: number; + databaseName?: string; + schemaName?: string | null; + message: string; + promptType: IAiChatPromptType; + onCommentGenerated?: (result: ITableCommentResult) => void; +} +``` + +**AiChat/index.tsx - JSON 解析和回调:** + +```typescript +function extractJsonFromContent(content: string): ITableCommentResult | null { + const jsonMatch = content.match(/\{[\s\S]*"table_comment"[\s\S]*"column_comments"[\s\S]*\}/); + if (jsonMatch) { + return JSON.parse(jsonMatch[0]); + } + return null; +} + +// 在 onDone 中: +if (promptType === 'NL_2_COMMENT' && commentCallbackRef.current) { + const jsonContent = extractJsonFromContent(session.currentContent); + if (jsonContent) { + commentCallbackRef.current(jsonContent); + } +} +``` + +**DatabaseTableEditor/index.tsx - 使用 AiChat:** + +```typescript +const handleCommentGenerated = useCallback((result: ITableCommentResult) => { + if (result.table_comment) { + baseInfoRef.current?.setTableComment(result.table_comment); + } + if (result.column_comments) { + result.column_comments.forEach((col) => { + columnListRef.current?.setColumnComment(col.column_name, col.comment); + }); + } +}, []); + +const openAiChatForGuess = useCallback(() => { + setPendingAiChat({ + dataSourceId, + databaseName, + schemaName, + message: `请为表 ${tableName} 及其所有字段生成合适的中文注释`, + promptType: 'NL_2_COMMENT', + onCommentGenerated: handleCommentGenerated, + }); + setCurrentWorkspaceExtend('ai'); +}, [dataSourceId, databaseName, schemaName, tableName]); +``` + +--- + +## 五、预期效果 + +### 功能层面 + +✅ **猜一猜功能正常工作** - AI 生成 JSON 格式注释,前端解析并应用 +✅ **用户先查看再确认** - 注释先显示在表单中,用户确认后再保存 +✅ **前后端统一** - AiChat 成为唯一的 AI 交互入口 +✅ **与现有流程一致** - 使用 `/modify/sql` + `updateAiComment` + +### 技术层面 + +✅ **代码简洁** - 删除了 DatabaseTableEditor 中重复的 EventSource 逻辑 +✅ **架构清晰** - 使用 pendingAiChat 机制统一触发 AI 功能 +✅ **易于维护** - 单一入口,统一管理 + +### 方案选择:Function Call 机制(推荐) + +#### 方案优势 + +✅ **符合 AI 原生设计理念** - OpenAI、Spring AI 均支持 Function Calling +✅ **结构化数据传递** - 避免文本解析错误 +✅ **易于扩展** - 可轻松添加更多 AI 工具函数 +✅ **技术成熟** - Spring AI 已完整支持 Function Calling +✅ **前后端解耦** - 前端只需处理 SSE 事件,后端负责执行 + +--- + +## 四、详细实施步骤 + +### 第一阶段:后端实现(Prioritized) + +#### Step 1:创建 Function Call 定义类 + +**文件:** `chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/function/CommentFunctionDefinitions.java` + +```java +package ai.chat2db.server.web.api.controller.ai.function; + +import org.springframework.ai.tool.ToolCallbackProvider; +import org.springframework.ai.tool.definition.ToolDefinition; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Map; + +/** + * 注释更新相关的 Function Call 定义 + * + * 用于 AI 模型调用,实现自动更新表/列注释 + */ +@Component +public class CommentFunctionDefinitions implements ToolCallbackProvider { + + @Override + public List getToolDefinitions() { + return List.of( + // 设置表注释 + ToolDefinition.builder() + .name("set_table_comment") + .description("设置表的注释。参数:table_name(表名)、comment(注释内容)") + .inputSchema(Map.of( + "type", "object", + "properties", Map.of( + "table_name", Map.of( + "type", "string", + "description", "要设置注释的表名" + ), + "comment", Map.of( + "type", "string", + "description", "注释内容,简洁准确的中文描述" + ) + ), + "required", List.of("table_name", "comment") + )) + .build(), + + // 设置列注释 + ToolDefinition.builder() + .name("set_column_comment") + .description("设置列的注释。参数:table_name(表名)、column_name(列名)、comment(注释内容)") + .inputSchema(Map.of( + "type", "object", + "properties", Map.of( + "table_name", Map.of( + "type", "string", + "description", "所属表名" + ), + "column_name", Map.of( + "type", "string", + "description", "要设置注释的列名" + ), + "comment", Map.of( + "type", "string", + "description", "注释内容,简洁准确的中文描述" + ) + ), + "required", List.of("table_name", "column_name", "comment") + )) + .build() + ); + } +} +``` + +#### Step 2:创建注释更新服务实现 + +**文件:** `chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/CommentUpdateServiceImpl.java` + +```java +package ai.chat2db.server.domain.core.impl; + +import org.springframework.ai.tool.annotation.Tool; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import ai.chat2db.server.domain.api.param.CommentUpdateParam; +import ai.chat2db.server.domain.api.service.TableService; +import lombok.extern.slf4j.Slf4j; + +/** + * 注释更新服务实现 + * + * 提供 AI Function Call 调用的工具方法 + * Spring AI 会自动调用这些方法来执行 Function + */ +@Service +@Slf4j +public class CommentUpdateServiceImpl { + + @Autowired + private TableService tableService; + + /** + * 设置表注释(AI 工具函数) + * + * Spring AI 会在 AI 模型返回 set_table_comment 调用时自动执行此方法 + * + * @param tableName 表名 + * @param comment 注释内容 + */ + @Tool(description = "设置表的注释") + public void setTableComment(String tableName, String comment) { + log.info("[AI Tool] Setting table comment: table={}, comment={}", tableName, comment); + + CommentUpdateParam param = CommentUpdateParam.builder() + .tableName(tableName) + .comment(comment) + .build(); + + tableService.updateTableComment(param); + + log.info("[AI Tool] Table comment updated successfully: table={}", tableName); + } + + /** + * 设置列注释(AI 工具函数) + * + * Spring AI 会在 AI 模型返回 set_column_comment 调用时自动执行此方法 + * + * @param tableName 所属表名 + * @param columnName 列名 + * @param comment 注释内容 + */ + @Tool(description = "设置列的注释") + public void setColumnComment(String tableName, String columnName, String comment) { + log.info("[AI Tool] Setting column comment: table={}, column={}, comment={}", + tableName, columnName, comment); + + CommentUpdateParam param = CommentUpdateParam.builder() + .tableName(tableName) + .columnName(columnName) + .comment(comment) + .build(); + + tableService.updateColumnComment(param); + + log.info("[AI Tool] Column comment updated successfully: table={}, column={}", + tableName, columnName); + } +} +``` + +#### Step 3:扩展 TableService 接口 + +**文件:** `chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TableService.java` + +```java +// 在现有接口中添加以下方法 + +/** + * 更新表注释 + * + * @param param 更新参数,包含 tableName、comment + */ +void updateTableComment(CommentUpdateParam param); + +/** + * 更新列注释 + * + * @param param 更新参数,包含 tableName、columnName、comment + */ +void updateColumnComment(CommentUpdateParam param); +``` + +#### Step 4:创建参数类 + +**文件:** `chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/CommentUpdateParam.java` + +```java +package ai.chat2db.server.domain.api.param; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 注释更新参数 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CommentUpdateParam { + + /** + * 表名 + */ + private String tableName; + + /** + * 列名(更新列注释时必填) + */ + private String columnName; + + /** + * 注释内容 + */ + private String comment; + + /** + * 数据源ID(可选,从上下文获取) + */ + private Long dataSourceId; + + /** + * 数据库名(可选,从上下文获取) + */ + private String databaseName; + + /** + * Schema名(可选,从上下文获取) + */ + private String schemaName; +} +``` + +#### Step 5:实现 TableService 方法 + +**文件:** `chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java` + +```java +// 在现有实现类中添加以下方法 + +@Autowired +private TableMapper tableMapper; // 或其他数据库访问层 + +@Override +public void updateTableComment(CommentUpdateParam param) { + // 构建更新表注释的 SQL + String sql = buildUpdateTableCommentSql(param); + + // 执行 SQL + executeUpdateSql(sql); +} + +@Override +public void updateColumnComment(CommentUpdateParam param) { + // 构建更新列注释的 SQL + String sql = buildUpdateColumnCommentSql(param); + + // 执行 SQL + executeUpdateSql(sql); +} + +private String buildUpdateTableCommentSql(CommentUpdateParam param) { + // 根据数据库类型生成不同的 SQL + // MySQL: ALTER TABLE table_name COMMENT = 'comment'; + // PostgreSQL: COMMENT ON TABLE table_name IS 'comment'; + // ... + return String.format("ALTER TABLE %s COMMENT = '%s'", + param.getTableName(), + escapeComment(param.getComment())); +} + +private String buildUpdateColumnCommentSql(CommentUpdateParam param) { + // 根据数据库类型生成不同的 SQL + // MySQL: ALTER TABLE table_name MODIFY COLUMN column_name ... COMMENT 'comment'; + // PostgreSQL: COMMENT ON COLUMN table_name.column_name IS 'comment'; + // ... + return String.format("ALTER TABLE %s MODIFY COLUMN %s ... COMMENT '%s'", + param.getTableName(), + param.getColumnName(), + escapeComment(param.getComment())); +} + +private String escapeComment(String comment) { + // 转义注释中的特殊字符 + return comment.replace("'", "\\'"); +} +``` + +#### Step 6:修改 ChatController 注册 Function + +**文件:** `chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java` + +```java +@RestController +@RequestMapping("/api/ai") +@Slf4j +public class ChatController { + + @Autowired + private StateMachineFactory stateMachineFactory; + + @Autowired + private AiChatConfig aiChatConfig; + + @Autowired + private CommentUpdateServiceImpl commentUpdateService; // 新增依赖 + + @GetMapping("/chat") + public SseEmitter chat(ChatQueryRequest queryRequest, @RequestHeader Map headers) + throws IOException { + String uid = headers.get("uid"); + if (StrUtil.isBlank(uid)) { + throw new ParamBusinessException("uid"); + } + + log.info("[ChatController] Received uid from client: {}", uid); + SseEmitter sseEmitter = new SseEmitter(CHAT_TIMEOUT); + + ChatClient chatClient = aiChatConfig.createChatClient(); + + // 如果是 NL_2_COMMENT,启用 Function Call + if (PromptType.NL_2_COMMENT.getCode().equals(queryRequest.getPromptType())) { + log.info("[ChatController] NL_2_COMMENT request, registering function tools"); + chatClient = chatClient.mutate() + .tools(commentUpdateService) // 注册工具函数 + .build(); + } + + LoginUser loginUser = ContextUtils.getLoginUser(); + ConnectInfo connectInfo = Chat2DBContext.getConnectInfo().copy(); + + ChatContext ctx = ChatContext.builder() + .uid(uid) + .request(queryRequest) + .sseEmitter(sseEmitter) + .chatClient(chatClient) // 使用可能带有 tools 的 client + .cancelled(false) + .loginUser(loginUser) + .connectInfo(connectInfo) + .build(); + + // ... 后续代码保持不变 ... + } +} +``` + +#### Step 7:修改 StreamAction 支持 Tool Call 事件 + +**文件:** `chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/statemachine/actions/StreamAction.java` + +```java +@Override +public void execute(StateContext context) { + log.info("[StreamAction] execute called"); + ChatContext ctx = getChatContext(context); + + if (ctx.isCancelled()) { + return; + } + + String prompt = ctx.getBuiltPrompt(); + + if (!promptValidator.isValidLength(prompt)) { + sendError(ctx.getSseEmitter(), "提示语超出最大长度"); + context.getStateMachine().sendEvent( + MessageBuilder.withPayload(ChatEvent.AI_CALL_FAILED).build() + ); + return; + } + + try { + sendStateEvent(ctx.getSseEmitter(), ChatState.STREAMING, "AI 正在生成..."); + + // 注册工具函数 + Object tools = PromptType.NL_2_COMMENT.getCode().equals(ctx.getRequest().getPromptType()) + ? commentUpdateService + : null; + + Flux flux = ctx.getChatClient().prompt() + .user(prompt) + .tools(tools) // 如果有工具,注册到调用中 + .stream() + .chatResponse(); + + flux.publishOn(Schedulers.boundedElastic()) + .doOnNext(chatResponse -> { + if (ctx.isCancelled()) { + throw new RuntimeException("Cancelled by user"); + } + + Generation generation = chatResponse.getResult(); + + // 检查是否有 tool_calls(Function Call) + if (generation.getMetadata() != null && + generation.getMetadata().containsKey("tool_calls")) { + + @SuppressWarnings("unchecked") + List> toolCalls = + (List>) generation.getMetadata().get("tool_calls"); + + for (Map toolCall : toolCalls) { + sendToolCallEvent(ctx.getSseEmitter(), toolCall); + } + + // Function Call 后 AI 会继续生成,不立即返回 + return; + } + + // 正常内容处理 + JSONObject data = new JSONObject(); + String content = generation.getOutput().getText(); + String thinking = extractThinking(generation); + + if (content != null && !content.isEmpty()) { + data.put("content", content); + } + if (thinking != null && !thinking.isEmpty()) { + data.put("thinking", thinking); + } + + if (!data.isEmpty()) { + try { + ctx.getSseEmitter().send(SseEmitter.event() + .id(ctx.getUid()) + .name("message") + .data(data.toJSONString()) + .reconnectTime(3000)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + }) + .doOnError(error -> { + log.error("[StreamAction] Stream error", error); + if (!ctx.isCancelled()) { + sendError(ctx.getSseEmitter(), "AI 调用失败:" + error.getMessage()); + } + context.getStateMachine().sendEvent( + MessageBuilder.withPayload(ChatEvent.AI_CALL_FAILED).build() + ); + }) + .doOnComplete(() -> { + log.info("[StreamAction] Stream completed"); + try { + ctx.getSseEmitter().send(SseEmitter.event() + .id("[DONE]") + .data("[DONE]") + .reconnectTime(3000)); + } catch (IOException ignored) { + } + ctx.getSseEmitter().complete(); + context.getStateMachine().sendEvent( + MessageBuilder.withPayload(ChatEvent.STREAM_FINISHED).build() + ); + }) + .subscribe(); + + } catch (Exception e) { + log.error("[StreamAction] Start streaming failed", e); + sendError(ctx.getSseEmitter(), "启动 AI 流式调用失败:" + e.getMessage()); + context.getStateMachine().sendEvent( + MessageBuilder.withPayload(ChatEvent.AI_CALL_FAILED).build() + ); + } +} + +/** + * 发送 tool_call 事件到前端 + */ +private void sendToolCallEvent(SseEmitter emitter, Map toolCall) { + try { + JSONObject data = new JSONObject(); + data.put("type", "tool_call"); + data.put("name", toolCall.get("name")); + data.put("arguments", toolCall.get("arguments")); + data.put("id", toolCall.get("id")); + + emitter.send(SseEmitter.event() + .name("tool_call") + .data(data.toJSONString())); + + log.info("[StreamAction] Sent tool_call event: {}", toolCall.get("name")); + } catch (IOException e) { + log.error("[StreamAction] Failed to send tool_call event", e); + } +} + +/** + * 提取 thinking 内容(如果有) + */ +private String extractThinking(Generation generation) { + if (generation.getMetadata() != null && generation.getMetadata().containsKey("thinking")) { + Object thinkingObj = generation.getMetadata().get("thinking"); + return thinkingObj instanceof String ? (String) thinkingObj : thinkingObj.toString(); + } + return null; +} +``` + +#### Step 8:更新 prompt-templates.yml + +**文件:** `chat2db-server-web-api/src/main/resources/prompt-templates.yml` + +```yaml +nl_2_comment: + name: "nl_2_comment" + description: "猜测表和字段注释" + template: | + ### 请根据以下 table properties 和 SQL input{description}. {ext} + # + ### {db_type} SQL tables, with their properties: + # + # {schema} + # + ### 任务:为表和字段生成合适的中文注释 + + 请分析以下表和字段的用途,为它们生成准确、简洁的中文注释。 + + **可用工具:** + - set_table_comment(table_name: str, comment: str) - 设置表注释 + - set_column_comment(table_name: str, column_name: str, comment: str) - 设置列注释 + + **要求:** + 1. 注释应该简洁明了,不超过 50 字 + 2. 注释应该准确反映表/字段的用途 + 3. 使用中文注释 + + **当前表:** + {message} + + 请调用工具函数来设置注释,而不是直接输出注释内容。 +``` + +#### Step 9:创建 REST API(可选,供前端手动调用) + +**文件:** `chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/CommentController.java` + +```java +package ai.chat2db.server.web.api.controller.rdb; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import ai.chat2db.server.domain.api.param.CommentUpdateParam; +import ai.chat2db.server.domain.api.service.TableService; +import ai.chat2db.server.tools.common.model.ActionResult; +import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; +import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; + +/** + * 表/列注释更新接口 + */ +@RestController +@ConnectionInfoAspect +@RequestMapping("/api/rdb/comment") +@Slf4j +public class CommentController { + + @Autowired + private TableService tableService; + + /** + * 更新表注释 + */ + @PostMapping("/table/update") + public ActionResult updateTableComment(@Valid @RequestBody CommentUpdateRequest request) { + log.info("[CommentController] Updating table comment: {}", request); + + CommentUpdateParam param = CommentUpdateParam.builder() + .tableName(request.getTableName()) + .comment(request.getComment()) + .build(); + + tableService.updateTableComment(param); + + return ActionResult.success(); + } + + /** + * 更新列注释 + */ + @PostMapping("/column/update") + public ActionResult updateColumnComment(@Valid @RequestBody CommentUpdateRequest request) { + log.info("[CommentController] Updating column comment: {}", request); + + CommentUpdateParam param = CommentUpdateParam.builder() + .tableName(request.getTableName()) + .columnName(request.getColumnName()) + .comment(request.getComment()) + .build(); + + tableService.updateColumnComment(param); + + return ActionResult.success(); + } +} +``` + +**文件:** `chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/CommentUpdateRequest.java` + +```java +package ai.chat2db.server.web.api.controller.rdb.request; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +/** + * 注释更新请求 + */ +@Data +public class CommentUpdateRequest { + + /** + * 数据源ID + */ + private Long dataSourceId; + + /** + * 数据库名 + */ + private String databaseName; + + /** + * Schema名 + */ + private String schemaName; + + /** + * 表名 + */ + @NotBlank(message = "表名不能为空") + private String tableName; + + /** + * 列名(更新列注释时必填) + */ + private String columnName; + + /** + * 注释内容 + */ + @NotBlank(message = "注释内容不能为空") + private String comment; +} +``` + +--- + +### 第二阶段:前端重构 + +#### Step 10:扩展 IAiChatPromptType 类型 + +**文件:** `chat2db-client/src/pages/main/workspace/store/common.ts` + +```typescript +// 修改前 +export type IAiChatPromptType = 'NL_2_SQL' | 'SQL_EXPLAIN' | 'SQL_OPTIMIZER' | 'SQL_2_SQL'; + +// 修改后 +export type IAiChatPromptType = + | 'NL_2_SQL' + | 'SQL_EXPLAIN' + | 'SQL_OPTIMIZER' + | 'SQL_2_SQL' + | 'NL_2_COMMENT'; // 新增猜一猜类型 +``` + +#### Step 11:修改 eventSource.ts 支持 tool_call + +**文件:** `chat2db-client/src/utils/eventSource.ts` + +```typescript +interface EventSourceOptions { + url: string; + uid: string; + onOpen?: () => void; + onMessage?: (content: string, thinking?: string) => void; + onStateChange?: (state: ChatStateType, message?: string) => void; + onError?: (error: string) => void; + onDone?: () => void; + onTablesSelected?: (tables: string[]) => void; + onSchemaFetched?: (ddl: string) => void; + onToolCall?: (toolCall: IToolCall) => void; // 新增 +} + +/** + * Tool Call 数据结构 + */ +interface IToolCall { + type: 'tool_call'; + name: string; + arguments: string; + id?: string; +} + +const connectToEventSource = (options: EventSourceOptions): (() => void) => { + const { + url, + uid, + onOpen, + onMessage, + onStateChange, + onError, + onDone, + onTablesSelected, + onSchemaFetched, + onToolCall, // 新增 + } = options; + + // ... 现有代码 ... + + // 新增:监听 tool_call 事件 + eventSource.addEventListener('tool_call', (event: MessageEvent) => { + console.log('[SSE] Tool call received:', event.data); + try { + const toolCall: IToolCall = JSON.parse(event.data); + if (onToolCall) { + onToolCall(toolCall); + } + } catch (e) { + console.error('[SSE] Failed to parse tool_call event', e); + } + }); + + // ... 其他监听器保持不变 ... + + return () => { + eventSource.close(); + }; +}; + +// 导出 ToolCall 类型 +export interface IToolCall { + type: 'tool_call'; + name: string; + arguments: string; + id?: string; +} + +export default connectToEventSource; +``` + +#### Step 12:修改 AiChat 组件处理 Function Call + +**文件:** `chat2db-client/src/components/AiChat/index.tsx` + +```typescript +import { IToolCall } from '@/utils/eventSource'; +import sqlService from '@/service/sql'; + +// 在 sendAiChatInternal 函数中添加 onToolCall 回调 +const sendAiChatInternal = useCallback( + (messageText: string, promptType: IAiChatPromptType = 'NL_2_SQL', info: typeof boundInfo) => { + // ... 现有代码 ... + + closeEventSource.current = connectToEventSource({ + url: `/api/ai/chat?${params}`, + uid: sessionId, + onOpen: () => { + console.log('[AiChat] SSE connection opened'); + updateState(sessionId, 'IDLE'); + }, + onStateChange: (state, _msg) => { + console.log('[AiChat] State changed:', state, _msg); + updateState(sessionId, state); + }, + onMessage: (content, thinking) => { + console.log('[AiChat] Message received:', { content, thinking }); + appendContent(sessionId, content, thinking); + }, + + // 新增:处理 Function Call + onToolCall: async (toolCall: IToolCall) => { + console.log('[AiChat] Tool call received:', toolCall); + + try { + const args = JSON.parse(toolCall.arguments); + + if (toolCall.name === 'set_table_comment') { + // 调用后端 API 更新表注释 + await sqlService.updateTableComment({ + dataSourceId: info.dataSourceId, + databaseName: info.databaseName, + schemaName: info.schemaName, + tableName: args.table_name, + comment: args.comment, + }); + message.success(`表 ${args.table_name} 注释已更新`); + + // 添加提示消息到聊天 + appendContent(sessionId, `\n✅ 已更新表 ${args.table_name} 的注释:${args.comment}\n`); + + } else if (toolCall.name === 'set_column_comment') { + // 调用后端 API 更新列注释 + await sqlService.updateColumnComment({ + dataSourceId: info.dataSourceId, + databaseName: info.databaseName, + schemaName: info.schemaName, + tableName: args.table_name, + columnName: args.column_name, + comment: args.comment, + }); + message.success(`字段 ${args.column_name} 注释已更新`); + + // 添加提示消息到聊天 + appendContent(sessionId, `\n✅ 已更新字段 ${args.table_name}.${args.column_name} 的注释:${args.comment}\n`); + } + } catch (error) { + console.error('[AiChat] Tool call execution failed:', error); + message.error(`工具调用失败:${error.message}`); + } + }, + + onTablesSelected: (tables) => { + console.log('[AiChat] Tables selected:', tables); + setSelectedTables(sessionId, tables); + }, + onSchemaFetched: (ddl) => { + console.log('[AiChat] Schema fetched, ddl length:', ddl?.length); + setSchemaInfo(sessionId, ddl); + }, + onDone: () => { + console.log('[AiChat] onDone callback, sessionId:', sessionId); + updateState(sessionId, 'COMPLETED'); + const currentSessions = useAiChatStore.getState().sessions; + const session = currentSessions.get(sessionId); + if (session?.currentContent) { + addMessage(sessionId, { + id: uuidv4(), + role: 'assistant', + content: session.currentContent, + }); + } + closeEventSource.current = undefined; + }, + onError: (error) => { + console.error('[AiChat] SSE error:', error); + setError(sessionId, error); + message.error(error); + closeEventSource.current = undefined; + }, + }); + }, + [ + createSession, + addMessage, + setLastRequest, + resetCurrentContent, + updateState, + appendContent, + setSelectedTables, + setSchemaInfo, + setError, + ], +); +``` + +#### Step 13:在 sql.ts 中添加注释更新 API + +**文件:** `chat2db-client/src/service/sql.ts` + +```typescript +// 新增:更新表注释 API +const updateTableComment = createRequest<{ + dataSourceId: number; + databaseName: string; + schemaName?: string | null; + tableName: string; + comment: string; +}, void>('/api/rdb/comment/table/update', { + method: 'post', +}); + +// 新增:更新列注释 API +const updateColumnComment = createRequest<{ + dataSourceId: number; + databaseName: string; + schemaName?: string | null; + tableName: string; + columnName: string; + comment: string; +}, void>('/api/rdb/comment/column/update', { + method: 'post', +}); + +// 在 export default 中添加 +export default { + // ... 现有方法 ... + updateTableComment, + updateColumnComment, +}; +``` + +#### Step 14:修改 DatabaseTableEditor 使用 AiChat(推荐方案) + +**文件:** `chat2db-client/src/blocks/DatabaseTableEditor/index.tsx` + +```typescript +import React, { memo, useEffect, useMemo, useRef, useState } from 'react'; +import { Button, Drawer, Modal, Spin, message } from 'antd'; +import classnames from 'classnames'; +import lodash from 'lodash'; +import { v4 as uuidv4 } from 'uuid'; +import BaseInfo, { IBaseInfoRef } from './BaseInfo'; +import ColumnList, { IColumnListRef } from './ColumnList'; +import ForeignKeyList, { IForeignKeyListRef } from './ForeignKeyList'; +import IndexList, { IIndexListRef } from './IndexList'; +import styles from './index.less'; +import i18n from '@/i18n'; +import sqlService from '@/service/sql'; +import { setCurrentWorkspaceExtend, setPendingAiChat } from '@/pages/main/workspace/store/common'; + +// 删除本地 IPromptType 枚举定义(第 31-38 行) +// 删除本地 guess() 函数(第 263-328 行) +// 删除本地 EventSource 相关代码和状态(aiContent、isAiDrawerOpen 等) + +interface IProps { + dataSourceId: number; + databaseName: string; + schemaName?: string | null; + tableName?: string; + databaseType: DatabaseTypeCode; + changeTabDetails: (data: IWorkspaceTab) => void; + tabDetails: IWorkspaceTab; + submitCallback: () => void; +} + +export default memo((props: IProps) => { + const { + databaseName, + dataSourceId, + tableName, + schemaName, + changeTabDetails, + tabDetails, + databaseType, + submitCallback, + } = props; + + // ... 现有状态和逻辑 ... + + /** + * 打开 AI Chat 进行猜一猜 + * + * 改为使用统一的 AiChat 组件,通过 pendingAiChat 机制触发 + */ + const openAiChatForGuess = useCallback(() => { + if (!tableName) { + message.warning('请先选择一个表'); + return; + } + + // 构建消息内容 + const message = `请为表 ${tableName} 及其所有字段生成合适的中文注释`; + + // 设置 pendingAiChat,触发 AiChat 组件 + setPendingAiChat({ + dataSourceId, + databaseName, + schemaName: schemaName || null, + message, + promptType: 'NL_2_COMMENT', + }); + + // 打开右侧 AI Chat 面板 + setCurrentWorkspaceExtend('ai'); + }, [dataSourceId, databaseName, schemaName, tableName]); + + // ... 其他现有逻辑 ... + + return ( + + +
    +
    + {tabList.map((item) => { + return ( +
    + {item.title} +
    + ); + })} +
    +
    + {/* 使用新的猜一猜按钮 */} + +
    +
    + +
    +
    +
    + {tabList.map((t) => { + return ( +
    + {t.component} +
    + ); + })} +
    +
    + + {/* 删除 AI Drawer(第 397-417 行) */} + + {/* 保留 SQL Preview Modal */} + { + setViewSqlModal(false); + }} + width="60vw" + maskClosable={false} + footer={false} + destroyOnHidden={true} + > + + +
    + ); +}); +``` + +--- + +## 五、技术原理说明 + +### 5.1 Spring AI Function Call 机制 + +```mermaid +sequenceDiagram + participant User as 用户/AI Chat + participant Backend as ChatController + participant AIModel as AI 模型 + participant ToolService as CommentUpdateService + participant Database as 数据库 + + User->>Backend: POST /api/ai/chat (NL_2_COMMENT) + Backend->>Backend: 注册 tools (set_table_comment, set_column_comment) + Backend->>AIModel: Prompt + Tools + AIModel->>AIModel: 分析表结构,生成注释 + AIModel->>Backend: tool_calls: [set_table_comment, set_column_comment] + Backend->>ToolService: 调用 setTableComment() + ToolService->>Database: ALTER TABLE ... COMMENT = '...' + Database-->>ToolService: 执行成功 + ToolService-->>Backend: 返回结果 + Backend->>AIModel: 工具执行结果 + AIModel->>AIModel: 继续生成或完成 + AIModel-->>Backend: 最终响应 + Backend->>User: SSE: tool_call + message + [DONE] + User->>User: 显示注释更新提示 +``` + +### 5.2 SSE 事件流 + +``` +SSE 事件顺序(NL_2_COMMENT): +1. event: connect → 连接建立 +2. event: state → FETCHING_TABLE_SCHEMA +3. event: schema_fetched → 发送 DDL 到前端 +4. event: state → BUILDING_PROMPT +5. event: state → STREAMING +6. event: tool_call → set_table_comment +7. event: tool_call → set_column_comment (可能有多个) +8. event: message → AI 生成的文本内容 +9. event: message → ... (流式) +10. event: message → [DONE] +11. 自动关闭连接 +``` + +--- + +## 六、实施清单 + +### 后端任务(优先级:高) + +| 序号 | 任务 | 文件 | 状态 | +|------|------|------|------| +| 1 | 创建 Function Call 定义类 | CommentFunctionDefinitions.java | ⏳ 待实施 | +| 2 | 创建注释更新服务实现 | CommentUpdateServiceImpl.java | ⏳ 待实施 | +| 3 | 创建参数类 | CommentUpdateParam.java | ⏳ 待实施 | +| 4 | 扩展 TableService 接口 | TableService.java | ⏳ 待实施 | +| 5 | 实现 TableService 方法 | TableServiceImpl.java | ⏳ 待实施 | +| 6 | 修改 ChatController 注册 Function | ChatController.java | ⏳ 待实施 | +| 7 | 修改 StreamAction 支持 Tool Call | StreamAction.java | ⏳ 待实施 | +| 8 | 更新 prompt-templates.yml | prompt-templates.yml | ⏳ 待实施 | +| 9 | 创建 REST API(可选) | CommentController.java | ⏳ 待实施 | + +### 前端任务(优先级:高) + +| 序号 | 任务 | 文件 | 状态 | +|------|------|------|------| +| 10 | 扩展 IAiChatPromptType 类型 | common.ts | ⏳ 待实施 | +| 11 | 修改 eventSource.ts 支持 tool_call | eventSource.ts | ⏳ 待实施 | +| 12 | 修改 AiChat 组件处理 Function Call | AiChat/index.tsx | ⏳ 待实施 | +| 13 | 在 sql.ts 中添加注释更新 API | sql.ts | ⏳ 待实施 | +| 14 | 修改 DatabaseTableEditor 使用 AiChat | DatabaseTableEditor/index.tsx | ⏳ 待实施 | + +### 测试任务(优先级:中) + +| 序号 | 任务 | 类型 | 状态 | +|------|------|------|------| +| 15 | 单元测试:CommentUpdateServiceImpl | 后端 | ⏳ 待实施 | +| 16 | 集成测试:NL_2_COMMENT 完整流程 | 后端 | ⏳ 待实施 | +| 17 | 前端测试:AiChat 组件 Function Call 处理 | 前端 | ⏳ 待实施 | +| 18 | 端到端测试:DatabaseTableEditor 猜一猜功能 | 全栈 | ⏳ 待实施 | + +--- + +## 七、预期效果 + +### 功能层面 + +✅ **猜一猜功能真正可用** - AI 生成的注释自动更新到数据库 +✅ **前后端统一** - AiChat 成为唯一的 AI 交互入口 +✅ **用户体验提升** - 注释自动生成并应用,无需手动复制粘贴 +✅ **扩展性强** - 可轻松添加更多 AI 工具函数(如删除注释、批量更新等) + +### 技术层面 + +✅ **代码复用** - 消除 DatabaseTableEditor 中重复的 EventSource 逻辑 +✅ **架构清晰** - Function Call 机制符合 AI 原生设计理念 +✅ **易于维护** - 单一入口,统一管理 +✅ **技术先进** - 采用 Spring AI 标准 Function Calling 机制 + +--- + +## 八、风险与注意事项 + +### 8.1 潜在风险 + +| 食险 | 影响 | 缓解措施 | +|------|------|----------| +| **Spring AI 版本兼容性** | 🟡 中 | 确认使用的 Spring AI 版本支持 Function Calling | +| **数据库 SQL 方言差异** | 🟡 中 | 在 TableServiceImpl 中根据数据库类型生成不同 SQL | +| **AI 模型 Function Call 支持程度** | 🟡 中 | 选择支持 Function Calling 的模型(如 OpenAI GPT-4) | +| **注释内容质量问题** | 🟢 低 | 在 prompt 中添加质量要求,限制注释长度 | +| **并发更新冲突** | 🟢 低 | Function Call 串行执行,避免并发问题 | + +### 8.2 注意事项 + +1. **数据库方言适配** + - MySQL: `ALTER TABLE table_name COMMENT = 'comment'` + - PostgreSQL: `COMMENT ON TABLE table_name IS 'comment'` + - Oracle: 需要特殊处理 + - SQL Server: 使用 `sp_addextendedproperty` + +2. **Function Call 执行顺序** + - Spring AI 会自动按顺序执行 tool_calls + - 需要确保 tool 函数幂等,避免重复执行问题 + +3. **前端状态同步** + - AiChat 更新注释后,需要刷新 DatabaseTableEditor 的显示 + - 可以通过事件或重新查询实现 + +4. **错误处理** + - Function Call 执行失败时,需要友好提示用户 + - 日志记录执行过程,便于排查问题 + +--- + +## 九、参考资料 + +### Spring AI Function Calling + +- [Spring AI Documentation](https://docs.spring.io/spring-ai/reference/) +- [Function Calling Guide](https://docs.spring.io/spring-ai/reference/api/functions.html) + +### OpenAI Function Calling + +- [OpenAI Function Calling Guide](https://platform.openai.com/docs/guides/function-calling) +- [Function Calling Best Practices](https://platform.openai.com/docs/guides/function-calling/best-practices) + +### Chat2DB 相关文档 + +- [状态机设计文档](./StateMachine.md) +- [Prompt 重构报告](./prompt-refactoring-completed.md) + +--- + +**文档结束** + +> 作者:Chat2DB Team +> 最后更新:2026-04-12 \ No newline at end of file diff --git a/docs/PRINCIPLE_AND_LOGS.md b/docs/PRINCIPLE_AND_LOGS.md new file mode 100644 index 000000000..544f23648 --- /dev/null +++ b/docs/PRINCIPLE_AND_LOGS.md @@ -0,0 +1,587 @@ +# SQL 智能补全原理与调试日志 + +## 一、整体架构 + +``` +用户输入 SQL + │ + ▼ +┌─────────────────────────────────────┐ +│ Monaco Editor 触发补全 │ +│ triggerCharacters: . : = 等 │ +└─────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────┐ +│ 1. 词法分析 (Lexer) │ +│ SQL 字符串 → Token 流 │ +└─────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────┐ +│ 2. 语法分析 (Parser) │ +│ Token 流 → AST (抽象语法树) │ +│ 记录光标位置 (cursorKeyPath) │ +└─────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────┐ +│ 3. 语义分析 (Reader) │ +│ 从 AST 提取语义信息 │ +│ 识别:表名/字段/别名/函数 │ +└─────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────┐ +│ 4. 补全生成 │ +│ 根据语义类型调用对应 API │ +│ - onSuggestTableNames │ +│ - onSuggestTableFields │ +└─────────────────────────────────────┘ + │ + ▼ +用户看到补全列表 +``` + +## 二、核心流程详解 + +### 2.1 词法分析(Lexer) + +**文件**: `sql-parser/mysql/lexer.ts` + +**作用**: 将 SQL 字符串转换为 Token 流 + +```javascript +输入: "SELECT * FROM users WHERE id = 1" + +输出: [ + { type: 'word', value: 'SELECT', position: [0, 6] }, + { type: 'whitespace', value: ' ', ignore: true }, + { type: 'special', value: '*', position: [7, 8] }, + { type: 'whitespace', value: ' ', ignore: true }, + { type: 'word', value: 'FROM', position: [9, 13] }, + { type: 'whitespace', value: ' ', ignore: true }, + { type: 'word', value: 'users', position: [14, 19] }, + // ... 更多 Token +] +``` + +**关键日志**: +```javascript +// 在 parser/chain.ts 中添加 +console.log('[Lexer] 输入 SQL:', text); +console.log('[Lexer] Token 数量:', tokens.length); +console.log('[Lexer] Token 流:', tokens); +``` + +### 2.2 语法分析(Parser) + +**文件**: `parser/chain.ts` + +**作用**: 基于递归下降算法构建 AST + +**核心算法**: +1. 从根节点开始深度优先遍历 +2. 尝试匹配每个子节点 +3. 失败时回溯到上一个成功点 +4. 记录光标前的所有节点路径 + +**AST 示例**: +```javascript +// SQL: SELECT * FROM users AS t WHERE t.id = 1 + +AST: [ + { + type: 'statement', + variant: 'select', + result: [{ name: { value: '*' } }], + from: { + sources: [ + { + type: 'identifier', + variant: 'table', + name: { + type: 'identifier', + variant: 'tableName', + tableName: { value: 'users' } + }, + alias: { value: 't' } // ← 别名 + } + ], + where: { /* WHERE 条件 */ } + } + } +] +``` + +**关键日志** (已添加): +```javascript +console.log('[Parser] 开始解析 SQL'); +console.log('[Parser] Token 数量:', tokens.length); +console.log('[Parser] 光标位置:', cursorIndex); +console.log('[Parser] 解析成功:', success); +console.log('[Parser] AST:', ast); +console.log('[Parser] 光标路径:', cursorKeyPath); +``` + +### 2.3 语义分析(Reader) + +**文件**: `sql-parser/base/reader.ts` + +**作用**: 从 AST 提取语义信息,识别补全类型 + +#### getCursorInfo - 识别光标处的语义类型 + +```javascript +输入: + - rootStatement: AST + - cursorKeyPath: ["0", "from", "sources", "0", "name", "alias"] + +处理流程: +1. 从 AST 中提取光标处的节点 +2. 判断父节点类型 +3. 返回语义类型 + +输出: +{ + type: 'tableFieldAfterGroup', // 表名限定字段 + token: { value: 'id' }, + groupName: 't' // 表别名 +} +``` + +**识别的类型**: +- `tableName`: 表名位置 +- `tableField`: 普通字段 +- `tableFieldAfterGroup`: `table.` 后面的字段 +- `functionName`: 函数名 + +**关键日志** (已添加): +```javascript +console.log('[Reader] getCursorInfo - keyPath:', keyPath); +console.log('[Reader] getCursorInfo - cursorValue:', cursorValue); +console.log('[Reader] getCursorInfo - cursorKey:', cursorKey); +console.log('[Reader] getCursorInfo - parentStatement:', parentStatement); +console.log('[Reader] getCursorInfo - typePlusVariant:', typePlusVariant); +console.log('[Reader] getCursorInfo - 识别为:', result?.type); +``` + +#### getFieldsFromStatement - 收集可用字段 + +```javascript +输入: + - rootStatement: AST + - cursorKeyPath: 光标路径 + - getFieldsByTableName: 获取字段的回调函数 + +处理流程: +1. 找到光标所在的 statement +2. 根据 variant 选择处理策略: + - 'select': 处理 FROM 子句 + - 'join': 找到父级 SELECT,处理所有 sources +3. 遍历所有表源 (sources + joins) +4. 为每个表收集字段 +5. 设置 groupPickerName (表别名) + +输出: +[ + { + label: 'id', + groupPickerName: 't', // ← 别名,用于分组 + tableInfo: { tableName: { value: 'users' } }, + originFieldName: 'id' + }, + { label: 'username', groupPickerName: 't', ... }, + // ... 更多字段 +] +``` + +**关键日志** (已添加): +```javascript +console.log('[Reader] getFieldsFromStatement - cursorRootStatement:', cursorRootStatement?.variant); +console.log('[Reader] getFieldsFromStatement - SELECT 语句模式'); +console.log('[Reader] getFieldsFromStatement - JOIN 语句模式'); +console.log('[Reader] getFieldsByFromClause - typePlusVariant:', typePlusVariant); +console.log('[Reader] getFieldsByFromClause - 处理表标识符'); +console.log('[Reader] getFieldsByFromClause - 别名:', itFromStatement.alias); +console.log('[Reader] getFieldsByFromClause - 使用别名作为分组名:', tableNameAlias); +console.log('[Reader] getFieldsByFromClause - 原始字段数:', originFields.length); +console.log('[Reader] getFieldsByFromClause - 处理后的字段示例:', originFields[0]); +``` + +### 2.4 补全生成(Monaco Plugin) + +**文件**: `monaco-plugin/index.ts` + +**作用**: 根据语义类型生成补全项 + +#### 补全策略分发 + +```javascript +switch (cursorInfo.type) { + case 'tableField': + // 普通字段补全(显示所有表的字段) + fields = getFieldsFromStatement(...) + groups = groupBy(fields, 'groupPickerName') + return fields + keywords + groupNames // 字段 + 关键字 + 表名分组 + + case 'tableFieldAfterGroup': + // 表名限定字段 (t.|) + fields = getFieldsFromStatement(...) + filteredFields = filter(fields, f.groupPickerName === 't') + return filteredFields + keywords // 只返回 t 表的字段 + + case 'tableName': + // 表名补全 (FROM |) + tables = getAllTableNames(...) + return tables + keywords + + default: + // 只返回 SQL 关键字 + return keywords +} +``` + +**关键日志** (已添加): +```javascript +console.log('[SQL 补全] === 开始补全流程 ==='); +console.log('[SQL 补全] 解析结果:', { success, hasError, cursorKeyPath }); +console.log('[SQL 补全] 光标信息:', cursorInfo); +console.log('[SQL 补全] 光标类型:', cursorInfo.type); +console.log('[SQL 补全] 表字段补全模式'); +console.log('[SQL 补全] 获取到字段数量:', cursorRootStatementFields.length); +console.log('[SQL 补全] 分组信息:', Object.keys(groups)); +console.log('[SQL 补全] 最终补全项总数:', result.length); +console.log('[SQL 补全] 表名限定字段模式,分组:', groupName); +console.log('[SQL 补全] 过滤前字段数量:', cursorRootStatementFieldsAfter.length); +console.log('[SQL 补全] 过滤后字段数量:', filteredFields.length); +``` + +#### 后端 API 调用 + +**文件**: `sql-autocomplete.ts` + +```javascript +// 获取表名列表 +onSuggestTableNames: async (cursorInfo) => { + data = await sqlService.getAllTableList({ + dataSourceId, + databaseName, + schemaName + }); + return data.map(table => ({ label: table.name, ... })); +} + +// 获取表字段列表 +onSuggestTableFields: async (tableInfo) => { + data = await sqlService.getColumnList({ + dataSourceId, + databaseName, + tableName: tableInfo.tableName.value + }); + return data.map(column => ({ label: column.name, ... })); +} +``` + +**关键日志** (已添加): +```javascript +console.log('[SQL 补全 - API] 获取表名列表,boundInfo:', { dataSourceId, databaseName, schemaName }); +console.log('[SQL 补全 - API] 获取到表名数量:', data.length); +console.log('[SQL 补全 - API] 获取表字段,表信息:', { tableName, namespace }); +console.log('[SQL 补全 - API] 获取到字段数量:', data.length); +console.log('[SQL 补全 - API] 字段示例:', data.slice(0, 3)); +``` + +## 三、表别名识别机制 + +### 3.1 别名在 AST 中的表示 + +```javascript +// SQL: FROM users AS t + +AST 节点: +{ + type: 'identifier', + variant: 'table', + name: { + type: 'identifier', + variant: 'tableName', + tableName: { value: 'users' } // 原始表名 + }, + alias: { value: 't' } // ← 别名 +} +``` + +### 3.2 别名传递流程 + +``` +1. Lexer 阶段 + "users AS t" → Token(users), Token(AS), Token(t) + +2. Parser 阶段 + 根据语法规则 tableSource: tableName [AS] alias + 构建 AST,设置 alias.value = 't' + +3. Reader 阶段 + getFieldsByFromClause 读取 alias.value + 设置 groupPickerName = 't' + +4. Monaco 阶段 + 根据 groupPickerName 生成分组 + 显示 "t" 作为补全项分组 + 过滤 t.后面的字段时匹配 groupPickerName +``` + +### 3.3 关键代码 + +```typescript +// reader.ts:186-220 +case 'identifier.table': + const itFromStatement = fromStatement as ISource; + + // 1. 获取真实字段 + let originFields = await getFieldsByTableName( + itFromStatement.name, + cursorInfo.token.value, + rootStatement + ); + + // 2. 提取别名 + const tableNameAlias: string = _.get(itFromStatement, 'alias.value'); + + let groupPickerName: string = null; + if (tableNameAlias) { + // 3. 有别名,使用别名作为分组名 + groupPickerName = tableNameAlias; + } else { + // 4. 无别名,使用表名作为分组名 + // ... (省略逻辑) + } + + // 5. 为字段添加分组信息 + originFields = originFields.map(originField => ({ + ...originField, + tableInfo: itFromStatement.name, + groupPickerName, // ← 关键字段 + originFieldName: originField.label, + })); +``` + +## 四、调试日志完整示例 + +### 4.1 正常补全流程日志 + +``` +[SQL 补全] === 开始补全流程 === +[SQL 补全] 解析结果:{ success: true, hasError: false, cursorKeyPath: [...], nextMatchingsCount: 5 } +[SQL 补全] 光标信息:{ type: 'tableFieldAfterGroup', token: {...}, groupName: 't' } +[SQL 补全] 光标类型:tableFieldAfterGroup +[SQL 补全] 表名限定字段模式,分组:t +[Reader] getCursorInfo - keyPath: ["0", "from", "sources", "0", "alias", "value"] +[Reader] getCursorInfo - cursorValue: { value: 't' } +[Reader] getCursorInfo - cursorKey: value +[Reader] getCursorInfo - parentStatement: { type: 'identifier', variant: 'columnAfterGroup', ... } +[Reader] getCursorInfo - typePlusVariant: identifier.columnAfterGroup +[Reader] getCursorInfo - 识别为表名限定字段,groupName: t +[Reader] getCursorInfo - 最终结果:{ type: 'tableFieldAfterGroup', ... } +[Reader] getFieldsFromStatement - 开始执行 +[Reader] getFieldsFromStatement - cursorRootStatement: select +[Reader] getFieldsFromStatement - SELECT 语句模式 +[Reader] getFieldsByFromClause - typePlusVariant: identifier.table +[Reader] getFieldsByFromClause - 处理表标识符,表信息:{ tableName: { value: 'users' } } +[Reader] getFieldsByFromClause - 别名:{ value: 't' } +[Reader] getFieldsByFromClause - 使用别名作为分组名:t +[Reader] getFieldsByFromClause - 原始字段数:10 +[Reader] getFieldsByFromClause - 处理后的字段示例:{ label: 'id', groupPickerName: 't', ... } +[SQL 补全 - API] 获取表字段,表信息:{ tableName: 'users', namespace: undefined, cursorValue: undefined } +[SQL 补全 - API] 获取到字段数量:10 +[SQL 补全 - API] 字段示例:[ + { name: 'id', columnType: 'bigint', comment: '用户 ID' }, + { name: 'username', columnType: 'varchar(50)', comment: '用户名' }, + { name: 'email', columnType: 'varchar(100)', comment: '邮箱' } +] +[SQL 补全] 过滤前字段数量:10 +[SQL 补全] 过滤后字段数量:10 +[SQL 补全] 最终补全项总数:15 +``` + +### 4.2 错误场景日志 + +**场景 1:API 调用失败** +``` +[SQL 补全 - API] 获取表字段,表信息:{ tableName: 'users' } +[SQL 补全 - API] 获取表字段失败:Error: Connection timeout + at ... +[SQL 补全] 获取到字段数量:0 +``` + +**场景 2:AST 解析失败** +``` +[SQL 补全] 解析结果:{ success: false, hasError: true, ... } +[SQL 补全] 光标信息:null +[SQL 补全] 无光标信息,返回关键字补全 +[SQL 补全] 关键字补全数量:5 +``` + +**场景 3:表别名未识别** +``` +[Reader] getFieldsByFromClause - 别名:null +[Reader] getFieldsByFromClause - 多个表名有值,不提供分组名 +[SQL 补全] 分组信息:[] +[SQL 补全] 最终补全项总数:10 // 只有字段,没有分组 +``` + +## 五、常见问题排查 + +### Q1: 为什么输入 `t.` 后不出现字段补全? + +**排查步骤**: +1. 检查控制台是否有 `[SQL 补全] 光标类型:tableFieldAfterGroup` + - 如果没有 → AST 解析失败 + - 如果有 → 继续下一步 + +2. 检查 `[Reader] getFieldsByFromClause - 别名:` 是否有值 + - 如果是 `null` → 别名识别失败 + - 如果有值 → 继续下一步 + +3. 检查 `[SQL 补全 - API] 获取到字段数量:` + - 如果是 0 → 后端 API 问题或表不存在 + - 如果有值 → 继续下一步 + +4. 检查 `[SQL 补全] 过滤后字段数量:` + - 如果是 0 → `groupPickerName` 不匹配 + - 如果有值 → 补全应该正常显示 + +### Q2: 为什么只显示字段,没有表名分组? + +**原因**: `groupPickerName` 为 null + +**排查**: +```javascript +console.log('[Reader] getFieldsByFromClause - 别名:', itFromStatement.alias); +console.log('[Reader] getFieldsByFromClause - 使用别名作为分组名:', tableNameAlias); +``` + +**解决方案**: +- 检查是否使用了 `AS` 关键字:`FROM users AS t` (推荐) +- 检查是否直接写别名:`FROM users t` (部分数据库支持) + +### Q3: 为什么补全项中有重复字段? + +**原因**: 多个表源有相同的 `groupPickerName` + +**排查**: +```javascript +console.log('[SQL 补全] 分组信息:', Object.keys(groups)); +``` + +**解决方案**: +- 使用不同的表别名 +- 检查是否有重复的 JOIN + +## 六、性能优化建议 + +### 6.1 缓存策略 + +```typescript +const fieldCache = new Map(); + +onSuggestTableFields: async (tableInfo) => { + const cacheKey = `${boundInfo.dataSourceId}_${boundInfo.databaseName}_${tableInfo.tableName.value}`; + + if (fieldCache.has(cacheKey)) { + console.log('[SQL 补全 - API] 使用缓存字段,key:', cacheKey); + return fieldCache.get(cacheKey); + } + + const data = await sqlService.getColumnList(...); + fieldCache.set(cacheKey, data); + return data; +} +``` + +### 6.2 防抖处理 + +```typescript +import { debounce } from 'lodash'; + +const debouncedSuggest = debounce(async (tableInfo) => { + console.log('[SQL 补全 - API] 实际调用 API'); + const data = await sqlService.getColumnList(...); + return data; +}, 300); +``` + +### 6.3 懒加载 + +```typescript +// 只在实际需要时才获取字段 +onSuggestFieldGroup: (tableNameOrAlias) => { + // 先显示分组名 + // 用户点击分组后再加载字段 + return { + label: tableNameOrAlias, + kind: monaco.languages.CompletionItemKind.Folder, + }; +} +``` + +## 七、测试用例 + +### 测试 1: 基本表别名补全 + +```sql +SELECT * FROM users AS t WHERE t. +``` + +**预期日志**: +``` +[Reader] getFieldsByFromClause - 别名:{ value: 't' } +[Reader] getFieldsByFromClause - 使用别名作为分组名:t +[SQL 补全] 光标类型:tableFieldAfterGroup +[SQL 补全] 分组:t +[SQL 补全] 过滤后字段数量:N (users 表的字段数) +``` + +### 测试 2: 多表 JOIN + +```sql +SELECT t1.id, t2.name +FROM users t1 +JOIN orders t2 ON t1.id = t2.user_id +WHERE t2. +``` + +**预期日志**: +``` +[Reader] getFieldsFromStatement - JOIN 语句模式 +[Reader] getFieldsByFromClause - 处理表标识符,表信息:users +[Reader] getFieldsByFromClause - 别名:{ value: 't1' } +[Reader] getFieldsByFromClause - 处理表标识符,表信息:orders +[Reader] getFieldsByFromClause - 别名:{ value: 't2' } +[SQL 补全] 过滤后字段数量:N (orders 表的字段数) +``` + +### 测试 3: 子查询 + +```sql +SELECT * FROM (SELECT id, username FROM users) AS t WHERE t. +``` + +**预期日志**: +``` +[Reader] getFieldsByFromClause - 处理子查询 +[SQL 补全 - API] 获取表字段,表信息:users +[Reader] getFieldsByFromClause - 子查询有别名:t +[SQL 补全] 过滤后字段数量:2 (id, username) +``` + +## 八、总结 + +SQL 智能补全的核心是: +1. **AST 解析**: 准确识别 SQL 语法结构 +2. **别名识别**: 通过 `alias.value → groupPickerName` 传递别名信息 +3. **字段收集**: 遍历所有表源,收集字段并设置分组 +4. **精准过滤**: 根据 `groupName` 过滤对应表的字段 + +通过日志可以清晰地看到数据流转过程,快速定位问题。 diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..57b56f803 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,144 @@ +# SQL 智能补全功能实现说明 + +## 实现方案 + +采用**方案二:启用 syntax-parser 的真实数据补全** + +## 修改内容 + +### 1. 新增文件 +- `src/components/MonacoEditor/syntax-parser/plugin/monaco-plugin/sql-autocomplete.ts` + - 实现了真实的 SQL 智能补全功能 + - 调用后端 API 获取表名和字段列表 + - 支持表别名补全(如 `t.` 后补全字段) + +### 2. 修改文件 + +#### MonacoEditor 组件 +- `src/components/MonacoEditor/index.tsx` + - 添加 `boundInfo` 属性支持 + - 初始化 SQL 智能补全 + - 在 boundInfo 变化时重新初始化补全 + +#### ConsoleEditor 组件 +- `src/components/ConsoleEditor/index.tsx` + - 传递 `boundInfo` 给 MonacoEditor + +## 功能特性 + +### 支持的补全类型 + +1. **表名补全** + - 在 `FROM`、`JOIN` 等关键字后触发 + - 从后端获取当前数据库的所有表 + +2. **字段补全**(核心功能) + - 支持 `表名.` 或 `表别名.` 后补全字段 + - 例如:`select * from AKMG_APP t where t.` 后输入字段名 + - 从后端获取表的所有列 + +3. **SQL 关键字补全** + - SELECT, FROM, WHERE, JOIN, GROUP BY, ORDER BY 等 + - 常用函数:COUNT, SUM, AVG, MAX, MIN 等 + +4. **Hover 提示** + - 鼠标悬停在字段上显示字段类型和注释 + - 鼠标悬停在表名上显示表信息 + +### 技术实现 + +```typescript +// 核心补全逻辑 +onSuggestTableFields: async (tableInfo, cursorValue, rootStatement) => { + const tableName = tableInfo?.tableName?.value; + + // 调用后端 API 获取字段列表 + const data = await sqlService.getColumnList({ + dataSourceId: boundInfo.dataSourceId, + databaseName: boundInfo.databaseName, + schemaName: boundInfo.schemaName, + tableName, + }); + + // 返回补全项 + return data.map((column) => ({ + label: column.name, + insertText: column.name, + kind: monaco.languages.CompletionItemKind.Field, + detail: column.columnType, + documentation: column.comment, + })); +} +``` + +## 使用场景 + +### 场景 1:表别名补全 +```sql +select * from AKMG_APP t where t. -- 输入 t. 后自动补全 AKMG_APP 表的字段 +``` + +### 场景 2:直接表名补全 +```sql +select AKMG_APP. -- 输入 AKMG_APP. 后自动补全字段 +``` + +### 场景 3:多表 JOIN +```sql +select t1.id, t2.name +from table1 t1 +join table2 t2 on t1.id = t2. -- 输入 t2. 后补全 table2 的字段 +``` + +## 性能优化 + +1. **按需加载** + - 只在 boundInfo 存在时初始化补全 + - 编辑器聚焦时才激活补全 + +2. **错误处理** + - API 调用失败时返回空数组,不影响编辑器使用 + - 控制台输出错误信息便于调试 + +## 与原有 IntelliSense 的对比 + +| 特性 | 原 IntelliSense | 新 syntax-parser | +|------|----------------|------------------| +| 表别名支持 | ❌ 不支持 | ✅ 完全支持 | +| 上下文理解 | ❌ 简单正则 | ✅ AST 语法树分析 | +| 数据来源 | ✅ 后端 API | ✅ 后端 API | +| 复杂 SQL 支持 | ❌ 有限 | ✅ 完整支持 | +| 响应速度 | ✅ 快 | ⚠️ 依赖解析速度 | + +## 后续优化建议 + +1. **缓存机制** + - 缓存已获取的表字段信息 + - 减少重复 API 调用 + +2. **智能排序** + - 根据使用频率排序补全项 + - 优先显示常用字段 + +3. **增量更新** + - 表结构变化时更新缓存 + - 支持手动刷新 + +4. **性能优化** + - Worker 线程解析 SQL + - 防抖处理减少解析频率 + +## 测试方法 + +1. 打开 SQL 编辑器 +2. 选择数据源和数据库 +3. 输入:`select * from 表名 t where t.` +4. 检查是否出现字段补全列表 +5. 检查字段类型和注释是否正确显示 + +## 相关文件 + +- 主实现:`src/components/MonacoEditor/syntax-parser/plugin/monaco-plugin/sql-autocomplete.ts` +- MonacoEditor: `src/components/MonacoEditor/index.tsx` +- ConsoleEditor: `src/components/ConsoleEditor/index.tsx` +- 原有 IntelliSense: `src/utils/IntelliSense/` diff --git a/docs/StateMachine.md b/docs/StateMachine.md new file mode 100644 index 000000000..9cf9579bc --- /dev/null +++ b/docs/StateMachine.md @@ -0,0 +1,160 @@ +# Prompt处理状态机:事件分支逻辑与完整流程图 + +## 一、设计理念 + +基于"前端触发事件,后端状态自动流转"的架构,事件(Event)代表用户或系统的明确**动作或指令**,状态(State)代表系统当前所处的**处理阶段**。状态机根据接收到的事件和当前状态,结合守卫条件,自动决定下一个状态并执行业务逻辑。 + +## 二、事件(Event)体系完整定义 + +### 2.1 用户发起的主要指令事件 + +这些事件对应前端可触发的各类`PromptType`请求。 + +| 事件 | 对应PromptType | 触发方 | 含义与携带信息 | +| ----------------------------- | ------------------ | ------ | ------------------------------------------------------------ | +| **`REQUEST_NL_TO_SQL`** | `NL_2_SQL` | 前端 | 请求将自然语言转换为SQL。需携带`message`(自然语言问题)。 | +| **`REQUEST_EXPLAIN_SQL`** | `SQL_EXPLAIN` | 前端 | 请求解释SQL语句。需携带`message`(SQL语句)。 | +| **`REQUEST_OPTIMIZE_SQL`** | `SQL_OPTIMIZER` | 前端 | 请求优化SQL语句。需携带`message`(SQL语句)。 | +| **`REQUEST_CONVERT_SQL`** | `SQL_2_SQL` | 前端 | 请求转换SQL方言。需携带`message`(SQL语句)和`destSqlType`(目标数据库类型)。 | +| **`REQUEST_TEXT_GENERATION`** | `TEXT_GENERATION` | 前端 | 请求直接进行文本生成。需携带`message`(任何文本)。 | +| **`REQUEST_GENERATE_TITLE`** | `TITLE_GENERATION` | 前端 | 请求为查询生成标题。需携带`message`(查询语句或描述)。 | +| **`REQUEST_GUESS_COMMENT`** | `NL_2_COMMENT` | 前端 | 请求猜测表/字段注释。需携带`message`(表名、字段名或SQL)。 | + +**关键属性**:所有上述事件均隐含一个由前端通过`tables`请求参数传递的**`tableNames`列表**。此属性是驱动核心分支逻辑的关键。 + +### 2.2 系统内部决策与处理事件 + +这些事件通常由状态机在特定状态的**动作(Action)** 或**守卫(Guard)** 中自动触发,而非前端直接调用。 + +| 事件 | 触发条件 | 含义与作用 | +| ------------------------- | ------------------------------------------------------------ | -------------------------------------------- | +| **`TABLES_PROVIDED`** | 守卫`hasTablesGuard()`检测到`tableNames`列表有效(非空)。 | 表示用户已明确提供表名,流程应跳过自动选表。 | +| **`TABLES_NOT_PROVIDED`** | 守卫`hasTablesGuard()`检测到`tableNames`列表无效(空或未提供)。 | 表示用户未提供表名,流程需进入自动选表阶段。 | +| **`TABLES_NOT_NEEDED`** | PromptType 为 `TEXT_GENERATION` 或 `TITLE_GENERATION`。 | 表示不需要表信息,直接构建 Prompt(跳过选表和获取 Schema)。 | +| **`AUTO_SELECT_DONE`** | 在`AUTO_SELECTING_TABLES`状态中,AI自动选表逻辑完成。 | 驱动状态离开"自动选表"状态,进入下一阶段。 | +| **`SCHEMA_FETCHED`** | 在`FETCHING_TABLE_SCHEMA`状态中,成功获取到指定表的DDL信息。 | 驱动状态进入Prompt构建阶段。 | +| **`PROMPT_BUILT`** | 在`BUILDING_PROMPT`状态中,最终Prompt构建完成。 | 驱动状态进入AI流式调用阶段。 | +| **`STREAM_FINISHED`** | 在`STREAMING`状态中,AI流式响应全部完成。 | 驱动状态进入完成终态。 | +| **`PROMPT_BUILD_FAILED`** | 在`BUILDING_PROMPT`等状态中,构建Prompt时发生错误。 | 驱动状态进入错误终态。 | +| **`AI_CALL_FAILED`** | 在`STREAMING`状态中,调用AI服务失败。 | 驱动状态进入错误终态。 | + +## 三、核心分支逻辑详解 + +状态机的分支逻辑主要由**初始路由决策**和**后续自动流转**两部分构成。 + +### 3.1 初始路由决策(基于`tables`参数) + +这是最主要的分支点,发生在状态机从`IDLE`状态接收到任意一个`REQUEST_*`事件时。 + +```mermaid +flowchart TD + A["IDLE (初始状态)"] --> B{"收到前端请求
    确定 PromptType"} + B --> C{"PromptType 类型"} + C -- "TEXT_GENERATION
    TITLE_GENERATION" --> E["触发事件: TABLES_NOT_NEEDED"] + E --> F["进入状态: BUILDING_PROMPT
    (跳过选表和获取 Schema)"] + C -- "其他类型" --> G{"检查 tableNames
    是否有效?"} + G -- 是 --> H["触发事件: TABLES_PROVIDED"] + H --> I["进入状态: FETCHING_TABLE_SCHEMA
    (跳过自动选表)"] + G -- 否 --> J["触发事件: TABLES_NOT_PROVIDED"] + J --> K["进入状态: AUTO_SELECTING_TABLES
    (需自动选表)"] +``` + +**逻辑说明**: + +1. **PromptType 分类决策**: + - `TEXT_GENERATION`、`TITLE_GENERATION`:不需要表信息,直接构建 Prompt + - 其他类型:根据 `tableNames` 是否提供决定后续流程 + +2. **分支条件**:取决于前端请求中 `tables` 参数是否提供了有效的表名列表。 + +3. **结果**: + - **TABLES_NOT_NEEDED**:直接进入 `BUILDING_PROMPT`,流程最短 + - **TABLES_PROVIDED**:直接进入 `FETCHING_TABLE_SCHEMA`,跳过自动选表 + - **TABLES_NOT_PROVIDED**:进入 `AUTO_SELECTING_TABLES`,启动 AI 选表子流程 + +### 3.2 后续自动流转路径 + +初始分支之后,状态机将沿着预设路径自动运行,直至完成。 + +```mermaid +flowchart TD + subgraph 主流程 + direction TB + S1["AUTO_SELECTING_TABLES
    (自动选表中)"] -- "AI选表完成
    触发: AUTO_SELECT_DONE" --> S2["FETCHING_TABLE_SCHEMA
    (获取表DDL)"] + S2 -- "DDL获取完成
    触发: SCHEMA_FETCHED" --> S3["BUILDING_PROMPT
    (构建最终Prompt)"] + S3 -- "Prompt构建完成
    触发: PROMPT_BUILT" --> S4["STREAMING
    (流式调用AI)"] + S4 -- "流式响应完成
    触发: STREAM_FINISHED" --> S5["COMPLETED
    (成功完成)"] + end + + subgraph 初始分支 + direction LR + IDLE -- "TEXT_GENERATION/TITLE_GENERATION
    触发: TABLES_NOT_NEEDED" --> S3 + IDLE -- "其他类型 + tables有效
    触发: TABLES_PROVIDED" --> S2 + IDLE -- "其他类型 + tables无效
    触发: TABLES_NOT_PROVIDED" --> S1 + end + + subgraph 异常分支 + S1 -. "选表失败
    触发: PROMPT_BUILD_FAILED" .-> ERR["FAILED"] + S2 -. "获取DDL失败
    触发: PROMPT_BUILD_FAILED" .-> ERR + S3 -. "构建Prompt失败
    触发: PROMPT_BUILD_FAILED" .-> ERR + S4 -. "AI调用失败
    触发: AI_CALL_FAILED" .-> ERR + end +``` + +**路径说明**: + +1. **直接构建路径**(TABLES_NOT_NEEDED):`IDLE` -> `BUILDING_PROMPT` -> `STREAMING` -> `COMPLETED`(流程最短,适用于文本生成) +2. **自动选表路径**(TABLES_NOT_PROVIDED):`IDLE` -> `AUTO_SELECTING_TABLES` -> `FETCHING_TABLE_SCHEMA` -> ... +3. **直连路径**(TABLES_PROVIDED):`IDLE` -> `FETCHING_TABLE_SCHEMA` -> ... +4. **共同路径**:`FETCHING_TABLE_SCHEMA` 和 `BUILDING_PROMPT` 后的流程完全一致:构建 Prompt -> 流式输出 -> 完成。 +5. **异常路径**:在任何非终态,如果对应的业务操作失败,应触发相应的失败事件(如 `PROMPT_BUILD_FAILED`),使状态机跳转到 `FAILED` 终态,便于统一错误处理。 + +## 四、完整状态转换图 (Mermaid) + +以下图表综合了所有状态、事件和分支逻辑。 + +```mermaid +stateDiagram-v2 + [*] --> IDLE + + note right of IDLE + 前端触发请求(如 REQUEST_NL_TO_SQL) + 根据 PromptType 和 tables 参数决定初始事件: + - TEXT_GENERATION/TITLE_GENERATION: TABLES_NOT_NEEDED + - 其他类型 + tables有效: TABLES_PROVIDED + - 其他类型 + tables无效: TABLES_NOT_PROVIDED + end note + + IDLE --> BUILDING_PROMPT: TABLES_NOT_NEEDED + IDLE --> FETCHING_TABLE_SCHEMA: TABLES_PROVIDED + IDLE --> AUTO_SELECTING_TABLES: TABLES_NOT_PROVIDED + + state 自动选表子流程 { + AUTO_SELECTING_TABLES --> FETCHING_TABLE_SCHEMA: AUTO_SELECT_DONE + } + + state 核心处理流程 { + FETCHING_TABLE_SCHEMA --> BUILDING_PROMPT: SCHEMA_FETCHED + BUILDING_PROMPT --> STREAMING: PROMPT_BUILT + STREAMING --> COMPLETED: STREAM_FINISHED + } + + AUTO_SELECTING_TABLES --> FAILED: AUTO_SELECT_FAILED + FETCHING_TABLE_SCHEMA --> FAILED: FETCH_SCHEMA_FAILED + BUILDING_PROMPT --> FAILED: PROMPT_BUILD_FAILED + STREAMING --> FAILED: AI_CALL_FAILED + + COMPLETED --> [*] + FAILED --> [*] +``` + +**图例解读**: + +1. **实线箭头**:代表正常的状态转换,由对应的事件触发。 +2. **虚线箭头**:代表异常的状态转换,由失败事件触发。 +3. **注释框**:解释了在 `IDLE` 状态,根据 PromptType 和 tables 参数如何决定初始事件。 +4. **状态分组**:将"自动选表"和"核心处理"分别用子状态框表示,突出了流程的模块化。 +5. **三条路径**: + - **TABLES_NOT_NEEDED**:直接进入 BUILDING_PROMPT(最短路径) + - **TABLES_PROVIDED**:跳过自动选表,直接获取 Schema + - **TABLES_NOT_PROVIDED**:先自动选表,再获取 Schema \ No newline at end of file diff --git a/docs/TEST.md b/docs/TEST.md new file mode 100644 index 000000000..5becc8fca --- /dev/null +++ b/docs/TEST.md @@ -0,0 +1,99 @@ +# SQL 智能补全测试用例 + +## 测试数据准备 + +为了测试表别名补全功能,需要确保数据库中有表。可以使用以下 SQL 创建测试表: + +```sql +CREATE TABLE test_users ( + id BIGINT PRIMARY KEY COMMENT '用户 ID', + username VARCHAR(50) NOT NULL COMMENT '用户名', + email VARCHAR(100) COMMENT '邮箱', + created_at DATETIME COMMENT '创建时间', + updated_at DATETIME COMMENT '更新时间' +) COMMENT '测试用户表'; + +CREATE TABLE test_orders ( + id BIGINT PRIMARY KEY COMMENT '订单 ID', + user_id BIGINT NOT NULL COMMENT '用户 ID', + amount DECIMAL(10,2) COMMENT '订单金额', + status INT COMMENT '订单状态', + created_at DATETIME COMMENT '创建时间' +) COMMENT '测试订单表'; +``` + +## 测试 SQL 示例 + +```sql +-- 测试 1:表别名补全 +select * from test_users t where t. + +-- 测试 2:直接字段名补全 +select * from test_users. + +-- 测试 3:多表 JOIN +select u.username, o.amount +from test_users u +join test_orders o on u.id = o. + +-- 测试 4:WHERE 条件 +select * from test_users where + +-- 测试 5:ORDER BY +select * from test_users order by + +-- 测试 6:GROUP BY +select username, count(*) from test_users group by +``` + +## 问题排查 + +### 问题 1:补全列表不弹出 + +**可能原因:** +- boundInfo 未正确传递 +- 后端 API 调用失败 +- SQL 解析失败 + +**排查步骤:** +1. 打开浏览器控制台,查看是否有错误日志 +2. 检查 Network 面板,查看 API 调用是否成功 +3. 检查 boundInfo 是否正确传递到 MonacoEditor + +### 问题 2:补全列表为空 + +**可能原因:** +- 数据库中没有表 +- API 返回数据格式不正确 +- 表名解析失败 + +**排查步骤:** +1. 检查数据库中是否有表 +2. 检查 `/api/rdb/table/column_list` API 返回数据 +3. 检查 tableInfo 参数是否正确 + +### 问题 3:只补全表名,不补全字段 + +**可能原因:** +- syntax-parser 未正确初始化 +- onSuggestTableFields 回调未正确实现 + +**排查步骤:** +1. 检查 MonacoEditor 是否收到 boundInfo +2. 检查 initSqlAutocomplete 是否被调用 +3. 检查 monacoSqlAutocomplete 的 opts 配置 + +## 成功标准 + +✅ 所有测试用例通过 +✅ 表别名补全功能正常工作 +✅ 无明显性能问题 +✅ 无控制台错误 +✅ Hover 提示正常显示 + +## 后续改进 + +1. 添加缓存机制,减少 API 调用 +2. 优化补全项排序 +3. 支持更多数据库类型 +4. 添加单元测试 diff --git a/docs/TREE_SEARCH_OPTIMIZATION_PLAN.md b/docs/TREE_SEARCH_OPTIMIZATION_PLAN.md new file mode 100644 index 000000000..4a12b3a3d --- /dev/null +++ b/docs/TREE_SEARCH_OPTIMIZATION_PLAN.md @@ -0,0 +1,1581 @@ +# Chat2DB 工作台树搜索优化方案 + +> **文档版本**: v1.0 +> **创建日期**: 2026-04-11 +> **作者**: AI Agent +> **状态**: 待评审 + +--- + +## 目录 + +- [一、背景与问题](#一背景与问题) +- [二、方案概述](#二方案概述) +- [三、当前架构分析](#三当前架构分析) +- [四、整体架构图](#四整体架构图) +- [五、时序图](#五时序图) + - [5.1 初始化加载时序图](#51-初始化加载时序图) + - [5.2 搜索时序图(后端模式)](#52-搜索时序图后端模式) + - [5.3 数据源切换时序图](#53-数据源切换时序图) +- [六、流程图](#六流程图) + - [6.1 总体实施流程](#61-总体实施流程) + - [6.2 索引更新流程](#62-索引更新流程) + - [6.3 搜索执行流程](#63-搜索执行流程) + - [6.4 前端树重构流程](#64-前端树重构流程) +- [七、详细设计方案](#七详细设计方案) + - [7.1 数据模型改造](#71-数据模型改造) + - [7.2 Service 层改造](#72-service-层改造) + - [7.3 API 层改造](#73-api-层改造) + - [7.4 前端改造](#74-前端改造) +- [八、数据存储架构](#八数据存储架构) +- [九、实施计划](#九实施计划) +- [十、风险与应对](#十风险与应对) +- [十一、成功标准](#十一成功标准) +- [十二、待确认问题](#十二待确认问题) + +--- + +## 一、背景与问题 + +### 1.1 当前问题 + +**问题描述**:工作台树搜索存在以下问题: + +1. **数据源切换后搜索不刷新** + - 前端树过滤 useEffect 只监听 `searchValue`,不监听 `treeData` + - 切换数据源后,`searchTreeData` 仍是旧数据源的过滤结果 + - **Bug 触发位置**: `Tree/index.tsx:167-175` + +2. **前端全量过滤性能差** + - 表数量 > 500 时,前端 DFS 过滤响应慢 + - 需要先加载所有表到内存,加载时间长 + +3. **视图/函数/存储过程不支持搜索** + - View/Function/Procedure 没有使用 Lucene 索引 + - 每次从数据库实时查询,不支持 `searchKey` 参数 + - 返回全量数据,性能差 + +### 1.2 优化目标 + +- ✅ 修复数据源切换后搜索不刷新的 Bug +- ✅ 支持后端搜索(大数据量场景) +- ✅ 支持视图、函数、存储过程的搜索 +- ✅ 支持树自动展开路径 +- ✅ 性能优化:搜索响应时间 < 500ms + +--- + +## 二、方案概述 + +### 2.1 核心思路 + +**方案 A**:扩展 Lucene 索引支持 + +1. 让 `Function`/`Procedure`/`Trigger` 实现 `IndexModel` 接口 +2. 在对应 Service 中引入 `LuceneIndexManager` +3. 复用现有的缓存和搜索基础设施 +4. 新增统一的树搜索接口 + +### 2.2 搜索模式决策 + +``` +数据量判断: +├─ treeData.length < 200 → 前端 DFS 过滤(快速) +└─ treeData.length >= 200 → 后端 Lucene 搜索(大数据量) +``` + +--- + +## 三、当前架构分析 + +### 3.1 数据存储层次结构 + +``` +物理存储位置: +└─ ~/.chat2db/index/{dataSourceId}_dev/ (Lucene 索引文件) + +逻辑存储结构: +┌─ Table ✅ 使用 Lucene 索引 ✅ 支持搜索 +├─ TableColumn ✅ 使用 Lucene 索引 ✅ 支持搜索 +├─ ForeignKey ✅ 使用 Lucene 索引 ✅ 支持搜索 +├─ View ❌ 直接查询数据库 ❌ 不支持搜索 +├─ Function ❌ 直接查询数据库 ❌ 不支持搜索 +├─ Procedure ❌ 直接查询数据库 ❌ 不支持搜索 +└─ Trigger ❌ 直接查询数据库 ❌ 不支持搜索 +``` + +### 3.2 详细对比 + +| 对象类型 | 存储位置 | 索引支持 | 搜索支持 | Service 实现 | +|---------|---------|---------|---------|-------------| +| **Table** | Lucene 索引文件 | ✅ 有 | ✅ 支持 | TableServiceImpl + LuceneIndexManager | +| **TableColumn** | Lucene 索引文件 | ✅ 有 | ✅ 支持 | TableServiceImpl + LuceneIndexManager | +| **ForeignKey** | Lucene 索引文件 | ✅ 有 | ✅ 支持 | TableServiceImpl + LuceneIndexManager | +| **View** | 数据库元数据 | ❌ 无 | ❌ 不支持 | ViewServiceImpl (直接查询) | +| **Function** | 数据库元数据 | ❌ 无 | ❌ 不支持 | FunctionServiceImpl (直接查询) | +| **Procedure** | 数据库元数据 | ❌ 无 | ❌ 不支持 | ProcedureServiceImpl (直接查询) | +| **Trigger** | 数据库元数据 | ❌ 无 | ❌ 不支持 | 无 Service | + +### 3.3 代码证据 + +**表使用 Lucene 索引** (`TableServiceImpl.java:428-440`): +```java +private void loadAndCacheMetadata(LuceneIndexManager
    mgr, TablePageQueryParam param, Long version) { + mgr.getLock().writeLock().lock(); + try { + Connection conn = Chat2DBContext.getConnection(); + MetaData meta = Chat2DBContext.getMetaData(); + List
    tables = meta.tables(conn, param.getDatabaseName(), param.getSchemaName(), null); + mgr.updateDocuments(tables, version); // ✅ 存入 Lucene 索引 + } catch (Exception e) { + log.error("loadAndCacheMetadata error,version:{}", version, e); + } finally { + mgr.getLock().writeLock().unlock(); + } +} +``` + +**视图直接查询数据库** (`ViewServiceImpl.java:14-18`): +```java +@Override +public ListResult
    views(String databaseName, String schemaName) { + return ListResult.of( + Chat2DBContext.getMetaData().views( + Chat2DBContext.getConnection(), + databaseName, + schemaName + ) + ); // ❌ 直接查询,无缓存 +} +``` + +--- + +## 四、整体架构图 + +``` +┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ +│ 前端层 │ │ 后端层 │ │ 数据库层 │ +│ (React + TS) │ │ (Spring Boot) │ │ (Metadata) │ +└────────┬─────────┘ └────────┬─────────┘ └────────┬─────────┘ + │ │ │ + │ 1. 输入搜索词 │ │ + │───────────────────────────>│ │ + │ │ │ + │ │ 2. 判断搜索模式 │ + │ │ (数据量 < 200?) │ + │ │ │ + │ │ ├─ YES ─┐ │ + │ │ │ │ │ + │ │ 3a. 前端 DFS 过滤 │ + │ │ (searchTree()) │ + │ │ │ + │ │ ├─ NO ──┐ │ + │ │ │ │ │ + │ │ 3b. 后端搜索 API │ + │<───────────────────────────│ /api/rdb/tree/search │ + │ 4. 返回搜索结果 │ │ + │ (扁平化 + parentPath) │ 4. LuceneIndexManager │ + │ │ ├─ Table 索引 │ + │ │ ├─ View 索引 (新增) │ + │ │ ├─ Function 索引 (新增)│ + │ │ ├─ Procedure 索引 (新增)│ + │ │ └─ Trigger 索引 (新增) │ + │ │ │ + │ │ 5. meta.views() │ + │ │ meta.functions() │ + │ │ meta.procedures() │ + │ │ meta.triggers() │ + │ │──────────────────────────>│ + │ │ │ + │ │ 6. 返回元数据 │ + │ │<──────────────────────────│ + │ │ │ + │ │ 7. updateDocuments() │ + │ │ (写入 Lucene 索引) │ + │ │ │ + │ 5. 构建树结构 │ 8. 返回搜索结果 │ + │ (buildTreeFromFlatData) │<──────────────────────────│ + │ 自动展开路径 │ │ + │ │ │ +``` + +--- + +## 五、时序图 + +### 5.1 初始化加载时序图 + +``` +┌─────────────┐ ┌──────────────┐ ┌─────────────────┐ ┌──────────────┐ ┌─────────────┐ +│ User │ │ Tree Component│ │ LuceneManagerFactory│ │ LuceneManager│ │ Database │ +└──────┬──────┘ └──────┬───────┘ └────────┬────────┘ └──────┬───────┘ └──────┬──────┘ + │ │ │ │ │ + │ 1. 连接数据源 │ │ │ │ + │───────────────>│ │ │ │ + │ │ │ │ │ + │ │ 2. getManager(id) │ │ │ + │ │──────────────────>│ │ │ + │ │ │ │ │ + │ │ │ 3. 创建/获取 │ │ + │ │ │ LuceneIndexManager│ │ + │ │ │────────────────>│ │ + │ │ │ │ │ + │ │ 4. 返回 manager │ │ │ + │ │<──────────────────│ │ │ + │ │ │ │ │ + │ │ 5. getMaxVersion │ │ │ + │ │────────────────────────────────────>│ │ + │ │ │ │ │ + │ │ │ 6. 查询 Lucene │ │ + │ │ │ version │ │ + │ │ │────────────────>│ │ + │ │ │ │ │ + │ │ 7. 返回 version=null│ │ │ + │ │<────────────────────────────────────│ │ + │ │ (首次连接,无缓存) │ │ │ + │ │ │ │ │ + │ │ 8. loadAndCacheMetadata() │ │ + │ │────────────────────────────────────>│ │ + │ │ │ │ │ + │ │ │ 9. meta.tables()│ │ + │ │ │────────────────>│───────────────>│ + │ │ │ │ │ + │ │ │ 10. 返回表元数据│ │ + │ │ │<────────────────│<───────────────│ + │ │ │ │ │ + │ │ │ 11. meta.views()│ │ + │ │ │────────────────>│───────────────>│ + │ │ │ │ │ + │ │ │ 12. 返回视图元数据│ │ + │ │ │<────────────────│<───────────────│ + │ │ │ │ │ + │ │ │ 13. meta.functions()│ │ + │ │ │────────────────>│───────────────>│ + │ │ │ │ │ + │ │ │ 14. 返回函数元数据│ │ + │ │ │<────────────────│<───────────────│ + │ │ │ │ │ + │ │ │ 15. meta.procedures()│ │ + │ │ │────────────────>│───────────────>│ + │ │ │ │ │ + │ │ │ 16. 返回存储过程元数据│ │ + │ │ │<────────────────│<───────────────│ + │ │ │ │ │ + │ │ │ 17. updateDocuments()│ │ + │ │ │ (Table/View/Function/Procedure) │ + │ │ │────────────────>│ │ + │ │ │ │ │ + │ │ │ 18. 写入 Lucene 索引│ │ + │ │ │────────────────>│ │ + │ │ │ │ │ + │ │ 19. 缓存完成 │ │ │ + │ │<────────────────────────────────────│ │ + │ │ │ │ │ + │ 20. 树加载完成 │ │ │ │ + │<──────────────│ │ │ │ +``` + +--- + +### 5.2 搜索时序图(后端模式) + +``` +┌─────────────┐ ┌──────────────┐ ┌───────────────┐ ┌──────────────┐ ┌─────────────┐ +│ User │ │ Tree Component│ │ Search API │ │LuceneManager │ │ Database │ +└──────┬──────┘ └──────┬───────┘ └───────┬───────┘ └──────┬───────┘ └──────┬──────┘ + │ │ │ │ │ + │ 1. 输入搜索词 │ │ │ │ + │───────────────>│ │ │ │ + │ │ │ │ │ + │ │ 2. useEffect 触发│ │ │ + │ │ (searchValue + treeData)│ │ │ + │ │ │ │ │ + │ │ 3. 判断:数据量 >= 200│ │ │ + │ │─────────────────>│ │ │ + │ │ │ │ │ + │ │ 4. POST /api/rdb/tree/search │ │ + │ │ {searchKey, treeNodeType} │ │ + │ │─────────────────────────────────>│ │ + │ │ │ │ │ + │ │ │ 5. 根据类型分发│ │ + │ │ │───────────────>│ │ + │ │ │ │ │ + │ │ │ 6. search() │ │ + │ │ │ (Lucene 全文检索)│ │ + │ │ │───────────────>│ │ + │ │ │ │ │ + │ │ │ 7. 构建 BooleanQuery│ │ + │ │ │ - type:VIEW │ │ + │ │ │ - databaseName│ │ + │ │ │ - schemaName │ │ + │ │ │ - searchKey │ │ + │ │ │ │ │ + │ │ │ 8. searcher.search()│ │ + │ │ │───────────────>│ │ + │ │ │ │ │ + │ │ │ 9. 返回 ScoreDocs│ │ + │ │ │<───────────────│ │ + │ │ │ │ │ + │ │ │ 10. 映射为 TreeNode│ │ + │ │ │ - 添加 parentPath│ │ + │ │ │ - 添加 extraParams│ │ + │ │ │ │ │ + │ │ 11. 返回 TreeNodeVO List│ │ │ + │ │<─────────────────────────────────│ │ + │ │ │ │ │ + │ │ 12. buildTreeFromFlatData() │ │ + │ │ - 创建路径节点 │ │ + │ │ - 构建父子关系 │ │ + │ │ - 自动展开匹配路径 │ │ + │ │ │ │ │ + │ │ 13. 渲染搜索结果│ │ │ + │ │ (高亮 + 可展开) │ │ │ + │ │ │ │ │ +``` + +--- + +### 5.3 数据源切换时序图 + +``` +┌─────────────┐ ┌──────────────┐ ┌───────────────┐ ┌──────────────┐ +│ User │ │ TableList │ │ Tree Component│ │ Backend │ +└──────┬──────┘ └──────┬───────┘ └───────┬───────┘ └──────┬───────┘ + │ │ │ │ + │ 1. 切换数据源 │ │ │ + │───────────────>│ │ │ + │ │ │ │ + │ │ 2. currentConnectionDetails 变化 │ + │ │─────────────────>│ │ + │ │ │ │ + │ │ 3. getTreeData() │ │ + │ │─────────────────>│ │ + │ │ │ │ + │ │ 4. getChildren API│ │ + │ │────────────────────────────────────>│ + │ │ │ │ + │ │ 5. 返回新数据源树│ │ + │ │<────────────────────────────────────│ + │ │ │ │ + │ │ 6. setTreeData(newData)│ │ + │ │─────────────────>│ │ + │ │ │ │ + │ │ │ 7. useEffect 触发│ + │ │ │ (treeData 变化)│ + │ │ │ │ + │ │ │ 8. searchValue 仍有效│ + │ │ │ │ + │ │ │ 9. 重新执行搜索逻辑│ + │ │ │ │ + │ │ │ ├─ 前端搜索 │ + │ │ │ │ 或 │ + │ │ │ ├─ 后端搜索 API │ + │ │ │ │ + │ │ │ 10. 显示新数据源│ + │ │ │ 搜索结果 │ + │ │ │ │ + │ │ │ ✅ BUG 修复完成 │ + │ │ │ │ +``` + +--- + +## 六、流程图 + +### 6.1 总体实施流程 + +``` +┌──────────────────────┐ +│ 阶段 1: 数据模型改造 │ +└──────────┬───────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ 1.1 Function 实现 IndexModel 接口 │ +│ - 添加 version 字段 │ +│ - 添加 name/comment/aiComment 字段 │ +│ - 实现 getClassType() 方法 │ +└──────────────────┬──────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ 1.2 Procedure 实现 IndexModel 接口 │ +│ - 添加 version 字段 │ +│ - 添加 name/comment/aiComment 字段 │ +│ - 实现 getClassType() 方法 │ +└──────────────────┬──────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ 1.3 Trigger 实现 IndexModel 接口 │ +│ - 添加 version 字段 │ +│ - 添加 name/comment/aiComment 字段 │ +│ - 实现 getClassType() 方法 │ +└──────────────────┬──────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ 1.4 View 已实现 IndexModel (Table) │ +│ - 无需改造(View 返回 Table 类型) │ +└──────────────────┬──────────────────────┘ + │ + ▼ +┌──────────────────────┐ +│ 阶段 2: Service 改造 │ +└──────────┬───────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ 2.1 FunctionServiceImpl 改造 │ +│ - 注入 LuceneIndexManagerFactory │ +│ - 新增 loadAndCacheMetadata() │ +│ - 新增 searchTreeNodes() 方法 │ +│ - functions() 方法使用索引 │ +└──────────────────┬──────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ 2.2 ProcedureServiceImpl 改造 │ +│ - 注入 LuceneIndexManagerFactory │ +│ - 新增 loadAndCacheMetadata() │ +│ - 新增 searchTreeNodes() 方法 │ +│ - procedures() 方法使用索引 │ +└──────────────────┬──────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ 2.3 ViewServiceImpl 改造 │ +│ - 注入 LuceneIndexManagerFactory │ +│ - 新增 loadAndCacheMetadata() │ +│ - 新增 searchTreeNodes() 方法 │ +│ - views() 方法使用索引 │ +└──────────────────┬──────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ 2.4 Trigger 新增 TriggerServiceImpl │ +│ - 创建 TriggerService 接口 │ +│ - 创建 TriggerServiceImpl 实现 │ +│ - 支持 Lucene 索引 │ +└──────────────────┬──────────────────────┘ + │ + ▼ +┌──────────────────────┐ +│ 阶段 3: API 层改造 │ +└──────────┬───────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ 3.1 新增 TreeSearchRequest │ +│ - dataSourceId │ +│ - databaseName/schemaName │ +│ - searchKey │ +│ - treeNodeType (TABLE/VIEW/...) │ +│ - pageSize │ +└──────────────────┬──────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ 3.2 新增 TreeNodeVO │ +│ - uuid/key/name │ +│ - treeNodeType │ +│ - parentPath │ +│ - extraParams │ +└──────────────────┬──────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ 3.3 新增 TreeSearchParam │ +│ - 业务参数对象 │ +│ - 用于 Service 层调用 │ +└──────────────────┬──────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ 3.4 新增/改造 Controller │ +│ ├─ TableController.searchTree() │ +│ ├─ ViewController.searchTree() │ +│ ├─ FunctionController.searchTree()│ +│ ├─ ProcedureController.searchTree()│ +│ └─ 或统一入口 TreeController │ +└──────────────────┬──────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ 3.5 新增 Converter │ +│ - toTreeSearchParam() │ +│ - treeNode2vo() │ +└──────────────────┬──────────────────────┘ + │ + ▼ +┌──────────────────────┐ +│ 阶段 4: 前端改造 │ +└──────────┬───────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ 4.1 新增 API 调用 │ +│ - searchTree(): ITreeSearchParams │ +│ - 返回 ITreeNodeResponse[] │ +└──────────────────┬──────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ 4.2 Tree 组件改造 │ +│ - 新增 backendSearchData 状态 │ +│ - 新增 useEffect 调用后端搜索 │ +│ - 依赖添加 treeData │ +│ - 修复切换数据源 BUG │ +└──────────────────┬──────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ 4.3 树重构算法 │ +│ - buildTreeFromFlatData() │ +│ - 根据 parentPath 创建路径节点 │ +│ - 构建父子关系 │ +│ - 自动展开匹配路径 │ +└──────────────────┬──────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ 4.4 搜索模式决策 │ +│ - treeData.length < 200 → 前端 │ +│ - treeData.length >= 200 → 后端 │ +└──────────────────┬──────────────────────┘ + │ + ▼ +┌──────────────────────┐ +│ 阶段 5: 测试优化 │ +└──────────┬───────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ 5.1 单元测试 │ +│ - Function/Procedure/Trigger 索引 │ +│ - 搜索功能测试 │ +│ - 缓存刷新测试 │ +└──────────────────┬──────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ 5.2 集成测试 │ +│ - 前端搜索 UI 测试 │ +│ - 数据源切换测试 │ +│ - 性能测试 │ +└──────────────────┬──────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ 5.3 优化 │ +│ - 搜索防抖 │ +│ - 结果缓存 │ +│ - 高亮显示 │ +│ - 分页加载 │ +└─────────────────────────────────────────┘ +``` + +--- + +### 6.2 索引更新流程 + +``` +┌──────────────────────┐ +│ 1. 连接数据库 │ +└──────────┬───────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ 2. 获取 LuceneIndexManager │ +│ managerFactory.getManager(dataSourceId)│ +└──────────────────┬──────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ 3. 检查缓存版本 │ +│ version = mgr.getMaxVersion(param) │ +└──────────────────┬──────────────────────┘ + │ + ▼ + ┌─────────────────────┐ + │ version == null ? │ + └────────┬────────────┘ + │ + ┌─────────┴─────────┐ + │ YES │ NO + ▼ ▼ +┌──────────────────┐ ┌──────────────────┐ +│ 4. 首次加载 │ │ 4. 检查 refresh 标志│ +│ 或缓存失效 │ │ │ +└────────┬─────────┘ └────────┬─────────┘ + │ │ + └────────┬───────────┘ + │ + ┌─────────┴─────────┐ + │ refresh == true ? │ + └────────┬──────────┘ + │ + ┌────────┴────────┐ + │ YES │ NO + ▼ ▼ +┌─────────────────┐ ┌──────────────────┐ +│ 5. 加载元数据 │ │ 5. 使用现有索引 │ +│ 写锁保护 │ │ 直接返回 │ +└────────┬────────┘ └────────┬─────────┘ + │ │ + ▼ │ +┌─────────────────────────┐ │ +│ 6. 元数据获取 │ │ +│ ├─ meta.tables() │ │ +│ ├─ meta.views() │ │ +│ ├─ meta.functions() │ │ +│ ├─ meta.procedures() │ │ +│ └─ meta.triggers() │ │ +└────────┬────────────────┘ + │ + ▼ +┌─────────────────────────┐ +│ 7. 批量更新索引 │ +│ mgr.updateDocuments() │ +│ ├─ Table 列表 │ +│ ├─ View 列表 │ +│ ├─ Function 列表 │ +│ ├─ Procedure 列表 │ +│ └─ Trigger 列表 │ +└────────┬────────────────┘ + │ + ▼ +┌─────────────────────────┐ +│ 8. 设置版本号和 aiComment│ +│ source.setVersion(v) │ +│ source.setAiComment(...)│ +└────────┬────────────────┘ + │ + ▼ +┌─────────────────────────┐ +│ 9. 构建 Document │ +│ ├─ type 字段 │ +│ ├─ name 字段 (TextField)│ +│ ├─ comment (TextField) │ +│ ├─ aiComment (TextField)│ +│ ├─ databaseName │ +│ ├─ schemaName │ +│ ├─ tableName │ +│ └─ source (StoredField)│ +└────────┬────────────────┘ + │ + ▼ +┌─────────────────────────┐ +│ 10. writer.updateDocuments│ +│ (BooleanQuery, docs) │ +└────────┬────────────────┘ + │ + ▼ +┌─────────────────────────┐ +│ 11. reload() │ +│ 刷新 IndexReader │ +│ 刷新 IndexSearcher │ +└────────┬────────────────┘ + │ + ▼ +┌─────────────────────────┐ +│ 12. 释放写锁 │ +└────────┬────────────────┘ + │ + ▼ +┌─────────────────────────┐ +│ 13. 索引更新完成 │ +└─────────────────────────┘ +``` + +--- + +### 6.3 搜索执行流程 + +``` +┌──────────────────────┐ +│ 1. 接收搜索请求 │ +│ TreeSearchParam │ +└──────────┬───────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ 2. 获取 LuceneIndexManager │ +│ managerFactory.getManager(id) │ +└──────────────────┬──────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ 3. 检查/刷新缓存 │ +│ needRefreshCache ? loadMetadata() │ +└──────────────────┬──────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ 4. 构建搜索查询 │ +│ buildSearchQuery(param, searchKey) │ +└──────────────────┬──────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ 5. 构建 BooleanQuery │ +│ ├─ type: Function/Procedure/... │ +│ ├─ databaseName (FILTER) │ +│ ├─ schemaName (FILTER) │ +│ └─ searchKey (MUST) │ +└──────────────────┬──────────────────────┘ + │ + ┌─────────┴─────────┐ + │ searchKey 是否为空│ + └────────┬──────────┘ + │ + ┌─────────┴─────────┐ + │ YES │ NO + ▼ ▼ +┌──────────────────┐ ┌──────────────────────┐ +│ 6a. MatchAllDocs │ │ 6b. MultiFieldQuery │ +│ 返回全部 │ │ ├─ name 字段 │ +│ │ │ ├─ comment 字段 │ +│ │ │ └─ aiComment 字段│ +└────────┬─────────┘ └──────────────────────┘ + │ │ + └────────┬───────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ 7. 执行搜索 │ +│ searcher.searchAfter(query, 1000) │ +└──────────────────┬──────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ 8. 返回 ScoreDocs │ +│ 包含 docId 和评分 │ +└──────────────────┬──────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ 9. 读取文档 │ +│ storedFields.document(docId) │ +└──────────────────┬──────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ 10. 解析 source 字段 │ +│ JSONObject.parseObject(source) │ +└──────────────────┬──────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ 11. 构建 TreeNode │ +│ ├─ 设置基本信息 │ +│ ├─ 构建 parentPath │ +│ │ ├─ databaseName │ +│ │ └─ schemaName (如果有) │ +│ └─ 设置 extraParams │ +└──────────────────┬──────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ 12. 返回 TreeNode List │ +└─────────────────────────────────────────┘ +``` + +--- + +### 6.4 前端树重构流程 + +``` +┌──────────────────────┐ +│ 1. 接收扁平化结果 │ +│ ITreeNodeResponse[]│ +│ (来自后端 API) │ +└──────────┬───────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ 2. 初始化数据结构 │ +│ map = new Map() │ +│ pathMap = new Map() │ +│ roots = [] │ +└──────────────────┬──────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ 3. 创建所有节点 │ +│ forEach item: │ +│ map.set(uuid, { │ +│ ...item, │ +│ children: [] │ +│ }) │ +└──────────────────┬──────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ 4. 收集路径节点 │ +│ forEach node: │ +│ if node.parentPath: │ +│ currentPath = "" │ +│ forEach pathItem: │ +│ currentPath += pathItem │ +│ if !pathMap.has(currentPath): │ +│ pathNode = { │ +│ uuid: path-xxx, │ +│ name: pathItem, │ +│ treeNodeType: 推断类型, │ +│ children: null, │ +│ extraParams: ... │ +│ } │ +│ pathMap.set(path, node) │ +└──────────────────┬──────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ 5. 构建路径树 │ +│ forEach pathNode in pathMap: │ +│ parentPath = 获取上层路径 │ +│ if parentPath exists: │ +│ parent = pathMap.get(parentPath)│ +│ parent.children.push(pathNode) │ +│ else: │ +│ roots.push(pathNode) │ +└──────────────────┬──────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ 6. 添加搜索结果到路径 │ +│ forEach node: │ +│ if node.parentPath: │ +│ parentKey = parentPath.join('/')│ +│ parent = pathMap.get(parentKey) │ +│ parent.children.push(node) │ +│ else: │ +│ roots.push(node) │ +└──────────────────┬──────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ 7. 标记展开状态 │ +│ forEach pathNode: │ +│ pathNode.expanded = true │ +│ (自动展开匹配路径) │ +└──────────────────┬──────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ 8. 返回树根节点集合 │ +│ return roots │ +└─────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ 9. 转平级用于虚拟滚动 │ +│ smoothTree(roots, result) │ +└──────────────────┬──────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ 10. 渲染树节点 │ +│ virtualScroll.render(nodes) │ +└─────────────────────────────────────────┘ +``` + +--- + +## 七、详细设计方案 + +### 7.1 数据模型改造 + +#### Function.java(改造后) + +```java +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class Function implements IndexModel { // ✅ 实现 IndexModel + + @JsonAlias({"FUNCTION_NAME"}) + private String name; // ✅ 实现 IndexModel + + @JsonAlias({"REMARKS"}) + private String comment; // ✅ 实现 IndexModel + + private String aiComment; // ✅ 新增 + + private Long version; // ✅ 新增 + + @JsonAlias({"FUNCTION_CAT"}) + private String databaseName; + + @JsonAlias({"FUNCTION_SCHEM"}) + private String schemaName; + + @JsonAlias({"FUNCTION_TYPE"}) + private Short functionType; + + @JsonAlias({"SPECIFIC_NAME"}) + private String specificName; + + private String functionBody; + + // ✅ 实现 IndexModel 接口方法 + @Override + public String getTableName() { + return null; // 函数没有表名 + } + + @Override + public Class getClassType() { + return Function.class; + } + + @Override + public void setTableName(String tableName) { + // 不支持 + } +} +``` + +#### Procedure.java(改造后) + +```java +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class Procedure implements IndexModel { // ✅ 实现 IndexModel + + @JsonAlias({"PROCEDURE_NAME"}) + private String name; // ✅ 实现 IndexModel + + @JsonAlias({"REMARKS"}) + private String comment; // ✅ 实现 IndexModel + + private String aiComment; // ✅ 新增 + + private Long version; // ✅ 新增 + + @JsonAlias({"PROCEDURE_CAT"}) + private String databaseName; + + @JsonAlias({"PROCEDURE_SCHEM"}) + private String schemaName; + + // ... 其他字段不变 + + @Override + public String getTableName() { + return null; + } + + @Override + public Class getClassType() { + return Procedure.class; + } +} +``` + +#### Trigger.java(改造后) + +```java +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class Trigger implements IndexModel { // ✅ 实现 IndexModel + + private String name; // ✅ 实现 IndexModel + + private String comment; // ✅ 实现 IndexModel + + private String aiComment; // ✅ 新增 + + private Long version; // ✅ 新增 + + private String databaseName; + + private String schemaName; + + private String eventManipulation; + + private String triggerBody; + + @Override + public String getTableName() { + return null; + } + + @Override + public Class getClassType() { + return Trigger.class; + } +} +``` + +--- + +### 7.2 Service 层改造 + +#### FunctionServiceImpl.java(改造后) + +```java +@Service +@Slf4j +public class FunctionServiceImpl implements FunctionService { + + @Autowired + private LuceneIndexManagerFactory managerFactory; + + @Override + public ListResult functions(String databaseName, String schemaName) { + // 使用索引(如果有) + FunctionQueryParams params = FunctionQueryParams.builder() + .databaseName(databaseName) + .schemaName(schemaName) + .build(); + + LuceneIndexManager mgr = managerFactory.getManager(getDataSourceId()); + Long version = mgr.getMaxVersion(params); + + if (version == null) { + loadAndCacheMetadata(mgr, databaseName, schemaName, version); + } + + List functions = (List) mgr.search(params, null, null); + return ListResult.of(functions); + } + + /** + * 搜索函数树节点 + */ + public List searchTreeNodes(FunctionSearchParam param) { + LuceneIndexManager mgr = managerFactory.getManager(param.getDataSourceId()); + Long version = mgr.getMaxVersion(param); + + if (needRefreshCache(param, version)) { + loadAndCacheMetadata(mgr, param.getDatabaseName(), param.getSchemaName(), version); + } + + return (List) mgr.search(param, null, param.getSearchKey()); + } + + /** + * 加载并缓存元数据 + */ + private void loadAndCacheMetadata(LuceneIndexManager mgr, + String databaseName, + String schemaName, + Long version) { + mgr.getLock().writeLock().lock(); + try { + Connection conn = Chat2DBContext.getConnection(); + MetaData meta = Chat2DBContext.getMetaData(); + List functions = meta.functions(conn, databaseName, schemaName); + + // 设置版本和 AI 注释 + functions.forEach(f -> { + f.setVersion(version); + // 可以调用 AI 服务生成注释 + // f.setAiComment(aiService.generateComment(f)); + }); + + mgr.updateDocuments(functions, version); + } catch (Exception e) { + log.error("loadAndCacheMetadata error", e); + } finally { + mgr.getLock().writeLock().unlock(); + } + } +} +``` + +--- + +### 7.3 API 层改造 + +#### TreeSearchRequest.java(新建) + +```java +@Data +public class TreeSearchRequest extends DataSourceBaseRequestInfo { + + @NotNull + private Long dataSourceId; + + private String databaseName; + + private String schemaName; + + @NotBlank + private String searchKey; + + /** + * 树节点类型过滤 + * TABLE / VIEW / FUNCTION / PROCEDURE / TRIGGER + */ + private String treeNodeType; + + private Integer pageSize = 100; +} +``` + +#### TreeNodeVO.java(新建) + +```java +@Data +public class TreeNodeVO { + + private String uuid; + + private String key; + + private String name; + + private String treeNodeType; + + private String pretendNodeType; + + private String comment; + + private Boolean isLeaf; + + private Boolean pinned; + + /** + * 父路径(核心字段) + * 例如:["database_name", "schema_name"] + */ + private List parentPath; + + /** + * 扩展参数 + */ + private Map extraParams; +} +``` + +#### 统一搜索接口(TreeController.java 新建) + +```java +@Slf4j +@ConnectionInfoAspect +@RequestMapping("/api/rdb/tree") +@RestController +public class TreeController { + + @Autowired + private TableService tableService; + + @Autowired + private ViewService viewService; + + @Autowired + private FunctionService functionService; + + @Autowired + private ProcedureService procedureService; + + @Autowired + private RdbWebConverter rdbWebConverter; + + /** + * 统一树搜索接口 + */ + @GetMapping("/search") + public ListResult searchTree(@Valid TreeSearchRequest request) { + TreeSearchParam param = rdbWebConverter.toTreeSearchParam(request); + + List result = new ArrayList<>(); + + // 根据类型分发 + String type = request.getTreeNodeType(); + if (StringUtils.isBlank(type) || "ALL".equals(type)) { + // 搜索所有类型 + result.addAll(tableService.searchTreeNodes(param)); + result.addAll(viewService.searchTreeNodes(param)); + result.addAll(functionService.searchTreeNodes(param)); + result.addAll(procedureService.searchTreeNodes(param)); + } else { + switch (type) { + case "TABLE": + result.addAll(tableService.searchTreeNodes(param)); + break; + case "VIEW": + result.addAll(viewService.searchTreeNodes(param)); + break; + case "FUNCTION": + result.addAll(functionService.searchTreeNodes(param)); + break; + case "PROCEDURE": + result.addAll(procedureService.searchTreeNodes(param)); + break; + default: + log.warn("Unknown tree node type: {}", type); + } + } + + return ListResult.of(rdbWebConverter.treeNode2vo(result)); + } +} +``` + +--- + +### 7.4 前端改造 + +#### API 定义(src/service/sql.ts) + +```typescript +export interface ITreeSearchParams { + dataSourceId: number; + dataSourceName: string; + databaseType: DatabaseTypeCode; + databaseName?: string; + schemaName?: string; + searchKey: string; + treeNodeType?: string; + pageSize?: number; +} + +export interface ITreeNodeResponse { + uuid: string; + key: string; + name: string; + treeNodeType: TreeNodeType; + pretendNodeType?: TreeNodeType; + comment?: string; + isLeaf?: boolean; + pinned?: boolean; + parentPath?: string[]; + databaseName?: string; + schemaName?: string; + extraParams: { + dataSourceId: number; + dataSourceName: string; + databaseName?: string; + schemaName?: string; + tableName?: string; + functionName?: string; + procedureName?: string; + [key: string]: any; + }; +} + +const searchTree = createRequest( + '/api/rdb/tree/search', + { method: 'get' } +); +``` + +#### Tree 组件改造(src/blocks/Tree/index.tsx) + +```typescript +// 新增状态 +const [backendSearchData, setBackendSearchData] = useState(null); +const [backendSearchLoading, setBackendSearchLoading] = useState(false); + +const currentConnectionDetails = useWorkspaceStore((state) => state.currentConnectionDetails); + +// 新增 useEffect 调用后端搜索 +useEffect(() => { + if (searchValue && currentConnectionDetails?.id) { + // 决策:数据量 >= 200 使用后端搜索 + const useBackendSearch = treeData && treeData.length >= 200; + + if (useBackendSearch) { + setBackendSearchLoading(true); + searchTree({ + dataSourceId: currentConnectionDetails.id, + dataSourceName: currentConnectionDetails.alias, + databaseType: currentConnectionDetails.type, + searchKey: searchValue, + pageSize: 100, + }) + .then((res) => { + // 将后端返回的扁平数据转为树结构 + const treeData = buildTreeFromFlatData(res); + setBackendSearchData(treeData); + setScrollTop(0); + }) + .catch(() => { + setBackendSearchData([]); + }) + .finally(() => { + setBackendSearchLoading(false); + }); + } + } else { + setBackendSearchData(null); + } +}, [searchValue, treeData, currentConnectionDetails]); // ✅ 修复:添加 treeData 依赖 + +// 树重构算法 +function buildTreeFromFlatData(flatNodes: ITreeNode[]): ITreeNode[] { + const map = new Map(); + const pathMap = new Map(); + const roots: ITreeNode[] = []; + + // 1. 创建所有节点 + flatNodes.forEach(item => { + map.set(item.uuid, { ...item, children: [] }); + }); + + // 2. 收集路径节点 + flatNodes.forEach(node => { + if (node.parentPath && node.parentPath.length > 0) { + let currentPath = ''; + node.parentPath.forEach((pathItem, index) => { + const prevPath = currentPath; + currentPath = prevPath ? `${prevPath}/${pathItem}` : pathItem; + + if (!pathMap.has(currentPath)) { + const pathNode: ITreeNode = { + uuid: `path-${currentPath}`, + name: pathItem, + treeNodeType: getPathNodeType(index), + children: null, + extraParams: buildPathNodeExtraParams(node.extraParams, index), + }; + pathMap.set(currentPath, pathNode); + } + }); + } + }); + + // 3. 构建路径树 + pathMap.forEach((pathNode, path) => { + const pathParts = path.split('/'); + if (pathParts.length > 1) { + const parentPath = pathParts.slice(0, -1).join('/'); + const parentNode = pathMap.get(parentPath); + if (parentNode) { + parentNode.children = [...(parentNode.children || []), pathNode]; + } + } else { + roots.push(pathNode); + } + }); + + // 4. 添加搜索结果到路径 + flatNodes.forEach(node => { + if (node.parentPath && node.parentPath.length > 0) { + const parentKey = node.parentPath.join('/'); + const parentNode = pathMap.get(parentKey); + if (parentNode) { + parentNode.children = [...(parentNode.children || []), node]; + } + } else { + roots.push(node); + } + }); + + // 5. 标记展开状态 + pathMap.forEach(node => { + // 自动展开路径节点 + }); + + return roots; +} + +// 修改渲染逻辑 +const realNodeList = (backendSearchData || searchSmoothTreeData || smoothTreeData).slice(startIdx, startIdx + 50); +``` + +--- + +## 八、数据存储架构 + +### 8.1 Lucene 索引文件位置 + +``` +物理存储: +└─ ~/.chat2db/index/ + ├─ {dataSourceId}_dev/ + │ ├─ segments_* + │ ├─ write.lock + │ ├─ _0.cfs + │ └─ _1.cfs + └─ {dataSourceId}_test/ + └─ ... +``` + +**代码位置**:`LuceneIndexManager.java:127-137` + +```java +private String getIndexPath(Long id) { + String environment = StringUtils.defaultString(System.getProperty("spring.profiles.active"), "dev"); + String basePath = System.getProperty("user.home") + "/.chat2db/index/"; + switch (environment.toLowerCase()) { + case "test": + return basePath + id + "_test"; + case "dev": + default: + return basePath + id + "_dev"; + } +} +``` + +### 8.2 索引字段结构 + +``` +Document 结构: +├─ type (StringField) - 类型标识:Table/View/Function/Procedure +├─ name (TextField) - 名称(支持全文搜索) +├─ comment (TextField) - 注释(支持全文搜索) +├─ aiComment (TextField) - AI 注释(支持全文搜索) +├─ databaseName (StringField) - 数据库名 +├─ schemaName (StringField) - Schema 名 +├─ tableName (StringField) - 表名 +├─ version (NumericDocValuesField) - 版本号 +└─ source (StoredField) - JSON 序列化的完整对象 +``` + +--- + +## 九、实施计划 + +### 9.1 工作分解 + +| 阶段 | 任务 | 子任务 | 预估工时 | 优先级 | +|------|------|--------|----------|--------| +| **1** | **数据模型改造** | 1.1 Function 实现 IndexModel | 2h | P0 | +| | | 1.2 Procedure 实现 IndexModel | 2h | P0 | +| | | 1.3 Trigger 实现 IndexModel | 2h | P0 | +| | | 1.4 单元测试 | 1h | P0 | +| **2** | **Service 层改造** | 2.1 FunctionServiceImpl | 3h | P0 | +| | | 2.2 ProcedureServiceImpl | 3h | P0 | +| | | 2.3 ViewServiceImpl | 3h | P0 | +| | | 2.4 新增 TriggerServiceImpl | 4h | P1 | +| | | 2.5 集成测试 | 2h | P0 | +| **3** | **API 层改造** | 3.1 TreeSearchRequest | 1h | P0 | +| | | 3.2 TreeNodeVO | 1h | P0 | +| | | 3.3 TreeSearchParam | 1h | P0 | +| | | 3.4 TreeController | 3h | P0 | +| | | 3.5 Converter | 2h | P0 | +| **4** | **前端改造** | 4.1 API 定义 | 1h | P0 | +| | | 4.2 Tree 组件改造 | 4h | P0 | +| | | 4.3 树重构算法 | 3h | P0 | +| | | 4.4 搜索模式决策 | 2h | P0 | +| | | 4.5 UI 优化 | 2h | P1 | +| **5** | **测试优化** | 5.1 单元测试 | 3h | P0 | +| | | 5.2 集成测试 | 4h | P0 | +| | | 5.3 性能测试 | 2h | P1 | +| | | 5.4 Bug 修复 | 2h | P0 | +| **总计** | | | **46h** | | + +### 9.2 里程碑 + +``` +Week 1: 后端数据模型 + Service 改造 +├─ Day 1-2: Function/Procedure/Trigger 模型改造 +├─ Day 3-4: Service 层改造 +└─ Day 5: 单元测试 + Code Review + +Week 2: API 层 + 前端改造 +├─ Day 1-2: API 层改造(Request/VO/Controller) +├─ Day 3-4: 前端 Tree 组件改造 +└─ Day 5: 集成测试 + +Week 3: 测试优化 +├─ Day 1-2: 集成测试 + Bug 修复 +├─ Day 3: 性能优化 +└─ Day 4-5: 上线准备 +``` + +--- + +## 十、风险与应对 + +| 风险 | 影响 | 概率 | 应对措施 | +|------|------|------|----------| +| **数据模型不兼容** | 高 | 中 | 保持现有字段,新增可选字段;向后兼容 | +| **Lucene 索引过大** | 中 | 中 | 设置索引大小限制;定期清理旧索引 | +| **性能下降** | 高 | 低 | 性能测试;优化索引策略;缓存优化 | +| **前端渲染卡顿** | 中 | 中 | 虚拟滚动;分页加载;防抖优化 | +| **AI Comment 生成慢** | 低 | 高 | 异步生成;批量处理;降级处理 | + +--- + +## 十一、成功标准 + +### 11.1 功能标准 + +- ✅ View/Function/Procedure 支持 searchKey 搜索 +- ✅ 搜索结果自动展开父路径 +- ✅ 切换数据源后搜索自动刷新 +- ✅ 支持大数据量场景(>1000 条) +- ✅ 搜索响应时间 < 500ms + +### 11.2 性能标准 + +- ✅ 首次加载时间 < 3s +- ✅ 搜索响应时间 < 500ms +- ✅ 索引文件大小 < 100MB +- ✅ 内存占用 < 500MB + +### 11.3 体验标准 + +- ✅ 搜索输入防抖(300ms) +- ✅ Loading 状态提示 +- ✅ 搜索结果显示计数 +- ✅ 支持"加载更多"(分页) +- ✅ 高亮匹配关键词 + +--- + +## 附录 + +### A. 相关文件位置 + +**前端**: +- `chat2db-client/src/pages/main/workspace/components/TableList/index.tsx` +- `chat2db-client/src/pages/main/workspace/components/OperationLine/index.tsx` +- `chat2db-client/src/blocks/Tree/index.tsx` +- `chat2db-client/src/blocks/Tree/treeConfig.tsx` +- `chat2db-client/src/service/sql.ts` + +**后端**: +- `chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Table.java` +- `chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Function.java` +- `chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Procedure.java` +- `chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Trigger.java` +- `chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java` +- `chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ViewServiceImpl.java` +- `chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/FunctionServiceImpl.java` +- `chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ProcedureServiceImpl.java` +- `chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/LuceneIndexManager.java` +- `chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java` + +### B. Bug 修复代码 + +**修复位置**:`Tree/index.tsx:167` + +```typescript +// 修改前(Bug) +useEffect(() => { + if (searchValue && treeData) { + const _searchTreeData = searchTree(cloneDeep(treeData), searchValue); + setSearchTreeData(_searchTreeData); + setScrollTop(0); + } else { + setSearchTreeData(null); + } +}, [searchValue]); // ❌ 缺少 treeData 依赖 + +// 修改后(已修复) +useEffect(() => { + if (searchValue && treeData) { + const _searchTreeData = searchTree(cloneDeep(treeData), searchValue); + setSearchTreeData(_searchTreeData); + setScrollTop(0); + } else { + setSearchTreeData(null); + } +}, [searchValue, treeData]); // ✅ 添加 treeData 依赖 +``` + +--- + +**文档结束** \ No newline at end of file diff --git a/docs/ai-sql-fix-feature.md b/docs/ai-sql-fix-feature.md new file mode 100644 index 000000000..cc095b6d6 --- /dev/null +++ b/docs/ai-sql-fix-feature.md @@ -0,0 +1,66 @@ +# AI SQL错误修复功能 + +## 功能概述 + +当SQL执行出现错误时,在错误结果页面显示"AI修复"按钮。点击后,AI会分析错误原因并提供修复后的SQL。 + +## 使用流程 + +1. 执行SQL语句 +2. 如果执行失败,显示错误信息 +3. 点击"AI修复"按钮 +4. 自动切换到AI聊天面板 +5. AI分析错误并返回修复结果 +6. 显示修复后的SQL和说明 + +## 实现细节 + +### 前端修改 + +1. **SearchResult组件** (`src/components/SearchResult/index.tsx`) + - 添加 `handleAiFix` 函数 + - 错误显示区域添加"AI修复"按钮 + - 传递错误信息和原始SQL到AI + +2. **新增 PromptType**: `SQL_FIX` + - 在 `common.ts` 中添加类型定义 + - 添加 `ISqlFixResult` 接口 + +3. **AiChat组件** + - 添加 `extractSqlFixFromContent` JSON解析函数 + - 添加 `sqlFixCallbackRef` 回调 + - 在 `onDone` 中处理 `SQL_FIX` 类型结果 + +4. **国际化** + - 中文: `common.button.aiFix`: 'AI修复' + - 英文: `common.button.aiFix`: 'AI Fix' + +### 后端修改 + +1. **PromptType枚举** + - 新增 `SQL_FIX("SQL错误修复")` + +2. **prompt-templates.yml** + - 新增 `sql_fix` 模板 + - 占位符: `{error_message}`, `{original_sql}`, `{db_type}` + +3. **PromptBuilderImpl** + - 更新 `validateContext` 允许 SQL_FIX 不需要 message + - 更新 `fillTemplate` 处理 `{error_message}` 和 `{original_sql}` 占位符 + +## AI返回格式 + +```json +{ + "error_analysis": "错误原因分析", + "fixed_sql": "修复后的SQL语句", + "explanation": "修复说明", + "can_fix": true +} +``` + +## 样式 + +- 错误容器使用 flex 布局垂直居中 +- AI修复按钮显示在错误信息下方 +- 使用 Iconfont 图标(code: \ue6ae) diff --git a/docs/guess-feature-design.md b/docs/guess-feature-design.md new file mode 100644 index 000000000..04103fe14 --- /dev/null +++ b/docs/guess-feature-design.md @@ -0,0 +1,1019 @@ +# 生成数据和导入数据猜一猜功能设计 + +## 一、功能概述 + +参考 `NL_2_COMMENT` 的实现模式,为两个场景新增 prompt 类型: + +| 场景 | 新增 PromptType | 功能描述 | +|------|-----------------|----------| +| **导入数据** - 字段映射 | `NL_2_FIELD_MAPPING` | AI 智能推荐源文件字段到目标表字段的映射关系 | +| **生成数据** - 表达式 | `NL_2_DATA_EXPRESSION` | AI 智能推荐各字段的数据生成表达式(datafaker) | + +## 二、整体架构设计 + +两个功能都复用现有的 `/api/ai/chat` SSE 接口,通过新增 `promptType` 和对应的状态机动作来实现。 + +**核心复用组件:** +- `/api/ai/chat` SSE 接口(ChatController) +- Spring State Machine 状态机 +- PromptTemplateRegistry 模板管理 +- EventSource 前端 SSE 连接 +- AiChat 组件交互流程 +- pendingAiChat 状态传递机制 + +## 三、流程图 + +### 3.1 导入数据 - 字段映射猜一猜 + +``` +用户上传文件 + ↓ +步骤1: 选择文件 + ↓ +点击下一步 + ↓ +调用 preview_headers 获取源字段和目标列 + ↓ +步骤2: 字段映射页面 + ↓ +显示源字段和目标字段映射表 + ↓ +[用户点击猜一猜按钮] ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ + ↓ ↓ +构建 pendingAiChat 切换到 AI Chat 面板 + ↓ ↓ +promptType = NL_2_FIELD_MAPPING AiChat 检测到 pendingAiChat + ↓ ↓ +传入: 源字段列表 + 表名 调用 /api/ai/chat SSE接口 + ↓ ↓ + 后端状态机执行: + IDLE → FETCHING_TABLE_SCHEMA + → FetchSchemaAction 自动获取目标表 DDL + → BUILDING_PROMPT + → 组装 prompt (源字段+目标表DDL+映射要求) + → STREAMING + → 调用 AI 生成映射建议 + → COMPLETED + ↓ ↓ + 前端解析 JSON 结果 + ↓ ↓ + 自动填充映射下拉框 ←━━━━━━━━┛ + ↓ +用户确认/修改映射 + ↓ +继续导入流程 +``` + +### 3.2 生成数据 - 表达式猜一猜 + +``` +打开生成数据弹窗 + ↓ +加载表列信息和模板 + ↓ +显示列配置表格 (列名, 类型, 注释, 表达式) + ↓ +[用户点击猜一猜按钮] ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ + ↓ ↓ +构建 pendingAiChat 切换到 AI Chat 面板 + ↓ ↓ +promptType = NL_2_DATA_EXPRESSION AiChat 检测到 pendingAiChat + ↓ ↓ +传入: 表名 调用 /api/ai/chat SSE接口 + ↓ ↓ + 后端状态机执行: + IDLE → FETCHING_TABLE_SCHEMA + → FetchSchemaAction 自动获取目标表 DDL + → BUILDING_PROMPT + → 组装 prompt (目标表DDL+datafaker要求) + → STREAMING + → 调用 AI 生成表达式建议 + → COMPLETED + ↓ ↓ + 前端解析 JSON 结果 + ↓ ↓ + 自动填充表达式列 ←━━━━━━━━┛ + ↓ +用户预览/修改表达式 + ↓ +执行数据生成 +``` + +## 四、时序图 + +### 4.1 导入数据 - 字段映射猜一猜 + +``` +用户 ImportDataModal WorkspaceStore AiChat组件 EventSource ChatController 状态机 AI模型 + | | | | | | | | + |-- 上传文件,进入步骤2 -->| | | | | | | + | |-- 显示字段映射表 ---->| | | | | | + | | (源字段 vs 目标字段) | | | | | | + | | | | | | | | + |--- 点击"猜一猜" ------>| | | | | | | + | | | | | | | | + | |-- setPendingAiChat({ | | | | | | + | | promptType: 'NL_2_FIELD_MAPPING', | | | | | + | | dataSourceId, databaseName, schemaName,| | | | | + | | tableName, sourceFields[], | | | | | + | | onMappingGenerated: callback | | | | | + | | }) | | | | | | + | |--->| | | | | | | + | | | | | | | | + | |-- setCurrentWorkspaceExtend('ai') | | | | | + | |--->| | | | | | | + | | |-- 检测到 -------->| | | | | + | | | pendingAiChat | | | | | + | | | | | | | | + | | | |-- connectToEventSource({ | | | + | | | | url: '/api/ai/chat?params', | | | + | | | | uid: sessionId | | | + | | | | }) | | | | + | | | |------->| | | | | + | | | | |-- GET /api/ai/chat ->| | | + | | | | | |-- 创建 ChatContext | | + | | | | | |-- sendEvent(TABLES_PROVIDED) ->| | + | | | | | | | | + | | | | | |--- IDLE → FETCHING_TABLE_SCHEMA --->| + | | | | | |--- FetchSchemaAction 获取目标表DDL -->| + | | | | | | (从数据库自动获取) | + | | | | | |--- 存储到 context.schemaDdl ----------| + | | | | | | | | + | | | | | |--- FETCHING_TABLE_SCHEMA → BUILDING_PROMPT ->| + | | | | | |--- 组装 prompt (源字段+目标表DDL) | + | | | | | | | | + | | | | | |--- BUILDING_PROMPT → STREAMING ->| | + | | | | | |--- ChatClient.prompt().stream() ------->| + | | | | | | | | + | | | | | |<-- 流式返回 JSON 结果 ---------------| + | | | | |<-- SSE event: message {content, thinking} | | + | | | |<-- onMessage callback| | | | + | | | | | | | | + | | | | | |<-- [DONE] -------------------------| + | | | | | |--- STREAMING → COMPLETED | | + | | | | | | | | + | | | |<-- onDone callback | | | | + | | | |-- extractJsonFromContent() | | | + | | | |-- 解析出 field_mappings[] | | | + | | | | | | | | + | |<-- onMappingGenerated(result) -------------| | | | | + | | | | | | | | + | |-- 自动填充 targetField 下拉框 | | | | | + |<-- 显示推荐映射结果 ---| | | | | | | + | | | | | | | | + |-- 确认或修改映射 ------>| | | | | | | + |-- 点击"下一步" -------->| | | | | | | + | |-- 继续导入流程 | | | | | | +``` + +### 4.2 生成数据 - 表达式猜一猜 + +``` +用户 DataGenerationModal WorkspaceStore AiChat组件 EventSource ChatController 状态机 AI模型 + | | | | | | | | + |-- 打开生成数据弹窗 --->| | | | | | | + | |-- 加载列配置表格 --->| | | | | | + | | (列名, 类型, 注释, 表达式) | | | | | + | | | | | | | | + |--- 点击"猜一猜" ------>| | | | | | | + | | | | | | | | + | |-- setPendingAiChat({ | | | | | | + | | promptType: 'NL_2_DATA_EXPRESSION', | | | | | + | | dataSourceId, databaseName, schemaName,| | | | | + | | tableName, | | | | | + | | onExpressionGenerated: callback | | | | | + | | }) | | | | | | + | |--->| | | | | | | + | | | | | | | | + | |-- setCurrentWorkspaceExtend('ai') | | | | | + | |--->| | | | | | | + | | |-- 检测到 -------->| | | | | + | | | pendingAiChat | | | | | + | | | | | | | | + | | | |-- connectToEventSource({ | | | + | | | | url: '/api/ai/chat?params', | | | + | | | | uid: sessionId | | | + | | | | }) | | | | + | | | |------->| | | | | + | | | | |-- GET /api/ai/chat ->| | | + | | | | | |-- 创建 ChatContext | | + | | | | | |-- sendEvent(TABLES_PROVIDED) ->| | + | | | | | | | | + | | | | | |--- IDLE → FETCHING_TABLE_SCHEMA --->| + | | | | | |--- FetchSchemaAction 获取目标表DDL -->| + | | | | | | (从数据库自动获取) | + | | | | | |--- 存储到 context.schemaDdl ----------| + | | | | | | | | + | | | | | |--- FETCHING_TABLE_SCHEMA → BUILDING_PROMPT ->| + | | | | | |--- 组装 prompt (目标表DDL+datafaker) | + | | | | | | | | + | | | | | |--- BUILDING_PROMPT → STREAMING ->| | + | | | | | |--- ChatClient.prompt().stream() ------->| + | | | | | | | | + | | | | | |<-- 流式返回 JSON 结果 ---------------| + | | | | |<-- SSE event: message {content, thinking} | | + | | | |<-- onMessage callback| | | | + | | | | | | | | + | | | | | |<-- [DONE] -------------------------| + | | | | | |--- STREAMING → COMPLETED | | + | | | | | | | | + | | | |<-- onDone callback | | | | + | | | |-- extractJsonFromContent() | | | + | | | |-- 解析出 column_expressions[] | | | + | | | | | | | | + | |<-- onExpressionGenerated(result) ----------| | | | | + | | | | | | | | + | |-- 自动填充表达式列 | | | | | + |<-- 显示推荐表达式 -----| | | | | | | + | | | | | | | | + |-- 确认/修改表达式 ----->| | | | | | | + |-- 点击"预览"或"确定生成"->| | | | | | | + | |-- 执行数据生成流程 | | | | | | +``` + +## 五、详细设计 + +### 5.1 新增 PromptType + +#### 后端 - PromptType.java + +文件路径: `chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/enums/PromptType.java` + +```java +NL_2_COMMENT("猜测表和字段注释"), +NL_2_COMMENT_BATCH("批量猜测表注释"), +NL_2_FIELD_MAPPING("智能字段映射推荐"), // 新增 +NL_2_DATA_EXPRESSION("智能数据生成表达式推荐"), // 新增 +``` + +#### 前端 - common.ts + +文件路径: `chat2db-client/src/pages/main/workspace/store/common.ts` + +```typescript +export type IAiChatPromptType = + | 'NL_2_SQL' + | 'SQL_EXPLAIN' + | 'SQL_OPTIMIZER' + | 'SQL_2_SQL' + | 'NL_2_COMMENT' + | 'NL_2_COMMENT_BATCH' + | 'NL_2_FIELD_MAPPING' // 新增 + | 'NL_2_DATA_EXPRESSION'; // 新增 +``` + +### 5.2 扩展 pendingAiChat 接口 + +文件路径: `chat2db-client/src/pages/main/workspace/store/common.ts` + +```typescript +export interface IPendingAiChat { + dataSourceId: number; + databaseName?: string; + schemaName?: string | null; + tableNames?: string[] | null; + message: string; + promptType: IAiChatPromptType; + onCommentGenerated?: (result: ITableCommentResult) => void; + onBatchCommentGenerated?: (result: IBatchTableCommentResult) => void; + + // 新增回调 + onMappingGenerated?: (result: IFieldMappingResult) => void; + onExpressionGenerated?: (result: IDataExpressionResult) => void; + + // 扩展参数(JSON 字符串,用于传递额外数据) + ext?: string; +} + +// 导入映射结果 +export interface IFieldMappingResult { + mappings: { + sourceField: string; + targetField: string; + confidence: number; // 匹配置信度 0-1 + }[]; +} + +// 生成数据表达式结果 +export interface IDataExpressionResult { + column_expressions: { + column_name: string; + expression: string; // datafaker 表达式 + reason: string; // 推荐理由 + }[]; +} +``` + +### 5.3 Prompt 模板设计 + +文件路径: `chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/resources/prompt-templates.yml` + +#### nl_2_field_mapping - 字段映射 + +```yaml +nl_2_field_mapping: + name: "nl_2_field_mapping" + description: "智能字段映射推荐" + template: | + ### 任务:根据源文件字段和目标数据库表结构,推荐最佳字段映射方案 + + **目标表**: {table_name} + **数据库类型**: {db_type} + + **源文件字段列表**: + {source_fields} + + **目标表字段结构** (自动获取): + {schema} + + **要求**: + 1. 根据字段名、数据类型、语义智能匹配源字段到目标字段 + 2. 考虑数据类型兼容性 + 3. 考虑字段命名语义相似性(如 name -> user_name, email -> user_email) + 4. 为每个源字段推荐最合适的目标字段 + 5. 如果某个源字段没有合适的目标字段,可以省略 + + **输出格式(严格 JSON,不要包含其他文字)**: + ```json + { + "mappings": [ + { + "sourceField": "源字段名", + "targetField": "目标字段名", + "confidence": 0.95 + } + ] + } + ``` + + **注意事项**: + 1. 只输出 JSON 内容,不要包含其他解释文字 + 2. confidence 值为 0-1 之间的小数,表示匹配置信度 + 3. 确保所有 sourceField 都在源字段列表中存在 + 4. 确保所有 targetField 都在目标表字段结构中存在 +``` + +**说明**: +- `{schema}` 占位符由后端 `FetchSchemaAction` 自动获取目标表 DDL 填充 +- 前端只需传 `sourceFields`(源文件字段列表) + +#### nl_2_data_expression - 数据生成表达式 + +```yaml +nl_2_data_expression: + name: "nl_2_data_expression" + description: "智能数据生成表达式推荐" + template: | + ### 任务:为数据库表的每个字段推荐合适的 datafaker 表达式 + + **目标表**: {table_name} + **数据库类型**: {db_type} + + **表字段信息** (自动获取): + {schema} + + **可用的 datafaker 表达式示例**: + - 姓名: #{Name.first_name}, #{Name.last_name}, #{Name.full_name} + - 邮箱: #{Internet.email_address}, #{Internet.url} + - 电话: #{Phone.cell_phone}, #{Phone.phone_number} + - 地址: #{Address.full_address}, #{Address.city}, #{Address.country} + - 日期: #{date.past}, #{date.future}, #{date.birthday} + - 数值: #{number.number_between '1,1000'}, #{number.random_double} + - 文本: #{lorem.sentence}, #{lorem.word}, #{lorem.paragraph} + - 公司: #{Company.name}, #{Company.industry}, #{Company.catch_phrase} + - ID: #{Code.isbn}, #{Code.asin}, #{Number.uuid} + - 布尔: #{bool.bool} + + **要求**: + 1. 根据字段名、数据类型、注释推荐合适的表达式 + 2. 考虑数据类型和长度限制(如 VARCHAR 长度、DECIMAL 精度) + 3. 表达式必须符合 datafaker 语法 + 4. 如果字段是主键或自增,可以跳过 + 5. 如果字段允许 NULL 且没有合适表达式,可以留空 + + **输出格式(严格 JSON,不要包含其他文字)**: + ```json + { + "column_expressions": [ + { + "column_name": "字段名", + "expression": "#{Name.first_name}", + "reason": "推荐原因" + } + ] + } + ``` + + **注意事项**: + 1. 只输出 JSON 内容,不要包含其他解释文字 + 2. expression 必须是有效的 datafaker 表达式 + 3. reason 简要说明为什么推荐这个表达式 + 4. 确保所有 column_name 都在表字段信息中存在 +``` + +**说明**: +- `{schema}` 占位符由后端 `FetchSchemaAction` 自动获取目标表 DDL 填充 +- 前端只需传 `tableName`,不需要传列信息 + +### 5.4 前端集成实现 + +#### 5.4.1 ImportDataModal - 字段映射猜一猜 + +文件路径: `chat2db-client/src/components/ImportDataModal/index.tsx` + +**导入依赖:** +```typescript +import { MagicOutlined } from '@ant-design/icons'; +import { setPendingAiChat, setCurrentWorkspaceExtend } from '@/pages/main/workspace/store'; +import { IFieldMappingResult } from '@/pages/main/workspace/store/common'; +``` + +**添加状态和回调:** +```typescript +const ImportDataModal = () => { + // ... 现有状态 + + // AI 猜一猜处理 + const handleAiGuessMapping = useCallback(() => { + if (!params || !previewData) { + message.warning('请先上传文件并预览'); + return; + } + + setPendingAiChat({ + dataSourceId: params.dataSourceId, + databaseName: params.databaseName, + schemaName: params.schemaName, + tableNames: [params.tableName], + message: `请为表 ${params.tableName} 推荐字段映射方案`, + promptType: 'NL_2_FIELD_MAPPING', + ext: JSON.stringify({ + sourceFields: previewData.headers, // 只需要传源文件字段 + }), + onMappingGenerated: handleMappingGenerated, + }); + setCurrentWorkspaceExtend('ai'); + }, [params, previewData]); + + const handleMappingGenerated = useCallback((result: IFieldMappingResult) => { + if (!result || !result.mappings || result.mappings.length === 0) { + message.warning('未获取到映射推荐'); + return; + } + + // 自动填充映射 + const newMappings = fieldMappings.map(m => { + const matched = result.mappings.find(r => r.sourceField === m.sourceField); + if (matched && matched.targetField) { + // 更新主键标识 + const targetCol = previewData?.tableColumns.find(col => col.name === matched.targetField); + return { + ...m, + targetField: matched.targetField, + primaryKey: !!targetCol?.primaryKey, + }; + } + return m; + }); + + setFieldMappings(newMappings); + message.success(`AI 已推荐 ${result.mappings.length} 个字段映射,请查看并确认`); + }, [fieldMappings, previewData]); + + // ... +``` + +**修改字段映射步骤 UI:** +```typescript +case 1: + return ( +
    +
    +
    + {i18n('workspace.table.import.fieldMapping.description')} +
    + +
    + + {/* 其余内容保持不变 */} +
    + +
    + {file?.name || '-'} +
    +
    + {/* ... */} +
    + ); +``` + +#### 5.4.2 DataGenerationModal - 表达式猜一猜 + +文件路径: `chat2db-client/src/components/DataGenerationModal/index.tsx` + +**导入依赖:** +```typescript +import { MagicOutlined } from '@ant-design/icons'; +import { setPendingAiChat, setCurrentWorkspaceExtend } from '@/pages/main/workspace/store'; +import { IDataExpressionResult } from '@/pages/main/workspace/store/common'; +``` + +**添加状态和回调:** +```typescript +const DataGenerationModal: React.FC = () => { + // ... 现有状态 + + // AI 猜一猜处理 + const handleAiGuessExpression = useCallback(() => { + if (!tableInfo || columns.length === 0) { + message.warning('请先加载表列信息'); + return; + } + + setPendingAiChat({ + dataSourceId: tableInfo.dataSourceId, + databaseName: tableInfo.databaseName, + schemaName: tableInfo.schemaName, + tableNames: [tableInfo.tableName], + message: `请为表 ${tableInfo.tableName} 的字段推荐 datafaker 表达式`, + promptType: 'NL_2_DATA_EXPRESSION', + onExpressionGenerated: handleExpressionGenerated, + }); + setCurrentWorkspaceExtend('ai'); + }, [tableInfo, columns]); + + const handleExpressionGenerated = useCallback((result: IDataExpressionResult) => { + if (!result || !result.column_expressions || result.column_expressions.length === 0) { + message.warning('未获取到表达式推荐'); + return; + } + + // 自动填充表达式 + const newColumns = columns.map(col => { + const matched = result.column_expressions.find(e => e.column_name === col.columnName); + if (matched && matched.expression) { + return { ...col, expression: matched.expression }; + } + return col; + }); + + setColumns(newColumns); + message.success(`AI 已推荐 ${result.column_expressions.length} 个字段表达式,请查看并确认`); + }, [columns]); + + // ... +``` + +**修改 UI 添加猜一猜按钮:** +```typescript +return ( + <> + setOpen(false)} + width={1100} + footer={[ + , + , + , + ]} + > +
    + + + + + {/* 添加猜一猜按钮 */} +
    +

    列配置

    + +
    + +
    + + {/* 其余内容保持不变 */} + + + {/* ... */} + +); +``` + +### 5.5 后端扩展实现 + +#### 5.5.1 BuildPromptAction 扩展 + +文件路径: `chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/actions/BuildPromptAction.java` + +```java +@Component +@Slf4j +public class BuildPromptAction implements Action { + + @Autowired + private PromptTemplateRegistry promptTemplateRegistry; + + @Override + public void execute(StateMachine stateMachine, + Event event, + ChatContext context) { + try { + ChatQueryRequest request = context.getRequest(); + String promptType = request.getPromptType(); + + PromptTemplate template = promptTemplateRegistry.getTemplate(promptType.toLowerCase()); + if (template == null) { + throw new IllegalStateException("Prompt template not found: " + promptType); + } + + String prompt = template.getTemplate(); + + // 替换通用占位符 + prompt = prompt.replace("{db_type}", getDbType(context)); + + // 根据不同类型处理特殊逻辑 + if (PromptType.NL_2_COMMENT.name().equals(promptType) || + PromptType.NL_2_COMMENT_BATCH.name().equals(promptType)) { + // 原有逻辑:schema DDL 已由 FetchSchemaAction 获取并存入 context + String ddl = context.getSchemaDdl(); + prompt = prompt.replace("{schema}", ddl); + prompt = prompt.replace("{description}", ""); + prompt = prompt.replace("{ext}", StringUtils.defaultString(request.getExt(), "")); + prompt = prompt.replace("{message}", StringUtils.defaultString(request.getMessage(), "")); + + } else if (PromptType.NL_2_FIELD_MAPPING.name().equals(promptType)) { + // 新增:字段映射 - schema DDL 已由 FetchSchemaAction 获取 + prompt = handleFieldMappingPrompt(prompt, context); + + } else if (PromptType.NL_2_DATA_EXPRESSION.name().equals(promptType)) { + // 新增:数据生成表达式 - schema DDL 已由 FetchSchemaAction 获取 + prompt = handleDataExpressionPrompt(prompt, context); + } + + context.setBuiltPrompt(prompt); + log.info("Prompt built for type: {}, length: {}", promptType, prompt.length()); + + } catch (Exception e) { + log.error("Failed to build prompt", e); + throw e; + } + } + + private String handleFieldMappingPrompt(String prompt, ChatContext context) { + ChatQueryRequest request = context.getRequest(); + String tableName = CollectionUtils.isEmpty(request.getTableNames()) + ? "" : request.getTableNames().get(0); + + // schema DDL 已由 FetchSchemaAction 获取 + String schemaDdl = context.getSchemaDdl(); + + // 从 ext 解析源字段列表 + String ext = request.getExt(); + String sourceFieldsText = ""; + if (StringUtils.isNotBlank(ext)) { + FieldMappingExt extData = JSON.parseObject(ext, FieldMappingExt.class); + if (extData != null && extData.getSourceFields() != null) { + sourceFieldsText = extData.getSourceFields().stream() + .map(f -> "- " + f) + .collect(Collectors.joining("\n")); + } + } + + prompt = prompt.replace("{table_name}", tableName); + prompt = prompt.replace("{source_fields}", sourceFieldsText); + prompt = prompt.replace("{schema}", schemaDdl); + prompt = prompt.replace("{message}", StringUtils.defaultString(request.getMessage(), "")); + + return prompt; + } + + private String handleDataExpressionPrompt(String prompt, ChatContext context) { + ChatQueryRequest request = context.getRequest(); + String tableName = CollectionUtils.isEmpty(request.getTableNames()) + ? "" : request.getTableNames().get(0); + + // schema DDL 已由 FetchSchemaAction 获取 + String schemaDdl = context.getSchemaDdl(); + + prompt = prompt.replace("{table_name}", tableName); + prompt = prompt.replace("{schema}", schemaDdl); + prompt = prompt.replace("{message}", StringUtils.defaultString(request.getMessage(), "")); + + return prompt; + } + + // 辅助类 + @Data + public static class FieldMappingExt { + private List sourceFields; // 只需要源文件字段 + } +} +``` + +**关键说明**: +- `FetchSchemaAction` 会在 `FETCHING_TABLE_SCHEMA` 状态时自动获取目标表 DDL 并存储到 `context.schemaDdl` +- `BuildPromptAction` 直接使用 `context.getSchemaDdl()` 即可,不需要前端传列信息 +- 前端只需要传 `sourceFields`(导入映射场景)或直接传 `tableName`(生成表达式场景) + +#### 5.5.2 AiChat 组件扩展 - JSON 解析 + +文件路径: `chat2db-client/src/components/AiChat/index.tsx` + +**添加新的 JSON 提取函数:** +```typescript +// 字段映射 JSON 提取 +function extractFieldMappingFromContent(content: string): IFieldMappingResult | null { + try { + const jsonMatch = content.match(/\{[\s\S]*"mappings"[\s\S]*\}/); + if (jsonMatch) { + return JSON.parse(jsonMatch[0]) as IFieldMappingResult; + } + const directJson = JSON.parse(content); + if (directJson.mappings) { + return directJson as IFieldMappingResult; + } + } catch (e) { + console.error('[extractFieldMappingFromContent] Parse error:', e); + } + return null; +} + +// 数据表达式 JSON 提取 +function extractDataExpressionFromContent(content: string): IDataExpressionResult | null { + try { + const jsonMatch = content.match(/\{[\s\S]*"column_expressions"[\s\S]*\}/); + if (jsonMatch) { + return JSON.parse(jsonMatch[0]) as IDataExpressionResult; + } + const directJson = JSON.parse(content); + if (directJson.column_expressions) { + return directJson as IDataExpressionResult; + } + } catch (e) { + console.error('[extractDataExpressionFromContent] Parse error:', e); + } + return null; +} +``` + +**修改 onDone 回调处理:** +```typescript +onDone: () => { + console.log('[AiChat] onDone callback, sessionId:', sessionId); + updateState(sessionId, 'COMPLETED'); + const currentSessions = useAiChatStore.getState().sessions; + const session = currentSessions.get(sessionId); + + if (session?.currentContent) { + addMessage(sessionId, { + id: uuidv4(), + role: 'assistant', + content: session.currentContent, + thinking: session.currentThinking || undefined, + }); + + // NL_2_COMMENT 处理(原有逻辑) + if (promptType === 'NL_2_COMMENT' && commentCallbackRef.current) { + try { + const jsonContent = extractJsonFromContent(session.currentContent); + if (jsonContent) { + commentCallbackRef.current(jsonContent); + message.success('AI 注释已生成,请查看并确认'); + } + } catch (e) { + console.error('[AiChat] Failed to parse comment JSON:', e); + message.warning('无法解析 AI 生成的注释,请手动查看'); + } + commentCallbackRef.current = undefined; + } + + // NL_2_COMMENT_BATCH 处理(原有逻辑) + if (promptType === 'NL_2_COMMENT_BATCH' && batchCommentCallbackRef.current) { + try { + const jsonContent = extractBatchJsonFromContent(session.currentContent); + if (jsonContent) { + batchCommentCallbackRef.current(jsonContent); + message.success('AI 批量注释已生成'); + } + } catch (e) { + console.error('[AiChat] Failed to parse batch comment JSON:', e); + message.warning('无法解析 AI 生成的批量注释,请手动查看'); + } + batchCommentCallbackRef.current = undefined; + } + + // NL_2_FIELD_MAPPING 处理(新增) + if (promptType === 'NL_2_FIELD_MAPPING' && mappingCallbackRef.current) { + try { + const jsonContent = extractFieldMappingFromContent(session.currentContent); + if (jsonContent) { + console.log('[AiChat] Parsed field mapping result:', jsonContent); + mappingCallbackRef.current(jsonContent); + message.success('AI 字段映射推荐已生成,请查看并确认'); + } + } catch (e) { + console.error('[AiChat] Failed to parse field mapping JSON:', e); + message.warning('无法解析 AI 生成的映射推荐,请手动查看'); + } + mappingCallbackRef.current = undefined; + } + + // NL_2_DATA_EXPRESSION 处理(新增) + if (promptType === 'NL_2_DATA_EXPRESSION' && expressionCallbackRef.current) { + try { + const jsonContent = extractDataExpressionFromContent(session.currentContent); + if (jsonContent) { + console.log('[AiChat] Parsed data expression result:', jsonContent); + expressionCallbackRef.current(jsonContent); + message.success('AI 表达式推荐已生成,请查看并确认'); + } + } catch (e) { + console.error('[AiChat] Failed to parse data expression JSON:', e); + message.warning('无法解析 AI 生成的表达式,请手动查看'); + } + expressionCallbackRef.current = undefined; + } + } + closeEventSource.current = undefined; +}, +``` + +**添加新的 ref:** +```typescript +export default memo(() => { + // ... 现有 ref + const commentCallbackRef = useRef<(result: ITableCommentResult) => void>(); + const batchCommentCallbackRef = useRef<(result: IBatchTableCommentResult) => void>(); + + // 新增 ref + const mappingCallbackRef = useRef<(result: IFieldMappingResult) => void>(); + const expressionCallbackRef = useRef<(result: IDataExpressionResult) => void>(); + + // ... +``` + +**在 pendingAiChat 检测中设置新回调:** +```typescript +useEffect(() => { + if (pendingAiChat && pendingAiChat.message) { + // ... 原有逻辑 + + if (pendingAiChat.onCommentGenerated) { + commentCallbackRef.current = pendingAiChat.onCommentGenerated; + } + if (pendingAiChat.onBatchCommentGenerated) { + batchCommentCallbackRef.current = pendingAiChat.onBatchCommentGenerated; + } + // 新增 + if (pendingAiChat.onMappingGenerated) { + mappingCallbackRef.current = pendingAiChat.onMappingGenerated; + } + if (pendingAiChat.onExpressionGenerated) { + expressionCallbackRef.current = pendingAiChat.onExpressionGenerated; + } + + sendAiChatInternal(pendingAiChat.message, pendingAiChat.promptType, overrideBoundInfo); + useWorkspaceStore.setState({ pendingAiChat: null }); + } +}, [pendingAiChat, boundInfo, sendAiChatInternal]); +``` + +### 5.6 ChatStateMachineConfig 扩展 + +文件路径: `chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/statemachine/ChatStateMachineConfig.java` + +**说明**: 状态机不需要修改,因为新类型也走相同的流程: +``` +IDLE → FETCHING_TABLE_SCHEMA → BUILDING_PROMPT → STREAMING → COMPLETED +``` + +`FetchSchemaAction` 会根据 `tableNames` 自动获取目标表 DDL 并存储到 `context.schemaDdl`。 + +**ChatController 中判断初始事件:** +```java +private ChatEvent determineInitialEvent(ChatQueryRequest request) { + // 所有需要表结构的场景都使用 TABLES_PROVIDED + if (CollectionUtils.isNotEmpty(request.getTableNames())) { + return ChatEvent.TABLES_PROVIDED; + } else { + return ChatEvent.TABLES_NOT_PROVIDED; + } +} +``` + +**关键说明**: +- `NL_2_FIELD_MAPPING` 和 `NL_2_DATA_EXPRESSION` 都需要表结构 +- 直接复用现有的 `TABLES_PROVIDED` 事件即可 +- `FetchSchemaAction` 会自动处理 DDL 获取逻辑 + +## 六、i18n 国际化 + +文件路径: `chat2db-client/src/i18n/zh-cn/common.ts` + +```typescript +// 在 common.button 部分添加 +'common.button.guess': '猜一猜', +'common.button.aiGuess': 'AI 猜一猜', +``` + +## 七、完整实现清单 + +### 前端修改 + +| 文件 | 修改内容 | +|------|----------| +| `src/pages/main/workspace/store/common.ts` | 新增 2 个 PromptType、2 个结果接口、扩展 IPendingAiChat | +| `src/components/ImportDataModal/index.tsx` | 添加猜一猜按钮、handleAiGuessMapping、handleMappingGenerated
    **只需传 sourceFields** | +| `src/components/DataGenerationModal/index.tsx` | 添加猜一猜按钮、handleAiGuessExpression、handleExpressionGenerated
    **只需传 tableName** | +| `src/components/AiChat/index.tsx` | 添加 2 个 JSON 解析函数、2 个 callback ref、onDone 中处理新类型 | +| `src/i18n/zh-cn/common.ts` | 添加国际化文案 | + +### 后端修改 + +| 文件 | 修改内容 | +|------|----------| +| `PromptType.java` | 新增 NL_2_FIELD_MAPPING、NL_2_DATA_EXPRESSION | +| `prompt-templates.yml` | 新增 nl_2_field_mapping、nl_2_data_expression 模板 | +| `BuildPromptAction.java` | 扩展 handleFieldMappingPrompt、handleDataExpressionPrompt 方法
    **使用 context.getSchemaDdl() 获取 DDL** | + +**关键优化**: +- 前端**不需要**传 schema 或列信息 +- `FetchSchemaAction` 自动从数据库获取表 DDL 存入 `context.schemaDdl` +- `BuildPromptAction` 直接使用 `context.getSchemaDdl()` 填充 `{schema}` 占位符 +- 完全复用现有状态机流程,无需修改状态机配置 + +## 八、测试要点 + +### 导入数据猜一猜 + +1. 上传 CSV/Excel 文件,进入步骤2 +2. 点击"猜一猜"按钮 +3. 验证 AI 面板打开并显示生成过程 +4. 验证返回的映射结果自动填充到下拉框 +5. 验证主键字段正确识别 +6. 测试手动修改映射后继续导入 +7. 测试无合适映射时的处理 + +### 生成数据猜一猜 + +1. 打开生成数据弹窗 +2. 点击"猜一猜"按钮 +3. 验证 AI 面板打开并显示生成过程 +4. 验证返回的表达式自动填充到表达式列 +5. 验证表达式语法正确性 +6. 测试预览功能验证生成数据 +7. 测试修改表达式后生成数据 + +### 边界情况 + +1. 无表名时的处理 +2. AI 返回格式错误的容错处理 +3. 网络超时的错误提示 +4. 重复点击按钮的防抖处理 +5. 大量字段(50+)的性能测试 + +## 九、后续优化建议 + +1. **置信度显示**: 在字段映射结果中显示 AI 匹配置信度,用颜色区分 +2. **部分应用**: 允许用户选择性地应用 AI 推荐的部分字段 +3. **历史记录**: 保存用户确认后的映射/表达式,用于训练优化 +4. **模板推荐**: 基于历史数据推荐最常用的表达式模板 +5. **批量导入**: 支持批量文件的智能映射 +6. **自定义 Prompt**: 允许高级用户自定义 prompt 模板 diff --git a/docs/prompt-refactoring-completed.md b/docs/prompt-refactoring-completed.md new file mode 100644 index 000000000..830ccde60 --- /dev/null +++ b/docs/prompt-refactoring-completed.md @@ -0,0 +1,343 @@ +# Chat2DB 提示词生成重构完成报告 + +## 重构概述 + +已完成 Chat2DB 提示词生成代码的全面重构,主要目标是: +1. **删除 PromptService 门面类** - 消除 311 行的上帝类 +2. **扩展 DatabaseService** - 复用现有服务,添加 Schema 获取方法 +3. **创建 PromptBuilder** - 专注提示词构建的建造者模式 +4. **集中管理模板** - 使用 PromptTemplateRegistry 统一管理所有提示词模板 + +--- + +## 完成的变更 + +### 1. 扩展 DatabaseService ✅ + +**文件:** `chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DatabaseService.java` + +**新增方法:** +- `queryTableDdl()` - 查询单个表的 DDL +- `buildTableColumn()` - 批量构建表列信息 +- `queryDatabaseTables()` - 查询数据库所有表信息 +- `queryRedisSchema()` - 查询 Redis Schema 信息 +- `queryDatabaseType()` - 获取数据库类型 + +**实现:** `chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DatabaseServiceImpl.java` +- 注入了 `TableService` 和 `DataSourceService` +- 实现了所有新增方法 +- 复用现有的 `TableService.pageQuery()` 等方法 + +--- + +### 2. 创建 Prompt 包 ✅ + +**目录:** `chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/prompt/` + +#### 2.1 PromptTemplate.java +- 提示词模板值对象 +- 包含字段:name, template, promptType, description + +#### 2.2 PromptContext.java +- 提示词构建上下文 +- 包含字段:promptType, message, ext, schemaDdl, dataSourceType, targetSqlType, forTableSelection + +#### 2.3 PromptValidator.java +- 提示词验证器 +- 方法: + - `isValidLength()` - 验证提示词长度 + - `getTokenCount()` - 计算 token 数量 + - `cleanPrompt()` - 清理特殊字符 + +#### 2.4 PromptTemplateRegistry.java +- 模板注册表(单例) +- 注册了 8 种模板类型: + - NL_2_SQL + - SQL_EXPLAIN + - SQL_OPTIMIZER + - SQL_2_SQL + - SELECT_TABLES + - TEXT_GENERATION + - TITLE_GENERATION + - NL_2_COMMENT + +#### 2.5 PromptBuilder.java +- 建造者接口 +- 流式 API 设计 + +#### 2.6 PromptBuilderImpl.java +- 建造者实现 +- 依赖注入 `PromptTemplateRegistry` 和 `PromptValidator` +- 实现模板填充、表选择指令附加等功能 + +--- + +### 3. 重构 Action 类 ✅ + +#### 3.1 BuildPromptAction.java +**重构前:** 99 行,包含模板构建逻辑 +**重构后:** 76 行,委托给 PromptBuilder + +**主要变更:** +- 删除 `buildPromptWithSchema()` 方法 +- 使用 `PromptBuilder.context(promptContext).build()` +- 代码更简洁 + +#### 3.2 AutoSelectTablesAction.java +**重构前:** 119 行,依赖 PromptService +**重构后:** 132 行,使用 PromptBuilder + +**主要变更:** +- 删除 `PromptService` 依赖 +- 注入 `PromptBuilder` +- `buildSelectPrompt()` 使用 `PromptContext` 构建 + +#### 3.3 FetchSchemaAction.java +**重构前:** 82 行,依赖 PromptService 和 ChatConverter +**重构后:** 93 行,直接使用 DatabaseService + +**主要变更:** +- 删除 `PromptService` 和 `ChatConverter` 依赖 +- 注入 `DatabaseService` +- 直接调用 `databaseService.buildTableColumn()` 和 `queryDatabaseTables()` + +#### 3.4 StreamAction.java +**重构前:** 103 行,硬编码长度验证 +**重构后:** 118 行,使用 PromptValidator + +**主要变更:** +- 删除硬编码常量 `MAX_PROMPT_LENGTH` 和 `TOKEN_CONVERT_CHAR_LENGTH` +- 注入 `PromptValidator` +- 使用 `promptValidator.isValidLength()` 验证 + +--- + +### 4. 删除 PromptService ✅ + +**文件已删除:** +``` +chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/utils/PromptService.java +``` + +**影响分析:** +- ✅ 无其他文件引用该类 +- ✅ 所有引用已迁移到新架构 + +--- + +## 新架构 + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Action 类 │ +│ (BuildPromptAction, AutoSelectTablesAction, FetchSchemaAction) │ +└─────────────────────────────────────────────────────────────────┘ + │ + ┌───────────────┴───────────────┐ + │ │ + ▼ ▼ +┌─────────────────────────────────┐ ┌─────────────────────────┐ +│ PromptBuilder │ │ DatabaseService │ +│ │ │ (现有服务,扩展) │ +│ - build(PromptContext) │ │ │ +│ - validate() │ │ + queryTableDdl() │ +│ │ │ + buildTableColumn() │ +│ 内部使用: │ │ + queryDatabaseTables()│ +│ - PromptTemplateRegistry │ │ + queryRedisSchema() │ +│ - PromptValidator │ │ + queryDatabaseType() │ +└─────────────────────────────────┘ └─────────────────────────┘ + │ + ▼ +┌─────────────────────────────────┐ +│ PromptTemplateRegistry │ +│ │ +│ - 集中管理 8 种提示词模板 │ +│ - 按 PromptType 获取模板 │ +└─────────────────────────────────┘ +``` + +--- + +## 代码对比 + +### 提示词构建逻辑 + +**重构前(分散在 BuildPromptAction 和 PromptService):** +```java +// BuildPromptAction.java +String builtPrompt = buildPromptWithSchema(prompt, ext, pType, dataSourceType, schemaDdl, request); +builtPrompt = builtPrompt.replaceAll("[\r\t]", "").replaceAll("#", ""); + +// PromptService.java +String.format("### 请根据以下 table properties 和 SQL input%s. %s\n#\n### %s SQL tables...", ...) +``` + +**重构后(集中在 PromptBuilder):** +```java +// BuildPromptAction.java +String builtPrompt = promptBuilder.context(promptContext).build(); + +// PromptBuilderImpl.java +private String fillTemplate(PromptTemplate template, PromptContext context) { + String templateStr = template.getTemplate(); + return templateStr + .replace("{description}", description) + .replace("{ext}", StringUtils.defaultString(context.getExt(), "")) + .replace("{db_type}", StringUtils.defaultString(context.getDataSourceType(), "MYSQL")) + .replace("{schema}", StringUtils.defaultString(context.getSchemaDdl(), "")) + .replace("{message}", context.getMessage()); +} +``` + +--- + +## 文件清单 + +### 新增文件(8 个) +1. `chat2db-server-web-api/.../prompt/PromptTemplate.java` - 模板值对象 +2. `chat2db-server-web-api/.../prompt/PromptContext.java` - 构建上下文 +3. `chat2db-server-web-api/.../prompt/PromptValidator.java` - 验证器 +4. `chat2db-server-web-api/.../prompt/PromptTemplateRegistry.java` - 模板注册表 +5. `chat2db-server-web-api/.../prompt/PromptBuilder.java` - 建造者接口 +6. `chat2db-server-web-api/.../prompt/PromptBuilderImpl.java` - 建造者实现 +7. `chat2db-server-web-api/src/main/resources/prompt-templates.yml` - 模板配置文件 +8. `docs/prompt-refactoring-plan.md` - 重构计划文档 + +### 修改文件(6 个) +1. `DatabaseService.java` - 接口扩展 +2. `DatabaseServiceImpl.java` - 实现扩展 +3. `BuildPromptAction.java` - 使用 PromptBuilder +4. `AutoSelectTablesAction.java` - 使用 PromptBuilder +5. `FetchSchemaAction.java` - 使用 DatabaseService +6. `StreamAction.java` - 使用 PromptValidator + +### 删除文件(1 个) +1. `PromptService.java` - 删除门面类 + +--- + +## 重构收益 + +| 指标 | 重构前 | 重构后 | 改进 | +|------|--------|--------|------| +| **核心类行数** | PromptService: 311 行 | PromptBuilderImpl: ~140 行 | ⬇️ 55% | +| **Action 平均行数** | ~100 行 | ~105 行 | 持平(但职责更清晰) | +| **职责分离** | 混乱 | 清晰 | ✅ | +| **模板管理** | 分散在代码中 | 集中注册 | ✅ | +| **代码重复** | 2+ 处重复 | 零重复 | ✅ | +| **可测试性** | 难以测试 | 可单元测试 | ✅ | +| **扩展性** | 修改代码 | 添加模板 | ✅ | + +--- + +## 后续建议 + +### 1. 单元测试(未实施) +为以下类添加单元测试: +- `PromptTemplateRegistryTest` +- `PromptBuilderImplTest` +- `PromptValidatorTest` +- `DatabaseServiceSchemaTest` + +### 2. 外部化模板(可选) +将模板从代码中提取到资源文件: +``` +resources/prompts/ +├── nl_2_sql_template.txt +├── sql_explain_template.txt +└── ... +``` + +### 3. 国际化支持(可选) +支持多语言模板: +```java +PromptTemplate template = registry.getTemplate(PromptType.NL_2_SQL, Locale.CHINESE); +``` + +--- + +## 编译验证 + +建议执行以下命令验证编译: + +```bash +# 后端编译 +cd chat2db-server +mvn clean install -DskipTests + +# 或者单独编译模块 +mvn clean install -pl chat2db-server-domain/chat2db-server-domain-core -am +mvn clean install -pl chat2db-server-web/chat2db-server-web-api -am +``` + +--- + +## 总结 + +本次重构成功实现了以下目标: + +✅ **删除 PromptService 门面类** - 消除 311 行上帝类 +✅ **扩展 DatabaseService** - 复用现有服务架构 +✅ **创建 PromptBuilder** - 专注提示词构建 +✅ **集中管理模板** - 8 种模板统一注册 +✅ **重构所有 Action** - 使用新架构 +✅ **零代码重复** - 单一事实来源 + +**新架构特点:** +- 职责清晰:每个类只做一件事 +- 易于扩展:新增模板只需在 Registry 中注册 +- 可测试性强:各组件可独立单元测试 +- 代码简洁:流式 API,易于阅读和维护 + +--- + +## Bug 修复 + +### 问题:SelectTablesAction 提示词缺失表名列表 + +**现象:** 用户报告在自动选择表时,提示词中的 `{schema}` 占位符为空,导致 AI 无法识别表名。 + +**原因:** `SelectTablesAction` 在选择表时需要使用所有表的 schema 信息,但此时 `FetchSchemaAction` 还未执行,`ctx.getSchemaDdl()` 返回空值。 + +**解决方案:** 修改 `SelectTablesAction.buildSelectPrompt()` 方法,直接调用 `DatabaseService.queryDatabaseTables()` 获取所有表的 schema,而不是从 context 中获取。 + +**修改文件:** `SelectTablesAction.java` +```java +// 修改前 +private String buildSelectPrompt(ChatContext ctx) { + PromptContext promptContext = PromptContext.builder() + .promptType(PromptType.SELECT_TABLES) + .message(ctx.getRequest().getMessage()) + .schemaDdl(ctx.getSchemaDdl()) // ❌ 此时为空 + .build(); + return promptBuilder.context(promptContext).build(); +} + +// 修改后 +private String buildSelectPrompt(ChatContext ctx) { + String schemaDdl = databaseService.queryDatabaseTables( // ✅ 直接获取 + ctx.getRequest().getDataSourceId(), + ctx.getRequest().getDatabaseName(), + ctx.getRequest().getSchemaName() + ); + + PromptContext promptContext = PromptContext.builder() + .promptType(PromptType.SELECT_TABLES) + .message(ctx.getRequest().getMessage()) + .schemaDdl(schemaDdl) + .build(); + return promptBuilder.context(promptContext).build(); +} +``` + +**状态机流程:** +``` +用户未提供表名: + IDLE → AUTO_SELECTING_TABLES (SelectTablesAction,获取所有表 schema) + → FETCHING_TABLE_SCHEMA (FetchSchemaAction,获取选中表的 schema) + → BUILDING_PROMPT → STREAMING + +用户提供表名: + IDLE → FETCHING_TABLE_SCHEMA (FetchSchemaAction,获取指定表的 schema) + → BUILDING_PROMPT → STREAMING +``` diff --git a/docs/prompt-refactoring-plan.md b/docs/prompt-refactoring-plan.md new file mode 100644 index 000000000..da9b45d02 --- /dev/null +++ b/docs/prompt-refactoring-plan.md @@ -0,0 +1,1083 @@ +# Chat2DB 提示词生成重构计划 + +## 执行摘要 + +当前的提示词生成代码**分散在多个文件中,存在逻辑重复、职责混乱、组织性差等问题**。主要问题包括: + +1. **提示词构建逻辑重复** - `BuildPromptAction` 和 `PromptService` 之间存在重复逻辑 +2. **PromptService 是上帝类** - 311 行的类混合了 Schema 获取、提示词构建、数据库查询等多种职责 +3. **硬编码的提示词模板** - 分散在各个 Action 类中 +4. **关注点未分离** - 业务逻辑与格式化逻辑混合 +5. **缺乏集中化的模板管理** - 没有统一的提示词模板管理机制 + +--- + +## 当前架构问题 + +### 1. **提示词构建逻辑重复** + +**问题:** `BuildPromptAction.buildPromptWithSchema()` (66-86 行) 与 `PromptService.buildSchemaPrompt()` (191-207 行) 逻辑重复。 + +```java +// BuildPromptAction.java:70-77 +String.format("### 请根据以下 table properties 和 SQL input%s. %s\n#\n### %s SQL tables...", ...) + +// PromptService.java:198-200 +String.format("### 请根据以下 table properties 和 SQL input%s. %s\n#\n### %s SQL tables...", ...) +``` + +**影响:** 两条不同的代码路径产生不同的提示词格式,导致不一致性。 + +--- + +### 2. **PromptService 是上帝类** + +**问题:** `PromptService` 承担过多职责: +- Schema 获取 (`queryDatabaseTables`, `queryRedisSchema`) +- DDL 构建 (`buildTableColumn`, `queryTableDdl`) +- 提示词构造 (`buildAutoPrompt`, `buildSchemaPrompt`) +- 数据库类型检测 (`queryDatabaseType`) +- 字符串清理 (`cleanPrompt`) + +**代码行数:** 311 行,包含 15+ 个 public/private 方法 + +--- + +### 3. **Action 类中硬编码提示词模板** + +**问题:** `AutoSelectTablesAction` 直接构建提示词: + +```java +// AutoSelectTablesAction.java:72 +return promptService.buildAutoPrompt(ctx.getRequest()) + + "\n\n请只输出 JSON,格式:{\"table_names\":[\"表名 1\",\"表名 2\"]},不要输出其他文字。"; +``` + +**影响:** 提示词模板分散,难以进行本地化和 A/B 测试。 + +--- + +### 4. **BuildPromptAction 关注点混乱** + +**问题:** `BuildPromptAction` 处理: +- 状态机事件处理 +- 提示词类型检测 +- 数据库类型猜测 +- 提示词模板格式化 +- 字符串清理 + +```java +// BuildPromptAction.java:45-53 +String dataSourceType = guessDataSourceType(schemaDdl); +String builtPrompt = buildPromptWithSchema(...); +builtPrompt = builtPrompt.replaceAll("[\r\t]", "").replaceAll("#", ""); +``` + +--- + +### 5. **缺乏提示词模板抽象** + +**问题:** 没有集中化的模板管理。模板: +- 硬编码在 format 调用中 +- 未外部化 +- 未版本化 +- 无法独立测试 + +--- + +## 重构架构 + +### 核心设计原则 + +1. **职责分离** - 提示词构建与数据获取分离 +2. **单一事实来源** - 模板集中管理,无重复 +3. **建造者模式** - 流式 API 构建提示词 +4. **复用现有服务** - Schema 获取合并到现有 `DatabaseService` +5. **删除门面** - 直接使用建造者,减少中间层 + +--- + +### 新架构 + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Action 类 │ +│ (BuildPromptAction, AutoSelectTablesAction, FetchSchemaAction) │ +└─────────────────────────────────────────────────────────────────┘ + │ + ┌───────────────┴───────────────┐ + │ │ + ▼ ▼ +┌─────────────────────────────────┐ ┌─────────────────────────┐ +│ PromptBuilder │ │ DatabaseService │ +│ │ │ (现有服务,扩展) │ +│ - build(PromptContext) │ │ │ +│ - validate(String prompt) │ │ + querySchema() │ +│ │ │ + queryTableDdl() │ +│ 内部使用: │ │ + queryDatabaseType() │ +│ - PromptTemplateRegistry │ │ + guessDbType() │ +│ - PromptValidator │ │ │ +└─────────────────────────────────┘ └─────────────────────────┘ + │ + ▼ +┌─────────────────────────────────┐ +│ PromptTemplateRegistry │ +│ │ +│ - 集中管理所有提示词模板 │ +│ - 按 PromptType 获取模板 │ +│ - 支持模板版本化 │ +└─────────────────────────────────┘ +``` + +--- + +## 重构计划 + +### 第一阶段:扩展 DatabaseService 📦 + +**目标服务:** `ai.chat2db.server.domain.api.service.DatabaseService` + +#### 1.1 在 DatabaseService 中添加 Schema 获取方法 + +```java +public interface DatabaseService { + // 现有方法... + + // 新增:查询表 DDL + String queryTableDdl(DataSourceConnectionInfo connectionInfo, + String schemaName, String tableName); + + // 新增:批量构建表列信息 + String buildTableColumn(DataSourceConnectionInfo connectionInfo, + String schemaName, List tableNames); + + // 新增:查询数据库所有表信息(用于自动选表场景) + String queryDatabaseTables(DataSourceConnectionInfo connectionInfo, + String schemaName); + + // 新增:查询 Redis Schema + String queryRedisSchema(DataSourceConnectionInfo connectionInfo, + String schemaName, List tableNames); + + // 新增:获取数据库类型 + String queryDatabaseType(Long dataSourceId); +} +``` + +#### 1.2 在 DatabaseServiceImpl 中实现 + +```java +@Service +@Slf4j +public class DatabaseServiceImpl implements DatabaseService { + + @Autowired + private TableService tableService; + + @Autowired + private DataSourceService dataSourceService; + + @Override + public String queryTableDdl(DataSourceConnectionInfo connectionInfo, + String schemaName, String tableName) { + // 实现:调用 TableService 查询表结构 + // 使用 SqlBuilder 构建 CREATE TABLE 语句 + } + + @Override + public String buildTableColumn(DataSourceConnectionInfo connectionInfo, + String schemaName, List tableNames) { + // 实现:批量构建表 DDL + return tableNames.stream() + .map(tableName -> queryTableDdl(connectionInfo, schemaName, tableName)) + .collect(Collectors.joining(";\n")); + } + + @Override + public String queryDatabaseTables(DataSourceConnectionInfo connectionInfo, + String schemaName) { + // 实现:查询所有表及元数据(注释、外键等) + } + + @Override + public String queryRedisSchema(DataSourceConnectionInfo connectionInfo, + String schemaName, List tableNames) { + // 实现:查询 Redis key 列表 + } + + @Override + public String queryDatabaseType(Long dataSourceId) { + // 实现:从 DataSource 获取类型 + } +} +``` + +--- + +### 第二阶段:创建提示词建造者 🔨 + +**创建目录:** `chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/prompt/` + +#### 2.1 创建 `PromptTemplate.java` - 模板值对象 + +```java +package ai.chat2db.server.web.api.controller.ai.prompt; + +import lombok.Builder; +import lombok.Data; +import ai.chat2db.server.web.api.controller.ai.enums.PromptType; + +@Data +@Builder +public class PromptTemplate { + private String name; + private String template; + private PromptType promptType; + private String description; +} +``` + +#### 2.2 创建 `PromptTemplateRegistry.java` - 模板注册表 + +```java +package ai.chat2db.server.web.api.controller.ai.prompt; + +import org.springframework.stereotype.Component; +import ai.chat2db.server.web.api.controller.ai.enums.PromptType; +import jakarta.annotation.PostConstruct; +import java.util.EnumMap; +import java.util.Map; + +@Component +public class PromptTemplateRegistry { + + private final Map templates = new EnumMap<>(PromptType.class); + + @PostConstruct + public void init() { + // 注册所有提示词模板 + register(PromptType.NL_2_SQL, buildNl2SqlTemplate()); + register(PromptType.SQL_EXPLAIN, buildSqlExplainTemplate()); + register(PromptType.SQL_OPTIMIZER, buildSqlOptimizerTemplate()); + register(PromptType.SQL_2_SQL, buildSql2SqlTemplate()); + register(PromptType.SELECT_TABLES, buildSelectTablesTemplate()); + register(PromptType.TEXT_GENERATION, buildTextGenerationTemplate()); + register(PromptType.TITLE_GENERATION, buildTitleGenerationTemplate()); + register(PromptType.NL_2_COMMENT, buildNl2CommentTemplate()); + } + + private void register(PromptType type, PromptTemplate template) { + templates.put(type, template); + } + + public PromptTemplate getTemplate(PromptType type) { + return templates.getOrDefault(type, getDefaultTemplate()); + } + + public PromptTemplate getTemplate(String code) { + PromptType type = PromptType.valueOf(code); + return getTemplate(type); + } + + private PromptTemplate getDefaultTemplate() { + return templates.get(PromptType.NL_2_SQL); + } + + // 构建各个模板... + private PromptTemplate buildNl2SqlTemplate() { + return PromptTemplate.builder() + .name("nl_2_sql") + .promptType(PromptType.NL_2_SQL) + .template("### 请根据以下 table properties 和 SQL input{description}. {ext}\n" + + "#\n" + + "### {db_type} SQL tables, with their properties:\n" + + "#\n" + + "# {schema}\n" + + "#\n" + + "#\n" + + "### SQL input: {message}") + .build(); + } + + // ... 其他模板构建方法 +} +``` + +#### 2.3 创建 `PromptValidator.java` - 验证器 + +```java +package ai.chat2db.server.web.api.controller.ai.prompt; + +import org.springframework.stereotype.Component; + +@Component +public class PromptValidator { + + private static final int MAX_PROMPT_LENGTH = 15400; + private static final int TOKEN_CONVERT_CHAR_LENGTH = 4; + + public boolean isValidLength(String prompt) { + return prompt != null && + prompt.length() / TOKEN_CONVERT_CHAR_LENGTH <= MAX_PROMPT_LENGTH; + } + + public int getTokenCount(String prompt) { + if (prompt == null) return 0; + return prompt.length() / TOKEN_CONVERT_CHAR_LENGTH; + } + + public String cleanPrompt(String prompt) { + if (prompt == null) return ""; + return prompt.replaceAll("[\r\t]", "").replaceAll("#", ""); + } +} +``` + +#### 2.4 创建 `PromptContext.java` - 构建上下文 + +```java +package ai.chat2db.server.web.api.controller.ai.prompt; + +import lombok.Builder; +import lombok.Data; +import ai.chat2db.server.web.api.controller.ai.enums.PromptType; +import ai.chat2db.server.web.api.controller.ai.request.ChatQueryRequest; + +@Data +@Builder +public class PromptContext { + + private PromptType promptType; + private String message; + private String ext; + private String schemaDdl; + private String dataSourceType; + private String targetSqlType; + private boolean forTableSelection; + + public static PromptContext from(ChatQueryRequest request) { + return PromptContext.builder() + .message(request.getMessage()) + .ext(request.getExt()) + .dataSourceType(request.getDataSourceType()) + .targetSqlType(request.getDestSqlType()) + .build(); + } +} +``` + +#### 2.5 创建 `PromptBuilder.java` - 建造者接口 + +```java +package ai.chat2db.server.web.api.controller.ai.prompt; + +public interface PromptBuilder { + PromptBuilder context(PromptContext context); + PromptBuilder message(String message); + PromptBuilder ext(String ext); + PromptBuilder schema(String schemaDdl); + PromptBuilder dataSourceType(String dataSourceType); + PromptBuilder targetSqlType(String targetSqlType); + PromptBuilder forTableSelection(boolean forTableSelection); + String build(); + boolean validate(); +} +``` + +#### 2.6 创建 `PromptBuilderImpl.java` - 建造者实现 + +```java +package ai.chat2db.server.web.api.controller.ai.prompt; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; +import ai.chat2db.server.web.api.controller.ai.enums.PromptType; + +@Component +public class PromptBuilderImpl implements PromptBuilder { + + private final PromptTemplateRegistry templateRegistry; + private final PromptValidator validator; + + private PromptContext context; + + public PromptBuilderImpl(PromptTemplateRegistry templateRegistry, + PromptValidator validator) { + this.templateRegistry = templateRegistry; + this.validator = validator; + } + + @Override + public PromptBuilder context(PromptContext context) { + this.context = context; + return this; + } + + @Override + public PromptBuilder message(String message) { + if (this.context == null) { + this.context = new PromptContext(); + } + this.context.setMessage(message); + return this; + } + + @Override + public PromptBuilder ext(String ext) { + if (this.context == null) { + this.context = new PromptContext(); + } + this.context.setExt(ext); + return this; + } + + @Override + public PromptBuilder schema(String schemaDdl) { + if (this.context == null) { + this.context = new PromptContext(); + } + this.context.setSchemaDdl(schemaDdl); + return this; + } + + @Override + public PromptBuilder dataSourceType(String dataSourceType) { + if (this.context == null) { + this.context = new PromptContext(); + } + this.context.setDataSourceType(dataSourceType); + return this; + } + + @Override + public PromptBuilder targetSqlType(String targetSqlType) { + if (this.context == null) { + this.context = new PromptContext(); + } + this.context.setTargetSqlType(targetSqlType); + return this; + } + + @Override + public PromptBuilder forTableSelection(boolean forTableSelection) { + if (this.context == null) { + this.context = new PromptContext(); + } + this.context.setForTableSelection(forTableSelection); + return this; + } + + @Override + public String build() { + validateContext(); + + PromptType type = context.getPromptType(); + PromptTemplate template = templateRegistry.getTemplate(type); + + String builtPrompt = fillTemplate(template, context); + + if (context.isForTableSelection()) { + builtPrompt = appendTableSelectionInstruction(builtPrompt); + } + + return validator.cleanPrompt(builtPrompt); + } + + @Override + public boolean validate() { + String builtPrompt = build(); + return validator.isValidLength(builtPrompt); + } + + private void validateContext() { + if (context == null) { + throw new IllegalStateException("PromptContext is null"); + } + if (StringUtils.isBlank(context.getMessage())) { + throw new IllegalArgumentException("Message is required"); + } + } + + private String fillTemplate(PromptTemplate template, PromptContext context) { + String templateStr = template.getTemplate(); + + return templateStr + .replace("{description}", context.getPromptType().getDescription()) + .replace("{ext}", StringUtils.defaultString(context.getExt(), "")) + .replace("{db_type}", StringUtils.defaultString(context.getDataSourceType(), "MYSQL")) + .replace("{schema}", StringUtils.defaultString(context.getSchemaDdl(), "")) + .replace("{message}", context.getMessage()); + } + + private String appendTableSelectionInstruction(String prompt) { + return prompt + "\n\n请只输出 JSON,格式:{\"table_names\":[\"表名 1\",\"表名 2\"]},不要输出其他文字。"; + } +} +``` + +--- + +### 第三阶段:删除 PromptService 门面 🗑️ + +#### 3.1 删除文件 + +``` +删除:chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/utils/PromptService.java +``` + +#### 3.2 迁移引用 + +**原代码:** +```java +@Autowired +private PromptService promptService; + +String schema = promptService.buildTableColumn(param, tableNames); +String prompt = promptService.buildAutoPrompt(request); +``` + +**新代码:** +```java +@Autowired +private DatabaseService databaseService; + +@Autowired +private PromptBuilderImpl promptBuilder; + +String schema = databaseService.buildTableColumn(connectionInfo, schemaName, tableNames); +String prompt = promptBuilder.context(context).build(); +``` + +--- + +### 第四阶段:重构 Action 类 🧹 + +#### 4.1 重构 `BuildPromptAction` + +```java +package ai.chat2db.server.web.api.controller.ai.statemachine.actions; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.statemachine.StateContext; +import org.springframework.stereotype.Component; + +import ai.chat2db.server.web.api.controller.ai.enums.PromptType; +import ai.chat2db.server.web.api.controller.ai.prompt.PromptBuilder; +import ai.chat2db.server.web.api.controller.ai.prompt.PromptContext; +import ai.chat2db.server.web.api.controller.ai.request.ChatQueryRequest; +import ai.chat2db.server.web.api.controller.ai.statemachine.ChatContext; +import ai.chat2db.server.web.api.controller.ai.statemachine.ChatEvent; +import ai.chat2db.server.web.api.controller.ai.statemachine.ChatState; +import lombok.extern.slf4j.Slf4j; + +@Component +@Slf4j +public class BuildPromptAction extends BaseChatAction { + + @Autowired + private PromptBuilder promptBuilder; + + @Override + public void execute(StateContext context) { + ChatContext chatContext = getChatContext(context); + if (chatContext.isCancelled()) { + return; + } + + try { + sendStateEvent(chatContext.getSseEmitter(), + ChatState.BUILDING_PROMPT, "正在构建提示..."); + + ChatQueryRequest request = chatContext.getRequest(); + String schemaDdl = chatContext.getSchemaDdl(); + + PromptType promptType = determinePromptType(request); + + PromptContext promptContext = PromptContext.builder() + .promptType(promptType) + .message(request.getMessage()) + .ext(request.getExt()) + .schemaDdl(schemaDdl) + .dataSourceType(guessDataSourceType(schemaDdl)) + .targetSqlType(request.getDestSqlType()) + .forTableSelection(false) + .build(); + + String builtPrompt = promptBuilder.context(promptContext).build(); + chatContext.setBuiltPrompt(builtPrompt); + + context.getStateMachine().sendEvent( + MessageBuilder.withPayload(ChatEvent.PROMPT_BUILT).build() + ).subscribe(); + + } catch (Exception e) { + log.error("Build prompt failed", e); + sendError(getChatContext(context).getSseEmitter(), + "构建提示失败:" + e.getMessage()); + context.getStateMachine().sendEvent( + MessageBuilder.withPayload(ChatEvent.PROMPT_BUILD_FAILED).build() + ).subscribe(); + } + } + + private PromptType determinePromptType(ChatQueryRequest request) { + String promptType = StringUtils.isBlank(request.getPromptType()) + ? PromptType.NL_2_SQL.getCode() + : request.getPromptType(); + return PromptType.valueOf(promptType); + } + + private String guessDataSourceType(String schemaDdl) { + if (StringUtils.isEmpty(schemaDdl)) return "MYSQL"; + String upper = schemaDdl.toUpperCase(); + if (upper.contains("MYSQL") || upper.contains("AUTO_INCREMENT")) { + return "MYSQL"; + } else if (upper.contains("POSTGRES") || upper.contains("SERIAL")) { + return "POSTGRESQL"; + } else if (upper.contains("ORACLE") || upper.contains("NUMBER(")) { + return "ORACLE"; + } + return "MYSQL"; + } +} +``` + +#### 4.2 重构 `AutoSelectTablesAction` + +```java +package ai.chat2db.server.web.api.controller.ai.statemachine.actions; + +import java.util.List; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.statemachine.StateContext; +import org.springframework.stereotype.Component; + +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; + +import ai.chat2db.server.web.api.controller.ai.prompt.PromptBuilder; +import ai.chat2db.server.web.api.controller.ai.prompt.PromptContext; +import ai.chat2db.server.web.api.controller.ai.statemachine.ChatContext; +import ai.chat2db.server.web.api.controller.ai.statemachine.ChatEvent; +import ai.chat2db.server.web.api.controller.ai.statemachine.ChatState; +import ai.chat2db.server.web.api.controller.ai.enums.PromptType; +import cn.hutool.core.util.StrUtil; +import lombok.extern.slf4j.Slf4j; + +@Component +@Slf4j +public class AutoSelectTablesAction extends BaseChatAction { + + @Autowired + private PromptBuilder promptBuilder; + + @Override + public void execute(StateContext context) { + ChatContext ctx = getChatContext(context); + if (ctx.isCancelled()) { + return; + } + + try { + sendStateEvent(ctx.getSseEmitter(), + ChatState.AUTO_SELECTING_TABLES, "正在选择相关表..."); + + List tableNames = selectTables(ctx); + + if (CollectionUtils.isNotEmpty(tableNames)) { + ctx.getRequest().setTableNames(tableNames); + ctx.setSelectedTables(tableNames); + sendTablesSelected(ctx.getSseEmitter(), tableNames); + } + + context.getStateMachine().sendEvent( + MessageBuilder.withPayload(ChatEvent.AUTO_SELECT_DONE).build() + ).subscribe(); + + } catch (Exception e) { + log.error("Auto select tables failed", e); + sendError(ctx.getSseEmitter(), "选表失败:" + e.getMessage()); + context.getStateMachine().sendEvent( + MessageBuilder.withPayload(ChatEvent.AUTO_SELECT_FAILED).build() + ).subscribe(); + } + } + + private List selectTables(ChatContext ctx) { + String selectPrompt = buildSelectPrompt(ctx); + + ChatResponse chatResponse = ctx.getChatClient().prompt() + .user(selectPrompt) + .call() + .chatResponse(); + + String content = extractContent(chatResponse); + return parseTableNames(content, ctx.getRequest().getTableNames()); + } + + private String buildSelectPrompt(ChatContext ctx) { + PromptContext promptContext = PromptContext.builder() + .promptType(PromptType.SELECT_TABLES) + .message(ctx.getRequest().getMessage()) + .dataSourceType(ctx.getRequest().getDataSourceType()) + .forTableSelection(true) + .build(); + + return promptBuilder.context(promptContext).build(); + } + + // ... 其他方法保持不变 +} +``` + +#### 4.3 重构 `FetchSchemaAction` + +```java +package ai.chat2db.server.web.api.controller.ai.statemachine.actions; + +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.statemachine.StateContext; +import org.springframework.stereotype.Component; + +import ai.chat2db.server.domain.api.service.DatabaseService; +import ai.chat2db.server.web.api.controller.ai.enums.PromptType; +import ai.chat2db.server.web.api.controller.ai.request.ChatQueryRequest; +import ai.chat2db.server.web.api.controller.ai.statemachine.ChatContext; +import ai.chat2db.server.web.api.controller.ai.statemachine.ChatEvent; +import ai.chat2db.server.web.api.controller.ai.statemachine.ChatState; +import lombok.extern.slf4j.Slf4j; + +@Component +@Slf4j +public class FetchSchemaAction extends BaseChatAction { + + @Autowired + private DatabaseService databaseService; + + @Override + public void execute(StateContext context) { + ChatContext ctx = getChatContext(context); + if (ctx.isCancelled()) { + return; + } + + try { + sendStateEvent(ctx.getSseEmitter(), + ChatState.FETCHING_TABLE_SCHEMA, "正在获取表结构..."); + + String schemaDdl = fetchSchemaDdl(ctx); + ctx.setSchemaDdl(schemaDdl); + + if (CollectionUtils.isNotEmpty(ctx.getRequest().getTableNames())) { + sendSchemaFetched(ctx.getSseEmitter(), schemaDdl); + } + + context.getStateMachine().sendEvent( + MessageBuilder.withPayload(ChatEvent.SCHEMA_FETCHED).build() + ).subscribe(); + + } catch (Exception e) { + log.error("Fetch schema failed", e); + sendError(ctx.getSseEmitter(), "获取表结构失败:" + e.getMessage()); + context.getStateMachine().sendEvent( + MessageBuilder.withPayload(ChatEvent.FETCH_SCHEMA_FAILED).build() + ).subscribe(); + } + } + + private String fetchSchemaDdl(ChatContext ctx) { + ChatQueryRequest request = ctx.getRequest(); + + if (isTextGeneration(request)) { + return ""; + } else if (hasTableNames(request)) { + return queryTablesWithNames(request); + } else { + return queryAllTables(request); + } + } + + private boolean isTextGeneration(ChatQueryRequest request) { + return PromptType.TEXT_GENERATION.getCode().equals(request.getPromptType()); + } + + private boolean hasTableNames(ChatQueryRequest request) { + return CollectionUtils.isNotEmpty(request.getTableNames()); + } + + private String queryTablesWithNames(ChatQueryRequest request) { + // 使用 DatabaseService 的新方法 + return databaseService.buildTableColumn( + buildConnectionInfo(request), + request.getSchemaName(), + request.getTableNames() + ); + } + + private String queryAllTables(ChatQueryRequest request) { + // 使用 DatabaseService 的新方法 + return databaseService.queryDatabaseTables( + buildConnectionInfo(request), + request.getSchemaName() + ); + } +} +``` + +--- + +### 第五阶段:重构 StreamAction 🔒 + +```java +package ai.chat2db.server.web.api.controller.ai.statemachine.actions; + +import java.io.IOException; +import java.util.Objects; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.statemachine.StateContext; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import com.alibaba.fastjson2.JSONObject; +import com.unfbx.chatgpt.entity.chat.Message; + +import ai.chat2db.server.web.api.controller.ai.prompt.PromptValidator; +import ai.chat2db.server.web.api.controller.ai.statemachine.ChatContext; +import ai.chat2db.server.web.api.controller.ai.statemachine.ChatEvent; +import ai.chat2db.server.web.api.controller.ai.statemachine.ChatState; +import lombok.extern.slf4j.Slf4j; +import reactor.core.publisher.Flux; +import reactor.core.scheduler.Schedulers; + +@Component +@Slf4j +public class StreamAction extends BaseChatAction { + + @Autowired + private PromptValidator promptValidator; + + @Override + public void execute(StateContext context) { + ChatContext ctx = getChatContext(context); + if (ctx.isCancelled()) { + return; + } + + String prompt = ctx.getBuiltPrompt(); + + // 使用 PromptValidator 验证 + if (!promptValidator.isValidLength(prompt)) { + sendError(ctx.getSseEmitter(), "提示语超出最大长度"); + context.getStateMachine().sendEvent( + MessageBuilder.withPayload(ChatEvent.AI_CALL_FAILED).build() + ).subscribe(); + return; + } + + try { + sendStateEvent(ctx.getSseEmitter(), ChatState.STREAMING, "AI 正在生成..."); + + Flux flux = ctx.getChatClient().prompt() + .user(prompt) + .stream() + .content(); + + // ... 流处理逻辑保持不变 + + } catch (Exception e) { + log.error("Start streaming failed", e); + sendError(ctx.getSseEmitter(), "启动 AI 流式调用失败:" + e.getMessage()); + context.getStateMachine().sendEvent( + MessageBuilder.withPayload(ChatEvent.AI_CALL_FAILED).build() + ).subscribe(); + } + } +} +``` + +--- + +## 新架构优势 + +| 方面 | 重构前 | 重构后 | +|------|--------|--------| +| **服务层** | PromptService (311 行上帝类) | DatabaseService (复用现有服务) | +| **门面模式** | PromptService 作为门面 | 删除门面,直接使用建造者 | +| **提示词构建** | 分散在 Action 和 Service 中 | PromptBuilder 集中管理 | +| **模板管理** | 硬编码在方法中 | PromptTemplateRegistry 集中注册 | +| **代码重复** | BuildPromptAction 和 PromptService 重复 | 单一事实来源 | +| **可测试性** | 难以独立测试模板 | 模板和建造者可单元测试 | +| **类数量** | 1 个大类 (PromptService) | 6 个专注类 | +| **职责分离** | 混乱 | 清晰 | + +--- + +## 重构后文件结构 + +``` +chat2db-server/ +└── chat2db-server-web/ + └── chat2db-server-web-api/ + └── src/main/java/ai/chat2db/server/web/api/controller/ai/ + ├── prompt/ # 新增包 + │ ├── PromptBuilder.java # 建造者接口 + │ ├── PromptBuilderImpl.java # 建造者实现 + │ ├── PromptContext.java # 上下文对象 + │ ├── PromptTemplate.java # 模板值对象 + │ ├── PromptTemplateRegistry.java # 模板注册表 + │ └── PromptValidator.java # 验证器 + │ + ├── statemachine/ + │ └── actions/ + │ ├── BuildPromptAction.java # 重构:使用 PromptBuilder + │ ├── AutoSelectTablesAction.java # 重构:使用 PromptBuilder + │ ├── FetchSchemaAction.java # 重构:使用 DatabaseService + │ └── StreamAction.java # 重构:使用 PromptValidator + │ + ├── enums/ + │ └── PromptType.java # 保持不变 + │ + └── utils/ + └── PromptService.java # 删除 🗑️ + +chat2db-server-domain/ +└── chat2db-server-domain-api/ + └── src/main/java/ai/chat2db/server/domain/api/service/ + └── DatabaseService.java # 扩展:添加 Schema 获取方法 +``` + +--- + +## 迁移步骤 + +### 第 1 周:扩展 DatabaseService +1. 在 `DatabaseService` 接口中添加新方法 +2. 在 `DatabaseServiceImpl` 中实现 +3. 编写单元测试 + +### 第 2 周:创建 Prompt 包 +1. 创建 `PromptTemplate` 和 `PromptTemplateRegistry` +2. 创建 `PromptValidator` +3. 创建 `PromptContext` +4. 实现 `PromptBuilder` 接口和实现 + +### 第 3 周:重构 Action 类 +1. 重构 `BuildPromptAction` +2. 重构 `AutoSelectTablesAction` +3. 重构 `FetchSchemaAction` +4. 重构 `StreamAction` + +### 第 4 周:清理与测试 +1. 删除 `PromptService` +2. 更新所有引用 +3. 集成测试 +4. 回归测试 + +--- + +## 测试策略 + +### 单元测试 + +```java +// PromptTemplateRegistryTest.java +@Test +void testGetTemplateByType() { + PromptTemplate template = registry.getTemplate(PromptType.NL_2_SQL); + assertNotNull(template); + assertTrue(template.getTemplate().contains("{schema}")); +} + +// PromptBuilderImplTest.java +@Test +void testBuildPromptWithSchema() { + PromptContext context = PromptContext.builder() + .promptType(PromptType.NL_2_SQL) + .message("查询用户") + .schemaDdl("CREATE TABLE user...") + .dataSourceType("MYSQL") + .build(); + + String prompt = promptBuilder.context(context).build(); + assertTrue(prompt.contains("MYSQL SQL tables")); + assertTrue(prompt.contains("查询用户")); +} + +// PromptValidatorTest.java +@Test +void testPromptLengthValidation() { + String longPrompt = "x".repeat(70000); // 超过 15400 tokens + assertFalse(validator.isValidLength(longPrompt)); +} +``` + +### 集成测试 + +```java +// BuildPromptActionIntegrationTest.java +@Test +void testBuildPromptActionFlow() { + // 模拟状态机执行 + stateMachine.sendEvent(MessageBuilder.withPayload(ChatEvent.TABLES_PROVIDED).build()); + + // 验证 prompt 被正确构建 + verify(chatContext).setBuiltPrompt(anyString()); + verify(stateMachine).sendEvent( + MessageBuilder.withPayload(ChatEvent.PROMPT_BUILT).build() + ); +} +``` + +--- + +## 风险提示 + +| 风险 | 影响 | 缓解措施 | +|------|------|----------| +| DatabaseService 方法实现复杂 | 中 | 充分测试,复用现有 TableService | +| Action 类重构遗漏 | 高 | 全局搜索 PromptService 引用 | +| 模板格式变化 | 中 | 保持向后兼容,渐进迁移 | +| 性能回退 | 低 | 基准测试,优化模板加载 | + +--- + +## 成功指标 + +1. **代码质量** + - 删除 PromptService (311 行) + - BuildPromptAction 减少到 ~60 行 + - 测试覆盖率 > 80% + +2. **架构清晰度** + - 提示词模板集中管理 + - DatabaseService 负责数据获取 + - PromptBuilder 负责提示词构建 + +3. **开发效率** + - 新增提示词类型 < 30 分钟 + - 模板修改无需重新编译(如外部化) + +--- + +## 总结 + +本次重构核心变更: + +1. ✅ **删除 PromptService 门面** - 减少中间层 +2. ✅ **扩展 DatabaseService** - 复用现有服务,添加 Schema 获取方法 +3. ✅ **创建 PromptBuilder** - 专注提示词构建 +4. ✅ **创建 PromptTemplateRegistry** - 集中管理模板 +5. ✅ **重构 Action 类** - 使用新架构 + +重构后架构更清晰、职责更明确、易于维护和扩展。 diff --git a/package.json b/package.json new file mode 100644 index 000000000..668e18891 --- /dev/null +++ b/package.json @@ -0,0 +1 @@ +{"dependencies": {}} \ No newline at end of file