From 1b90e552a51492ad8e84a54870f6512029a4bdde Mon Sep 17 00:00:00 2001 From: baozaotumao2025 Date: Sat, 18 Jul 2026 21:10:39 +0800 Subject: [PATCH] first commit --- .codegraph/.gitignore | 5 + .dockerignore | 5 + .env | 2 + .env.example | 8 + .gitignore | 10 + .nvmrc | 1 + .pnpm-store/v11/index.db | Bin 0 -> 8192 bytes AGENTS.md | 18 + Dockerfile | 15 + README.md | 67 + compose.yaml | 9 + docs/api-contracts.md | 32 + docs/architecture.md | 28 + docs/interaction-design.md | 28 + docs/operations.md | 33 + docs/ui-design.md | 63 + eslint.config.js | 19 + index.html | 13 + nginx.conf | 21 + package.json | 51 + pnpm-lock.yaml | 6295 ++++++++++++++++++ src/api/client.ts | 57 + src/api/index.ts | 9 + src/app/App.tsx | 29 + src/app/AppShell.tsx | 51 + src/app/guards.tsx | 15 + src/auth/AuthProvider.tsx | 63 + src/auth/activity.ts | 24 + src/auth/refresh.ts | 83 + src/auth/session.ts | 36 + src/components/AsyncState.tsx | 23 + src/components/PageHeader.tsx | 10 + src/components/ReportSummary.tsx | 33 + src/components/ThemeToggle.tsx | 7 + src/design/ThemeProvider.tsx | 38 + src/design/theme.ts | 49 + src/errors/AppError.ts | 23 + src/errors/ErrorBoundary.tsx | 20 + src/errors/index.ts | 2 + src/errors/normalizeError.ts | 42 + src/logging/index.ts | 2 + src/logging/logger.ts | 20 + src/logging/sanitize.ts | 11 + src/main.tsx | 14 + src/pages/AccountPage.tsx | 23 + src/pages/LoginPage.tsx | 50 + src/pages/NewRunPage.tsx | 53 + src/pages/OverviewPage.tsx | 39 + src/pages/ProvidersPage.tsx | 57 + src/pages/ReportsPage.tsx | 15 + src/pages/RunDetailPage.tsx | 100 + src/pages/RunsPage.tsx | 32 + src/pages/UsersPage.tsx | 32 + src/pages/runUi.ts | 47 + src/schemas/auth.ts | 47 + src/schemas/common.ts | 13 + src/schemas/health.ts | 4 + src/schemas/providers.ts | 47 + src/schemas/reports.ts | 10 + src/schemas/runs.ts | 65 + src/services/authService.ts | 27 + src/services/healthService.ts | 4 + src/services/index.ts | 14 + src/services/providerService.ts | 22 + src/services/reportService.ts | 7 + src/services/runService.ts | 17 + src/styles.css | 99 + src/vite-env.d.ts | 1 + tests/e2e/backend.test.ts | 29 + tests/integration/pages.test.tsx | 210 + tests/integration/serviceContracts.test.ts | 50 + tests/integration/themeToggle.test.tsx | 15 + tests/setup.ts | 15 + tests/unit/api/client.test.ts | 48 + tests/unit/auth/activity.test.ts | 33 + tests/unit/auth/refresh.test.ts | 101 + tests/unit/auth/session.test.ts | 30 + tests/unit/components/ReportSummary.test.tsx | 32 + tests/unit/design/consistency.test.ts | 37 + tests/unit/design/contrast.test.ts | 38 + tests/unit/design/theme.test.ts | 21 + tests/unit/errors/normalizeError.test.ts | 18 + tests/unit/logging/sanitize.test.ts | 18 + tests/unit/pages/runUi.test.tsx | 43 + tests/unit/schemas/reports.test.ts | 25 + tests/unit/schemas/runDetail.test.ts | 27 + tsconfig.app.json | 21 + tsconfig.json | 7 + tsconfig.node.json | 10 + vite.config.ts | 7 + vitest.config.ts | 12 + 91 files changed, 9056 insertions(+) create mode 100644 .codegraph/.gitignore create mode 100644 .dockerignore create mode 100644 .env create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 .nvmrc create mode 100644 .pnpm-store/v11/index.db create mode 100644 AGENTS.md create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 compose.yaml create mode 100644 docs/api-contracts.md create mode 100644 docs/architecture.md create mode 100644 docs/interaction-design.md create mode 100644 docs/operations.md create mode 100644 docs/ui-design.md create mode 100644 eslint.config.js create mode 100644 index.html create mode 100644 nginx.conf create mode 100644 package.json create mode 100644 pnpm-lock.yaml create mode 100644 src/api/client.ts create mode 100644 src/api/index.ts create mode 100644 src/app/App.tsx create mode 100644 src/app/AppShell.tsx create mode 100644 src/app/guards.tsx create mode 100644 src/auth/AuthProvider.tsx create mode 100644 src/auth/activity.ts create mode 100644 src/auth/refresh.ts create mode 100644 src/auth/session.ts create mode 100644 src/components/AsyncState.tsx create mode 100644 src/components/PageHeader.tsx create mode 100644 src/components/ReportSummary.tsx create mode 100644 src/components/ThemeToggle.tsx create mode 100644 src/design/ThemeProvider.tsx create mode 100644 src/design/theme.ts create mode 100644 src/errors/AppError.ts create mode 100644 src/errors/ErrorBoundary.tsx create mode 100644 src/errors/index.ts create mode 100644 src/errors/normalizeError.ts create mode 100644 src/logging/index.ts create mode 100644 src/logging/logger.ts create mode 100644 src/logging/sanitize.ts create mode 100644 src/main.tsx create mode 100644 src/pages/AccountPage.tsx create mode 100644 src/pages/LoginPage.tsx create mode 100644 src/pages/NewRunPage.tsx create mode 100644 src/pages/OverviewPage.tsx create mode 100644 src/pages/ProvidersPage.tsx create mode 100644 src/pages/ReportsPage.tsx create mode 100644 src/pages/RunDetailPage.tsx create mode 100644 src/pages/RunsPage.tsx create mode 100644 src/pages/UsersPage.tsx create mode 100644 src/pages/runUi.ts create mode 100644 src/schemas/auth.ts create mode 100644 src/schemas/common.ts create mode 100644 src/schemas/health.ts create mode 100644 src/schemas/providers.ts create mode 100644 src/schemas/reports.ts create mode 100644 src/schemas/runs.ts create mode 100644 src/services/authService.ts create mode 100644 src/services/healthService.ts create mode 100644 src/services/index.ts create mode 100644 src/services/providerService.ts create mode 100644 src/services/reportService.ts create mode 100644 src/services/runService.ts create mode 100644 src/styles.css create mode 100644 src/vite-env.d.ts create mode 100644 tests/e2e/backend.test.ts create mode 100644 tests/integration/pages.test.tsx create mode 100644 tests/integration/serviceContracts.test.ts create mode 100644 tests/integration/themeToggle.test.tsx create mode 100644 tests/setup.ts create mode 100644 tests/unit/api/client.test.ts create mode 100644 tests/unit/auth/activity.test.ts create mode 100644 tests/unit/auth/refresh.test.ts create mode 100644 tests/unit/auth/session.test.ts create mode 100644 tests/unit/components/ReportSummary.test.tsx create mode 100644 tests/unit/design/consistency.test.ts create mode 100644 tests/unit/design/contrast.test.ts create mode 100644 tests/unit/design/theme.test.ts create mode 100644 tests/unit/errors/normalizeError.test.ts create mode 100644 tests/unit/logging/sanitize.test.ts create mode 100644 tests/unit/pages/runUi.test.tsx create mode 100644 tests/unit/schemas/reports.test.ts create mode 100644 tests/unit/schemas/runDetail.test.ts create mode 100644 tsconfig.app.json create mode 100644 tsconfig.json create mode 100644 tsconfig.node.json create mode 100644 vite.config.ts create mode 100644 vitest.config.ts diff --git a/.codegraph/.gitignore b/.codegraph/.gitignore new file mode 100644 index 0000000..d20c0fe --- /dev/null +++ b/.codegraph/.gitignore @@ -0,0 +1,5 @@ +# CodeGraph data files — local to each machine, not for committing. +# Ignore everything in .codegraph/ except this file itself, so transient +# files (the database, daemon.pid, sockets, logs) never show up in git. +* +!.gitignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..c16a5a8 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +node_modules +dist +.git +coverage +.env diff --git a/.env b/.env new file mode 100644 index 0000000..feec408 --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +VITE_API_BASE_URL=http://localhost:8000/api/v1 +VITE_LOG_LEVEL=info diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..054f64f --- /dev/null +++ b/.env.example @@ -0,0 +1,8 @@ +# Backend API root. Restart the dev server after changing it. +VITE_API_BASE_URL=http://localhost:8000/api/v1 + +# Frontend console log threshold: debug | info | warn | error +VITE_LOG_LEVEL=info + +# Never put passwords, tokens, JUDGE_API_KEY, or TARGET_API_KEY in VITE_ variables. +# Every VITE_ variable is bundled into browser-visible frontend code. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b14d7c1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +node_modules/ +dist/ +coverage/ +.env.local +*.log +*.tsbuildinfo +vite.config.js +vite.config.d.ts +vitest.config.js +vitest.config.d.ts diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..209e3ef --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +20 diff --git a/.pnpm-store/v11/index.db b/.pnpm-store/v11/index.db new file mode 100644 index 0000000000000000000000000000000000000000..7f9770bd25137067815560f9ef52a33e713f2b53 GIT binary patch literal 8192 zcmeIuzpBD86bA4#2p0s=&GDX11#$5OY&BppTCFMSqD0LV@%|C%po4=C;1j#tR!Xsx zd-*<+oFpepe$$EEhlalXPCq)NHmfksS%-)*#*-P9XRK%~B>T9;=Xc?(b$^tiS5|q+ zqJcmF0uX=z1Rwwb2tWV=5P$##awu^7v_7h}nsvK|di`yVdUMb_v)cb|%{g=6U0>Kr zkg^>qDAS^Pkk?+mi kUUFZI)hjuq$Cn@g0SG_<0uX=z1Rwwb2tWV=5J(070T$gf00000 literal 0 HcmV?d00001 diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..296f076 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,18 @@ +# Repository Instructions + +## UI changes + +Before changing UI, themes, layout, spacing, colors, or components: + +1. Read `docs/ui-design.md`. +2. Reuse its global design tokens; do not introduce page-specific radii, spacing, or semantic colors. +3. Update or add a failing test before changing behavior or visual rules. +4. Run: + + ```bash + pnpm test -- --run tests/unit/design/consistency.test.ts + pnpm test -- --run tests/unit/design/contrast.test.ts + pnpm check + ``` + +Switches, circular progress, avatars, and other function-defined shapes are exempt from the shared radius rule. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e7cf14a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +FROM node:22-alpine AS build +WORKDIR /app +RUN corepack enable +COPY package.json pnpm-lock.yaml ./ +RUN pnpm install --frozen-lockfile +COPY . . +ARG VITE_API_BASE_URL=http://127.0.0.1:8000/api/v1 +ENV VITE_API_BASE_URL=$VITE_API_BASE_URL +RUN pnpm build + +FROM nginx:1.27-alpine +COPY nginx.conf /etc/nginx/conf.d/default.conf +COPY --from=build /app/dist /usr/share/nginx/html +EXPOSE 80 +HEALTHCHECK CMD wget -qO- http://127.0.0.1/healthz || exit 1 diff --git a/README.md b/README.md new file mode 100644 index 0000000..7ca9f3b --- /dev/null +++ b/README.md @@ -0,0 +1,67 @@ +# AI Safety Console Web + +AI 模型内部安全测试平台的独立 React 前端。UI 使用官方 [AgentScope Spark Design](https://sparkdesign.agentscope.io/),提供可持久化切换的蓝色安全控制台和浅色专业两套主题。 + +## 项目结构 + +```text +src/ +├── api/ # Fetch 客户端与 Zod 响应校验 +├── auth/ # 会话、身份恢复和登退录 +├── design/ # Spark tokens、两套主题与切换状态 +├── errors/ # 错误类型、归一化和 React Error Boundary +├── logging/ # 结构化日志与敏感数据脱敏 +├── schemas/ # Zod 请求/响应和业务不变量 +├── services/ # 与全部后端端点对应的业务服务 +├── pages/ # 页面交互 +└── components/ # 可复用 UI 状态和主题切换 +tests/ +├── unit/ # 日志、错误、schema、API 和主题单元测试 +└── integration/ # 端点契约和 UI 切换集成测试 +docs/ # UI、交互、架构、API、测试与部署文档 +``` + +认证支持 access/refresh token 原子轮换、活跃用户静默续约、标签页内/跨标签页 single-flight,以及业务请求 401 后的一次续约与重放。跨标签页协调使用浏览器 Web Locks API。 + +## 本地开发 + +需要 Node.js 20–24 和 Corepack。 + +```bash +corepack enable +pnpm install --frozen-lockfile +cp .env.example .env +pnpm dev +``` + +默认访问 `http://localhost:5173`,API 默认为 `http://127.0.0.1:8000/api/v1`。不要将模型 API Key 放入前端 `.env`;密钥只由后端保管。 + +## 质量检查 + +```bash +pnpm check +``` + +真实后端检查(不会使用 mock,需提供测试账户): + +```bash +E2E_API_BASE_URL=http://localhost:8000/api/v1 E2E_USERNAME=admin E2E_PASSWORD=密码 pnpm test:e2e +``` + +该命令依次运行 ESLint、Vitest 和生产构建。 + +## 容器部署 + +```bash +VITE_API_BASE_URL=https://api.example.com/api/v1 docker compose up --build -d +``` + +默认映射到 `http://localhost:8080`。`VITE_API_BASE_URL` 是构建时变量,修改后需重新构建镜像。 + +## 文档 + +- [UI 规范(强制令牌与提交门禁)](docs/ui-design.md) +- [交互设计](docs/interaction-design.md) +- [架构与主题](docs/architecture.md) +- [API 映射](docs/api-contracts.md) +- [测试、安全与运维](docs/operations.md) diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..fad7191 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,9 @@ +services: + web: + build: + context: . + args: + VITE_API_BASE_URL: ${VITE_API_BASE_URL:-http://127.0.0.1:8000/api/v1} + ports: + - "${WEB_PORT:-8080}:80" + restart: unless-stopped diff --git a/docs/api-contracts.md b/docs/api-contracts.md new file mode 100644 index 0000000..a5fb84b --- /dev/null +++ b/docs/api-contracts.md @@ -0,0 +1,32 @@ +# API 映射 + +所有路径相对 `VITE_API_BASE_URL`,默认为 `/api/v1` 服务。除登录外,请求都携带 Bearer token。 + +| 领域 | 前端操作 | 后端端点 | +| --- | --- | --- | +| 认证 | 登录、续约、当前用户、修改密码、退出 | `POST /auth/login`, `POST /auth/refresh`, `GET /auth/me`, `PATCH /auth/password`, `POST /auth/logout` | +| 用户 | 列表、创建、启停、重置密码、删除 | `GET/POST /auth/users`, `PATCH/DELETE /auth/users/{id}`, `PATCH /auth/users/{id}/password` | +| 提供商 | 列表、详情、创建、修改、删除 | `GET/POST /providers`, `GET/PATCH/DELETE /providers/{id}` | +| 提供商 | 连通检查、模型发现 | `POST /providers/{id}/check`, `GET /providers/{id}/models` | +| 运行 | 列表、创建、详情、取消、删除 | `GET/POST /runs`, `GET/PATCH/DELETE /runs/{id}` | +| 运行 | 恢复、重试错误、单条重试、逐条结果 | `POST /runs/{id}/resume`, `POST /runs/{id}/retry-errors`, `POST /runs/{id}/results/{execution_id}/retry`, `GET /runs/{id}/results` | +| 报告 | 列表、详情 | `GET /reports`, `GET /reports/{run_id}` | +| 系统 | 健康状态 | `GET /health` | + +## 数据规则 + +- API 响应必须通过 Zod 校验,否则转为 `INVALID_RESPONSE`,不把不可信数据交给 UI。 +- 运行详情必须满足 `processed_count = completed_count + error_count`;`error_count` 仅统计执行异常,不包含 `verdict = fail`;终态必须有 `poll_after_seconds = 0`。 +- 逐条结果使用 `execution_status`(`completed | error`)表示调用是否完成,使用可空 `verdict`(`pass | fail | needs_human_review | judge_format_error | null`)表示仲裁结论;前端不读取旧 `status`。 +- 逐条结果的执行 ID 搜索直接使用 `GET /runs/{id}/results` 已返回的 `execution_id` 在 UI 过滤,不新增请求参数或后端端点。 +- 报告的 `summary.execution_statuses` 与 `summary.verdicts` 分开统计;`by_mode` 下也分别包含 `execution_statuses` 和 `verdicts`。 +- `POST /runs/{id}/retry-errors` 只重试 `execution_status = error`,不重试任何仲裁结论。 +- `POST /runs/{id}/results/{execution_id}/retry` 无请求体,沿用原 Run 的测试范围和自动仲裁配置,并以相同 `execution_id` 替换旧结果;仅 `completed / completed_with_errors` 状态允许提交。 +- `POST /runs/{id}/resume` 无请求体,返回 `skipped_count`;`POST /runs/{id}/retry-errors` 无请求体,返回 `retry_count`。两者返回 202 后必须重新获取运行详情,不能将 POST 响应视为执行完成;409 时也重新获取详情。 +- 单条重试返回 202 后按运行详情的 `poll_after_seconds`(缺省 2 秒)轮询,终态后重新获取结果。重跑期间旧结果可能暂时不存在,UI 保留“重新执行中”状态;404 刷新结果,409 刷新 Run,网络错误或 5xx 先刷新 Run,避免不确定是否受理时重复提交。 +- `POST /runs` 总是携带新的 UUID `Idempotency-Key`。 +- 登录和续约都会原子替换 access/refresh token 及两个过期时间。 +- access token 剩余不超过 30 分钟且用户最近 30 分钟有真实操作时,`POST /auth/refresh` 携带 UUID `Idempotency-Key`。 +- 网络错误和 5xx 最多尝试 3 次,且一直复用原 refresh token 和原 key;409 保留凭据并等待 `refresh_after`。 +- 普通业务请求首次 401 共享 single-flight 续约,成功后仅重放一次;只有 refresh 本身返回 401 才清除会话。 +- HTTP 204 按无响应体处理;403/404/409/422/502 映射为可操作的用户消息。 diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..b57e0c9 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,28 @@ +# 架构与主题 + +## 数据流 + +UI 页面只调用 `services/`;service 先用 `schemas/` 中的 Zod schema 校验请求,再由 `api/client.ts` 发起请求并用 Zod 校验响应。跨字段业务不变量也在 schema 层完成。错误统一进入 `errors/`,日志进入 `logging/` 并在输出前递归脱敏。 + +```text +pages/components → services → schemas + api client → FastAPI + ↓ + errors + logging +``` + +## Spark Design + +组件仅从官方 `@agentscope-ai/design` 引入。`src/design/theme.ts` 是主题注册表,它将 Spark/Ant Design token 与业务页分开;`ThemeProvider.tsx` 只负责注入主题和切换状态;`styles.css` 使用语义 CSS 变量,不在页面内写主题颜色。 + +当前主题: + +- `blue`:默认的深蓝安全控制台。 +- `light`:以 Spark 官方 `#615CED` 为主色、`#FAFAFA` 为布局底色的浅色方案。 + +新增主题时,扩展 `ThemeName`、`themes` 注册表和对应 `[data-theme]` CSS 变量,再把两态切换控件改为选择器即可,不需要修改业务页。 + +## 认证与路由 + +access token、refresh token 和两个过期时间作为一个 JSON 对象原子保存在 `localStorage`,使多标签页共享同一会话。启动时通过 `/auth/me` 恢复身份。`AuthGuard` 保护业务路由,`AdminGuard` 保护用户管理。 + +`auth/activity.ts` 只记录点击、键盘、触摸和导航,30 秒内最多写入一次;轮询和页面可见本身不算活动。`auth/refresh.ts` 在用户活跃且 access token 进入最后 30 分钟时静默续约。标签页内用共享 Promise,标签页间用 Web Locks 保证只有一个 refresh;待刷新的业务请求用新 access token 重放一次。refresh 401 才清理会话,续约成功不刷新路由、当前用户查询或业务状态。 diff --git a/docs/interaction-design.md b/docs/interaction-design.md new file mode 100644 index 0000000..bcd433a --- /dev/null +++ b/docs/interaction-design.md @@ -0,0 +1,28 @@ +# 交互设计 + +## 主流程 + +1. 用户登录,系统用 `/auth/me` 确认身份和权限。 + 活跃用户在 access token 剩余不超过 30 分钟时静默续约;续约不跳转、不刷新页面,不清空当前任务。 +2. 概览页确认后端健康、模型配置和近期运行。 +3. 管理员在模型配置页建立 target/judge 配置,可查询模型并执行最小连通性检查。 +4. 用户选择 smoke/all 与是否自动裁判,确认后创建运行。客户端自动携带 UUID 幂等键,避免重复提交。 +5. 详情页展示探测、执行、裁判和完成阶段,按后端 `poll_after_seconds` 轮询。 +6. 运行中可取消;中断后可恢复;局部错误可只重试错误样例;终态后可查看逐条结果、汇总报告或确认删除。 + `cancelled / failed / pending / probing / running` 可确认后“恢复运行”;`completed_with_errors` 且 `error_count > 0` 可确认后“重试错误(N)”。提交期间按钮禁用,成功提示跳过或重试数量;409 会刷新详情以校正操作状态。 +7. 逐条结果可按执行状态、仲裁结论筛选,并可按执行 ID 搜索;三项条件组合生效,执行 ID 支持忽略大小写的片段匹配。 +8. `completed / completed_with_errors` 的每条结果可确认后单独“重新执行”。提交中仅该按钮显示“提交中…”,受理后该 Run 的全部单条重试按钮锁定并显示目标执行 ID“重新执行中…”,直到 Run 再次进入终态。目标结果会进入跟踪状态并置顶,暂时绕过执行状态、仲裁结论和执行 ID 筛选,用户可手动停止跟踪;原筛选值始终保留。 + +## 模型等待交互 + +- 创建成功后立即进入运行详情,不用一个无信息的全屏 loading 阻塞。 +- Steps 显示当前阶段,环形进度和成功/错误/总数提供可量化反馈,当前样例 ID 说明系统仍在前进。 +- 文案明确告知“可安全离开”;顶栏在全站显示活跃运行数,返回后会恢复轮询。 +- 运行期间每 3 秒同步已落库的逐条结果。运行进入终态时会立即执行最后一次结果同步,再停止轮询,避免错过最后落库的样例。 +- 终态是停止常规轮询的唯一条件。网络错误显示可重试状态,不把未知状态误报为失败。 +- 恢复或重试成功只表示任务已重新排队;详情立即刷新,并继续严格使用最新 `poll_after_seconds` 轮询,直到 `terminal = true`。 +- 单条重跑会短暂删除旧结果;结果列表找不到目标执行 ID 时继续显示“正在等待跟踪结果…”,终态后用相同执行 ID 置顶展示新结果,即使新状态不符合原筛选也不消失。网络错误或 5xx 先刷新 Run 状态,发现已进入执行态时不重复提交。 + +## 闭环与破坏性操作 + +创建、修改、检查、重试等操作都有进行中状态和成功/失败反馈。取消、删除、禁用用户、重置密码等高风险操作要求确认。修改本人密码后所有旧 token 立即失效,前端清理会话并要求使用新密码登录。 diff --git a/docs/operations.md b/docs/operations.md new file mode 100644 index 0000000..8b422c9 --- /dev/null +++ b/docs/operations.md @@ -0,0 +1,33 @@ +# 测试、安全与运维 + +## 测试 + +`tests/unit` 覆盖脱敏、错误归一化、主题持久化、API 响应校验、认证原子存储、single-flight 续约、幂等重试、401 单次重放、运行数据不变量、结果语义标识和运行进入终态时的最终结果同步。`tests/integration` 验证主题切换、逐条结果筛选与搜索、单条重试结果跨筛选置顶跟踪等页面交互,以及所有 service 到后端路由的映射。 + +```bash +pnpm test +pnpm lint +pnpm build +``` + +## 安全 + +- 前端不包含 `TARGET_API_KEY` 或 `JUDGE_API_KEY`,也不应在 Vite 变量中暴露它们。 +- 完整认证对象保存在 `localStorage` 以支持多标签页原子轮换,不分开写入 access/refresh token;生产环境必须使用 HTTPS 并保持严格 CSP,降低 XSS 窃取风险。 +- 续约的待处理 refresh token 和 `Idempotency-Key` 作为一组保存;网络/5xx 失败后不换 key,refresh 401 或用户主动退出才清理凭据。 +- logger 对 password、token、API key、Authorization 等键及嵌套数据递归脱敏。 +- UI 权限控制只用于交互,真实授权必须由后端执行。 +- 生产环境应使用 HTTPS,并将后端 CORS 限制为实际前端域名。 + +## 日志与错误 + +`VITE_LOG_LEVEL` 可为 `debug` / `info` / `warn` / `error`。生产环境推荐 `warn`。日志是结构化对象,但不发送到外部系统;如果日后引入观测平台,只需替换 `logging/logger.ts` 的输出端。React Error Boundary 处理未捕获的渲染错误,请求错误由页面提供重试入口。 + +## 部署 + +Docker 采用 Node 构建 + Nginx 静态服务两阶段镜像。Nginx 配置了 SPA history fallback、静态资源缓存和 `/healthz`。API 地址在构建时写入: + +```bash +docker build --build-arg VITE_API_BASE_URL=https://api.example.com/api/v1 -t ai-safety-console-web . +docker run --rm -p 8080:80 ai-safety-console-web +``` diff --git a/docs/ui-design.md b/docs/ui-design.md new file mode 100644 index 0000000..d9854e5 --- /dev/null +++ b/docs/ui-design.md @@ -0,0 +1,63 @@ +# UI 设计方案 + +## 设计目标 + +界面服务于高风险模型测试操作:优先显示系统状态、当前进度、错误原因和下一步操作,同时保留类 Apple 产品的克制、层次和留白。不用装饰性动画掩盖真实等待,不将密钥或敏感请求数据作为视觉素材。 + +## 组件基础 + +按钮、表单、卡片、表格、标签、弹窗、步骤、进度和空状态统一使用项目现有的 `antd`。禁止页面自行引入第二套组件库或局部重写组件形状。两套主题只能替换颜色,不得改变组件尺寸、圆角和信息层级。 + +## 强制设计令牌 + +以下令牌定义在 `src/styles.css`,所有页面必须复用: + +| 令牌 | 值 | 用途 | +| --- | --- | --- | +| `--radius-control` | `8px` | Button、Input、Select、Tag 等控件 | +| `--radius-surface` | `16px` | Card、Modal、Alert、Table 等容器 | +| `--space-card` | `24px` | 卡片和内容面板内边距 | +| `--space-section` | `24px` | 同层内容区块之间的间距 | + +禁止在页面样式中新增一次性胶囊圆角(例如 `999px`)或为相同层级使用不同圆角。Switch、圆形进度和头像等由功能决定形状的组件除外。需要新尺寸时,先扩展全局令牌并补充一致性测试,再在页面使用。 + +颜色必须来自 `src/styles.css` 的主题变量或 `src/design/theme.ts` 的 Ant Design token。普通文字与背景对比度至少为 WCAG AA `4.5:1`;大号文字至少 `3:1`。状态不得只靠颜色表达,必须同时保留文字或图标。 + +## 页面布局规则 + +- Card 内容默认使用 `--space-card`,同级 Card 或内容区块使用 `--space-section`。 +- 页面不得通过负 margin 或相邻零间距制造视觉粘连。 +- 同一组标签尺寸、圆角和内边距必须一致;结论只通过语义色、字重和状态点强化。 +- 多列内容在 960px 以下收为单列;700px 以下保证导航、表单和统计区可重排。 +- 新页面优先复用现有 PageHeader、AsyncState、ReportSummary 和 Ant Design 组件。 + +## 两套可切换方案 + +| 方案 | 用途 | 视觉特征 | +| --- | --- | --- | +| `blue` | 默认安全控制台 | 使用 Ant Design 深色算法,深蓝布局、高对比信息、冷色状态强调,适合长时间监控 | +| `light` | 浅色专业工作台 | 浅灰布局、白色表面、Spark 紫色主操作,适合报告审阅 | + +登录页和应用顶栏均提供“切换前端 UI 方案”按钮。选择保存在 `localStorage`,刷新和重新登录后仍保留。 + +## 页面层级 + +- 登录:品牌与价值主张在左,唯一主任务登录在右;窄屏只保留登录任务。 +- 应用框架:侧边栏承载主导航与身份,顶栏显示活跃任务和主题切换,内容区只保留当前业务。 +- 数据概览:先显示可行动指标,再显示最近运行,不使用无决策价值的装饰图表。 +- 表单:按完成任务的顺序分组,就地校验,主提交操作保持唯一。 +- 运行详情:阶段、进度、计数和当前样例位于首屏,结果和报告位于下方标签页。 +- 结果卡片:顶部使用统一 Tag 明确标识“类型 / 模式 / 执行状态 / 仲裁结论”,将后端原始值转为中文语义。执行状态和仲裁结论使用两个独立筛选器,不混合 `completed/error` 与 `pass/fail`;执行 ID 搜索与两个筛选器组合生效,并支持忽略大小写的片段匹配。所有标签使用相同的 `8px` 圆角;结论只增加字重和状态点。通过为绿色、未通过或错误为红色、待复核为橙色,文字始终保留。 +- 结果卡片在 Run 完成或部分错误时提供“重新执行”;使用 Ant Design 确认框说明结果将被替换。提交、运行中状态以按钮文字和信息提示表达,不只依赖颜色;Run 非终态时同 Run 的全部单条重试按钮禁用。重试目标使用信息提示说明跟踪状态并置顶显示,不修改用户已有筛选;提示提供“停止跟踪”按钮。 + +## 响应式与可访问性 + +960px 以下将多列卡片收为单列,700px 以下将侧边栏改为页首导航。主题切换和状态不仅依赖颜色,破坏性操作使用文字和确认弹窗。系统尊重 `prefers-reduced-motion`,所有主要交互均使用语义按钮、表单标签和可见错误文案。 + +## 提交门禁 + +所有 UI 修改提交前必须运行 `pnpm check`。其中: + +- `tests/unit/design/consistency.test.ts` 防止圆角、间距和规范文档漂移。 +- `tests/unit/design/contrast.test.ts` 校验双主题标签与主按钮对比度。 +- `tests/integration/pages.test.tsx` 验证全部页面可挂载及关键路由行为。 diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..0a93604 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,19 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' + +export default tseslint.config( + { ignores: ['dist', 'coverage'] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ['**/*.{ts,tsx}'], + languageOptions: { ecmaVersion: 2022, globals: globals.browser }, + plugins: { 'react-hooks': reactHooks, 'react-refresh': reactRefresh }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }], + }, + }, +) diff --git a/index.html b/index.html new file mode 100644 index 0000000..93c15e6 --- /dev/null +++ b/index.html @@ -0,0 +1,13 @@ + + + + + + + AI 模型安全测试平台 + + +
+ + + diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..4e8846e --- /dev/null +++ b/nginx.conf @@ -0,0 +1,21 @@ +server { + listen 80; + server_name _; + root /usr/share/nginx/html; + + location = /healthz { + access_log off; + add_header Content-Type text/plain; + return 200 "ok\n"; + } + + location / { + try_files $uri $uri/ /index.html; + } + + location ~* \.(?:js|css|png|jpg|jpeg|gif|svg|ico|woff2?)$ { + expires 7d; + add_header Cache-Control "public, immutable"; + try_files $uri =404; + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..700eb83 --- /dev/null +++ b/package.json @@ -0,0 +1,51 @@ +{ + "name": "ai-safety-console-web", + "version": "1.0.0", + "private": true, + "type": "module", + "packageManager": "pnpm@9.15.9", + "engines": { + "node": ">=20 <25", + "pnpm": ">=9 <10" + }, + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "preview": "vite preview", + "lint": "eslint .", + "test": "vitest run", + "test:e2e": "vitest run tests/e2e --environment node", + "test:watch": "vitest", + "check": "eslint . && vitest run && tsc -b && vite build" + }, + "dependencies": { + "@tanstack/react-query": "5.90.20", + "antd": "5.27.6", + "classnames": "2.5.1", + "react": "18.3.1", + "react-dom": "18.3.1", + "react-router-dom": "7.9.6", + "zod": "4.1.12" + }, + "devDependencies": { + "@eslint/js": "9.39.1", + "@testing-library/jest-dom": "6.9.1", + "@testing-library/react": "16.3.0", + "@testing-library/user-event": "14.6.1", + "@types/node": "24.10.1", + "@types/react": "18.3.27", + "@types/react-dom": "18.3.7", + "@vitejs/plugin-react": "5.1.1", + "@vitest/coverage-v8": "4.0.14", + "eslint": "9.39.1", + "eslint-plugin-react-hooks": "7.0.1", + "eslint-plugin-react-refresh": "0.4.24", + "globals": "16.5.0", + "jsdom": "27.2.0", + "msw": "2.12.3", + "typescript": "5.9.3", + "typescript-eslint": "8.48.0", + "vite": "7.2.4", + "vitest": "4.0.14" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..9de4d61 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,6295 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@tanstack/react-query': + specifier: 5.90.20 + version: 5.90.20(react@18.3.1) + antd: + specifier: 5.27.6 + version: 5.27.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: + specifier: 2.5.1 + version: 2.5.1 + react: + specifier: 18.3.1 + version: 18.3.1 + react-dom: + specifier: 18.3.1 + version: 18.3.1(react@18.3.1) + react-router-dom: + specifier: 7.9.6 + version: 7.9.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + zod: + specifier: 4.1.12 + version: 4.1.12 + devDependencies: + '@eslint/js': + specifier: 9.39.1 + version: 9.39.1 + '@testing-library/jest-dom': + specifier: 6.9.1 + version: 6.9.1 + '@testing-library/react': + specifier: 16.3.0 + version: 16.3.0(@testing-library/dom@10.4.1)(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@testing-library/user-event': + specifier: 14.6.1 + version: 14.6.1(@testing-library/dom@10.4.1) + '@types/node': + specifier: 24.10.1 + version: 24.10.1 + '@types/react': + specifier: 18.3.27 + version: 18.3.27 + '@types/react-dom': + specifier: 18.3.7 + version: 18.3.7(@types/react@18.3.27) + '@vitejs/plugin-react': + specifier: 5.1.1 + version: 5.1.1(vite@7.2.4(@types/node@24.10.1)) + '@vitest/coverage-v8': + specifier: 4.0.14 + version: 4.0.14(vitest@4.0.14(@types/node@24.10.1)(jsdom@27.2.0)(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))) + eslint: + specifier: 9.39.1 + version: 9.39.1 + eslint-plugin-react-hooks: + specifier: 7.0.1 + version: 7.0.1(eslint@9.39.1) + eslint-plugin-react-refresh: + specifier: 0.4.24 + version: 0.4.24(eslint@9.39.1) + globals: + specifier: 16.5.0 + version: 16.5.0 + jsdom: + specifier: 27.2.0 + version: 27.2.0 + msw: + specifier: 2.12.3 + version: 2.12.3(@types/node@24.10.1)(typescript@5.9.3) + typescript: + specifier: 5.9.3 + version: 5.9.3 + typescript-eslint: + specifier: 8.48.0 + version: 8.48.0(eslint@9.39.1)(typescript@5.9.3) + vite: + specifier: 7.2.4 + version: 7.2.4(@types/node@24.10.1) + vitest: + specifier: 4.0.14 + version: 4.0.14(@types/node@24.10.1)(jsdom@27.2.0)(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3)) + +packages: + + '@acemir/cssom@0.9.31': + resolution: {integrity: sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==} + + '@adobe/css-tools@4.5.0': + resolution: {integrity: sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==} + + '@agentscope-ai/design@1.0.32': + resolution: {integrity: sha512-9h+G4NN/7PdgSsgZN959QgkCvORXy2fC4bRz89c2c8Zhs9//XxRlBXKnCFYiy4CjL7bw98jNF3x61Ayber9TWw==} + engines: {pnpm: '9'} + hasBin: true + peerDependencies: + '@rc-component/util': 1.2.1 + antd: ^5.18.0 + classnames: ^2.5.1 + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@agentscope-ai/icons-override-antd@6.0.0': + resolution: {integrity: sha512-TcHniQJWCLge4D9kyZ3TgkA8B5NPsdQ53rKhz5J22BwwkJiG1Pqp6L1MfWkxG0iEnYn9p7ExdyTQ2OQnm8/z4Q==} + engines: {node: '>=8'} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + '@agentscope-ai/icons-svg-override-antd@4.4.2': + resolution: {integrity: sha512-zCDu9GR2WEW7vXOu3ja9+cfWpgoNG89LzVYJTgTy5lOKQjgoBw4pEWA3gcKmJEpx1eCwRqLUtjAcPluMxQgzvA==} + + '@agentscope-ai/icons@1.0.68': + resolution: {integrity: sha512-2ZZl0YiqFnBkV/6ZyhniXhNy2lxD+srZzCauaK9xk3IJBFRCCIF4GkJay+arxnwQ+uq8Ipjo+UX9o7Grqq4OXA==} + + '@ant-design/colors@7.2.1': + resolution: {integrity: sha512-lCHDcEzieu4GA3n8ELeZ5VQ8pKQAWcGGLRTQ50aQM2iqPpq2evTxER84jfdPvsPAtEcZ7m44NI45edFMo8oOYQ==} + + '@ant-design/colors@8.0.1': + resolution: {integrity: sha512-foPVl0+SWIslGUtD/xBr1p9U4AKzPhNYEseXYRRo5QSzGACYZrQbe11AYJbYfAWnWSpGBx6JjBmSeugUsD9vqQ==} + + '@ant-design/cssinjs-utils@1.1.3': + resolution: {integrity: sha512-nOoQMLW1l+xR1Co8NFVYiP8pZp3VjIIzqV6D6ShYF2ljtdwWJn5WSsH+7kvCktXL/yhEtWURKOfH5Xz/gzlwsg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@ant-design/cssinjs@1.24.0': + resolution: {integrity: sha512-K4cYrJBsgvL+IoozUXYjbT6LHHNt+19a9zkvpBPxLjFHas1UpPM2A5MlhROb0BT8N8WoavM5VsP9MeSeNK/3mg==} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + '@ant-design/fast-color@2.0.6': + resolution: {integrity: sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA==} + engines: {node: '>=8.x'} + + '@ant-design/fast-color@3.0.1': + resolution: {integrity: sha512-esKJegpW4nckh0o6kV3Tkb7NPIZYbPnnFxmQDUmL08ukXZAvV85TZBr70eGuke/CIArLaP6aw8lt9KILjnWuOw==} + engines: {node: '>=8.x'} + + '@ant-design/icons-svg@4.5.0': + resolution: {integrity: sha512-1BTUFyKPTBZ53MuTP8s0k5SFEXL7o3VHEOwLgzaoWKwnBeqIcqUtVshc4SKzhI6uACfqhJqBwBUE9FsWR3uULA==} + + '@ant-design/icons@5.6.1': + resolution: {integrity: sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==} + engines: {node: '>=8'} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + '@ant-design/react-slick@1.1.2': + resolution: {integrity: sha512-EzlvzE6xQUBrZuuhSAFTdsr4P2bBBHGZwKFemEfq8gIGyIQCxalYfZW/T2ORbtQx5rU69o+WycP3exY/7T1hGA==} + peerDependencies: + react: '>=16.9.0' + + '@asamuzakjp/css-color@4.1.2': + resolution: {integrity: sha512-NfBUvBaYgKIuq6E/RBLY1m0IohzNHAYyaJGuTK79Z23uNwmz2jl1mPsC5ZxCCxylinKhT1Amn5oNTlx1wN8cQg==} + + '@asamuzakjp/dom-selector@6.8.1': + resolution: {integrity: sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ==} + + '@asamuzakjp/nwsapi@2.3.9': + resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.7': + resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.29.7': + resolution: {integrity: sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.7': + resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-transform-react-jsx-self@7.29.7': + resolution: {integrity: sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.29.7': + resolution: {integrity: sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.29.7': + resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.7': + resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.7': + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@1.0.2': + resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} + engines: {node: '>=18'} + + '@codemirror/autocomplete@6.20.3': + resolution: {integrity: sha512-tlosUqb+3BbxCxZdu4tKeRghPFC+QM7q4X5YhKV2eCmPG+1r2F3f4AaSz5sCrFqUtX4Jh20VFTKecl16MgiV9g==} + + '@codemirror/commands@6.10.4': + resolution: {integrity: sha512-Ryk9y9T0FFVF0cUGhAknveAyUOl/A1qReTFi+qPKtOh2Z9F4AUBz3XOrYD4ZEgZirdugVzHvd/2/Wcwy5OliTg==} + + '@codemirror/lang-cpp@6.0.3': + resolution: {integrity: sha512-URM26M3vunFFn9/sm6rzqrBzDgfWuDixp85uTY49wKudToc2jTHUrKIGGKs+QWND+YLofNNZpxcNGRynFJfvgA==} + + '@codemirror/lang-css@6.3.1': + resolution: {integrity: sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==} + + '@codemirror/lang-go@6.0.1': + resolution: {integrity: sha512-7fNvbyNylvqCphW9HD6WFnRpcDjr+KXX/FgqXy5H5ZS0eC5edDljukm/yNgYkwTsgp2busdod50AOTIy6Jikfg==} + + '@codemirror/lang-html@6.4.11': + resolution: {integrity: sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==} + + '@codemirror/lang-java@6.0.2': + resolution: {integrity: sha512-m5Nt1mQ/cznJY7tMfQTJchmrjdjQ71IDs+55d1GAa8DGaB8JXWsVCkVT284C3RTASaY43YknrK2X3hPO/J3MOQ==} + + '@codemirror/lang-javascript@6.2.5': + resolution: {integrity: sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A==} + + '@codemirror/lang-json@6.0.2': + resolution: {integrity: sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==} + + '@codemirror/lang-markdown@6.5.1': + resolution: {integrity: sha512-6re5avCNfyRMIoi3XNjbEfQM1vTeVD3JS3g/Fyegyso/eoANFM71Cyvbb66LDyYtQLMEcRFlzioywCqDo9SlLA==} + + '@codemirror/lang-php@6.0.2': + resolution: {integrity: sha512-ZKy2v1n8Fc8oEXj0Th0PUMXzQJ0AIR6TaZU+PbDHExFwdu+guzOA4jmCHS1Nz4vbFezwD7LyBdDnddSJeScMCA==} + + '@codemirror/lang-python@6.2.1': + resolution: {integrity: sha512-IRjC8RUBhn9mGR9ywecNhB51yePWCGgvHfY1lWN/Mrp3cKuHr0isDKia+9HnvhiWNnMpbGhWrkhuWOc09exRyw==} + + '@codemirror/lang-yaml@6.1.3': + resolution: {integrity: sha512-AZ8DJBuXGVHybpBQhmZtgew5//4hv3tdkXnr3vDmOUMJRuB6vn/uuwtmTOTlqEaQFg3hQSVeA90NmvIQyUV6FQ==} + + '@codemirror/language@6.12.4': + resolution: {integrity: sha512-1q4PaT+o6PbgpkJt4Q8Fv5XJxTy4FUZ4MWETtyiDw3J0Pyr9E2vqcKL+k9wcvjNTIsauxvE7OfmWj3FRPHQ76A==} + + '@codemirror/lint@6.9.7': + resolution: {integrity: sha512-28/+iWLYxKxsvGYhSYL7zaCZqLz5+FFFDq9tVsvGv9kv8RY4fFAchJ5WX9M3YrrRlTIsECjsXPqeNgnSmNP2dg==} + + '@codemirror/search@6.7.1': + resolution: {integrity: sha512-uMe5UO6PamJtSHrXhhHOzSX3ReWtiJrva6GnPMwSOrZtiExb5X5eExhr2OUZQVvdxPsKpY3Ro2mFbQadpPWmHA==} + + '@codemirror/state@6.7.1': + resolution: {integrity: sha512-9QzNDgE4EYDnAHfrTlR2lwiPciiOymLtwKK+8yHQzCc7GXhAP9xdEbEJFy2IWB1j9UGUl9BsgMmTo/ImA02T7A==} + + '@codemirror/theme-one-dark@6.1.3': + resolution: {integrity: sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==} + + '@codemirror/view@6.43.6': + resolution: {integrity: sha512-EVunGSYN1wz1p75WY1s3Xg7t3i8Yol0kGZGizNdX9BUFgMFILYVe8/u6EVpo7Ff5PwbZuILb4QAq7IZoKzIEQA==} + + '@csstools/color-helpers@6.1.0': + resolution: {integrity: sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg==} + engines: {node: '>=20.19.0'} + + '@csstools/css-calc@3.2.1': + resolution: {integrity: sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-color-parser@4.1.9': + resolution: {integrity: sha512-paQcIaOO53Rk5+YrBaBjm/SgrV4INImjo2BT1DtQRYr+XeTRbeAYlS+jxXp9drqvKmtFnWRJKIalDLhZZDu42A==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-parser-algorithms@4.0.0': + resolution: {integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.6': + resolution: {integrity: sha512-TcJCWFbXLPpJYq6z7bfOyjWYJDiDg2/I4gyUC9pqPNqHFRIey0EB0q0L5cSnQDfWJg8Jd6VadakxdIez/3zkqQ==} + peerDependencies: + css-tree: ^3.2.1 + peerDependenciesMeta: + css-tree: + optional: true + + '@csstools/css-tokenizer@4.0.0': + resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} + engines: {node: '>=20.19.0'} + + '@emotion/babel-plugin@11.13.5': + resolution: {integrity: sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==} + + '@emotion/cache@11.14.0': + resolution: {integrity: sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==} + + '@emotion/css@11.13.5': + resolution: {integrity: sha512-wQdD0Xhkn3Qy2VNcIzbLP9MR8TafI0MJb7BEAXKp+w4+XqErksWR4OXomuDzPsN4InLdGhVe6EYcn2ZIUCpB8w==} + + '@emotion/hash@0.8.0': + resolution: {integrity: sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==} + + '@emotion/hash@0.9.2': + resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==} + + '@emotion/memoize@0.9.0': + resolution: {integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==} + + '@emotion/react@11.14.0': + resolution: {integrity: sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==} + peerDependencies: + '@types/react': '*' + react: '>=16.8.0' + peerDependenciesMeta: + '@types/react': + optional: true + + '@emotion/serialize@1.3.3': + resolution: {integrity: sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==} + + '@emotion/sheet@1.4.0': + resolution: {integrity: sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==} + + '@emotion/unitless@0.10.0': + resolution: {integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==} + + '@emotion/unitless@0.7.5': + resolution: {integrity: sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==} + + '@emotion/use-insertion-effect-with-fallbacks@1.2.0': + resolution: {integrity: sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==} + peerDependencies: + react: '>=16.8.0' + + '@emotion/utils@1.4.2': + resolution: {integrity: sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==} + + '@emotion/weak-memoize@0.4.0': + resolution: {integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==} + + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.2': + resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.6': + resolution: {integrity: sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.39.1': + resolution: {integrity: sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@halofe/global-locale@1.0.3': + resolution: {integrity: sha512-3pH462KcEwA6/hBz3Tl38piu7HURYX/mL+z036DJXuLnnHnKKEvMAlrdAfbSGBI7larUp/C0CmbQIJ60WhcFrQ==} + + '@halofe/global-string-format@1.0.8': + resolution: {integrity: sha512-Ge26bMtQaNM6LMROm1NYgA3YTDfkhnewd2Qd4R1x9SZcBrHDLqtVcA845+zM6/SS3GMHZh4iD7+0n2/w6iLOJw==} + + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} + + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@inquirer/ansi@1.0.2': + resolution: {integrity: sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==} + engines: {node: '>=18'} + + '@inquirer/confirm@5.1.21': + resolution: {integrity: sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/core@10.3.2': + resolution: {integrity: sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/figures@1.0.15': + resolution: {integrity: sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==} + engines: {node: '>=18'} + + '@inquirer/type@3.0.10': + resolution: {integrity: sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@juggle/resize-observer@3.4.0': + resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==} + + '@lezer/common@1.5.2': + resolution: {integrity: sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ==} + + '@lezer/cpp@1.1.6': + resolution: {integrity: sha512-vh9gWWJOXFVY8HBHK3Twzq8MgwG2iN4GSyzBP9sCGTe37P15x2R14VaBQk0VA0ezTRN1KHYBBsHhvpGZ2Xy/pA==} + + '@lezer/css@1.3.4': + resolution: {integrity: sha512-N+tn9tej2hPvyKgHEApMOQfHczDJCwxrRFS3SPn9QjYN+uwHvEDnCgKRrb3mxDYxRS8sKMM8fhC3+lc04Abz5Q==} + + '@lezer/go@1.0.1': + resolution: {integrity: sha512-xToRsYxwsgJNHTgNdStpcvmbVuKxTapV0dM0wey1geMMRc9aggoVyKgzYp41D2/vVOx+Ii4hmE206kvxIXBVXQ==} + + '@lezer/highlight@1.2.3': + resolution: {integrity: sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==} + + '@lezer/html@1.3.13': + resolution: {integrity: sha512-oI7n6NJml729m7pjm9lvLvmXbdoMoi2f+1pwSDJkl9d68zGr7a9Btz8NdHTGQZtW2DA25ybeuv/SyDb9D5tseg==} + + '@lezer/java@1.1.3': + resolution: {integrity: sha512-yHquUfujwg6Yu4Fd1GNHCvidIvJwi/1Xu2DaKl/pfWIA2c1oXkVvawH3NyXhCaFx4OdlYBVX5wvz2f7Aoa/4Xw==} + + '@lezer/javascript@1.5.4': + resolution: {integrity: sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==} + + '@lezer/json@1.0.3': + resolution: {integrity: sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==} + + '@lezer/lr@1.4.10': + resolution: {integrity: sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A==} + + '@lezer/markdown@1.7.2': + resolution: {integrity: sha512-iTkYvoVcKt3WkeL7qUDyXHONZEwLio4wj8KTNi2dnjQEXBZKMV63BpQrPqfsM+OkvuRbiSTAcycYAsQzLhRNoQ==} + + '@lezer/php@1.0.5': + resolution: {integrity: sha512-W7asp9DhM6q0W6DYNwIkLSKOvxlXRrif+UXBMxzsJUuqmhE7oVU+gS3THO4S/Puh7Xzgm858UNaFi6dxTP8dJA==} + + '@lezer/python@1.1.19': + resolution: {integrity: sha512-MhQIURHRytsNzP/YXnqpYKW6la6voAH3kyplTOOiCdjyFY6cWWGFVmYVdHIPrElqSDf4iCDktQCockB9FxuhzQ==} + + '@lezer/yaml@1.0.4': + resolution: {integrity: sha512-2lrrHqxalACEbxIbsjhqGpSW8kWpUKuY6RHgnSAFZa6qK62wvnPxA8hGOwOoDbwHcOFs5M4o27mjGu+P7TvBmw==} + + '@marijn/find-cluster-break@1.0.3': + resolution: {integrity: sha512-FY+MKLBoTsLNJF/eLWaOsXGdz6uh3Iu1axjPf6TUq92IYumcTcXWHoS747JARLkcdlJ/Waiaxc5wQfFO8jC6NA==} + + '@mswjs/interceptors@0.40.0': + resolution: {integrity: sha512-EFd6cVbHsgLa6wa4RljGj6Wk75qoHxUSyc5asLyyPSyuhIcdS2Q3Phw6ImS1q+CkALthJRShiYfKANcQMuMqsQ==} + engines: {node: '>=18'} + + '@open-draft/deferred-promise@2.2.0': + resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} + + '@open-draft/logger@0.3.0': + resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==} + + '@open-draft/until@2.1.0': + resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} + + '@rc-component/async-validator@5.1.2': + resolution: {integrity: sha512-WYbrZSjzznU1ekD0qFq2qRxt309VoS61MTG5npnFQlKYcoy9IzU8T+ZCIhq5bGAXRbXysABFWTspicMfmWFwow==} + engines: {node: '>=14.x'} + + '@rc-component/color-picker@2.0.1': + resolution: {integrity: sha512-WcZYwAThV/b2GISQ8F+7650r5ZZJ043E57aVBFkQ+kSY4C6wdofXgB0hBx+GPGpIU0Z81eETNoDUJMr7oy/P8Q==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/context@1.4.0': + resolution: {integrity: sha512-kFcNxg9oLRMoL3qki0OMxK+7g5mypjgaaJp/pkOis/6rVxma9nJBF/8kCIuTYHUQNr0ii7MxqE33wirPZLJQ2w==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/mini-decimal@1.1.4': + resolution: {integrity: sha512-xiuXcaCwyOWpD8a8scdExFl+bntNphAW8XeenL1ig2en0AAZY0Pcp4pC0dI22qJ+NvxKn9RoNIoRdqYU3BLH4w==} + engines: {node: '>=8.x'} + + '@rc-component/mutate-observer@1.1.0': + resolution: {integrity: sha512-QjrOsDXQusNwGZPf4/qRQasg7UFEj06XiCJ8iuiq/Io7CrHrgVi6Uuetw60WAMG1799v+aM8kyc+1L/GBbHSlw==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/portal@1.1.2': + resolution: {integrity: sha512-6f813C0IsasTZms08kfA8kPAGxbbkYToa8ALaiDIGGECU4i9hj8Plgbx0sNJDrey3EtHO30hmdaxtT0138xZcg==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/qrcode@1.0.1': + resolution: {integrity: sha512-g8eeeaMyFXVlq8cZUeaxCDhfIYjpao0l9cvm5gFwKXy/Vm1yDWV7h2sjH5jHYzdFedlVKBpATFB1VKMrHzwaWQ==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/tour@1.15.1': + resolution: {integrity: sha512-Tr2t7J1DKZUpfJuDZWHxyxWpfmj8EZrqSgyMZ+BCdvKZ6r1UDsfU46M/iWAAFBy961Ssfom2kv5f3UcjIL2CmQ==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/trigger@2.3.1': + resolution: {integrity: sha512-ORENF39PeXTzM+gQEshuk460Z8N4+6DkjpxlpE7Q3gYy1iBpLrx0FOJz3h62ryrJZ/3zCAUIkT1Pb/8hHWpb3A==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/util@1.2.1': + resolution: {integrity: sha512-AUVu6jO+lWjQnUOOECwu8iR0EdElQgWW5NBv5vP/Uf9dWbAX3udhMutRlkVXjuac2E40ghkFy+ve00mc/3Fymg==} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@rolldown/pluginutils@1.0.0-beta.47': + resolution: {integrity: sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw==} + + '@rollup/rollup-android-arm-eabi@4.62.2': + resolution: {integrity: sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.62.2': + resolution: {integrity: sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.62.2': + resolution: {integrity: sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.62.2': + resolution: {integrity: sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.62.2': + resolution: {integrity: sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.62.2': + resolution: {integrity: sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.62.2': + resolution: {integrity: sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.62.2': + resolution: {integrity: sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.62.2': + resolution: {integrity: sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.62.2': + resolution: {integrity: sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.62.2': + resolution: {integrity: sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-loong64-musl@4.62.2': + resolution: {integrity: sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.62.2': + resolution: {integrity: sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-ppc64-musl@4.62.2': + resolution: {integrity: sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.62.2': + resolution: {integrity: sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.62.2': + resolution: {integrity: sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.62.2': + resolution: {integrity: sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.62.2': + resolution: {integrity: sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.62.2': + resolution: {integrity: sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openbsd-x64@4.62.2': + resolution: {integrity: sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.62.2': + resolution: {integrity: sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.62.2': + resolution: {integrity: sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.62.2': + resolution: {integrity: sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.62.2': + resolution: {integrity: sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.62.2': + resolution: {integrity: sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==} + cpu: [x64] + os: [win32] + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@tanstack/query-core@5.90.20': + resolution: {integrity: sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg==} + + '@tanstack/react-query@5.90.20': + resolution: {integrity: sha512-vXBxa+qeyveVO7OA0jX1z+DeyCA4JKnThKv411jd5SORpBKgkcVnYKCiBgECvADvniBX7tobwBmg01qq9JmMJw==} + peerDependencies: + react: ^18 || ^19 + + '@testing-library/dom@10.4.1': + resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} + engines: {node: '>=18'} + + '@testing-library/jest-dom@6.9.1': + resolution: {integrity: sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==} + engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + + '@testing-library/react@16.3.0': + resolution: {integrity: sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==} + engines: {node: '>=18'} + peerDependencies: + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 || ^19.0.0 + '@types/react-dom': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@testing-library/user-event@14.6.1': + resolution: {integrity: sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==} + engines: {node: '>=12', npm: '>=6'} + peerDependencies: + '@testing-library/dom': '>=7.21.4' + + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/is-hotkey@0.1.10': + resolution: {integrity: sha512-RvC8KMw5BCac1NvRRyaHgMMEtBaZ6wh0pyPTBu7izn4Sj/AX9Y4aXU5c7rX8PnM/knsuUpC1IeoBkANtxBypsQ==} + + '@types/js-cookie@3.0.6': + resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/lodash@4.17.24': + resolution: {integrity: sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==} + + '@types/node@24.10.1': + resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==} + + '@types/parse-json@4.0.2': + resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} + + '@types/prop-types@15.7.15': + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + + '@types/react-dom@18.3.7': + resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==} + peerDependencies: + '@types/react': ^18.0.0 + + '@types/react@18.3.27': + resolution: {integrity: sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==} + + '@types/statuses@2.0.6': + resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==} + + '@typescript-eslint/eslint-plugin@8.48.0': + resolution: {integrity: sha512-XxXP5tL1txl13YFtrECECQYeZjBZad4fyd3cFV4a19LkAY/bIp9fev3US4S5fDVV2JaYFiKAZ/GRTOLer+mbyQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.48.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/parser@8.48.0': + resolution: {integrity: sha512-jCzKdm/QK0Kg4V4IK/oMlRZlY+QOcdjv89U2NgKHZk1CYTj82/RVSx1mV/0gqCVMJ/DA+Zf/S4NBWNF8GQ+eqQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/project-service@8.48.0': + resolution: {integrity: sha512-Ne4CTZyRh1BecBf84siv42wv5vQvVmgtk8AuiEffKTUo3DrBaGYZueJSxxBZ8fjk/N3DrgChH4TOdIOwOwiqqw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/scope-manager@8.48.0': + resolution: {integrity: sha512-uGSSsbrtJrLduti0Q1Q9+BF1/iFKaxGoQwjWOIVNJv0o6omrdyR8ct37m4xIl5Zzpkp69Kkmvom7QFTtue89YQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.48.0': + resolution: {integrity: sha512-WNebjBdFdyu10sR1M4OXTt2OkMd5KWIL+LLfeH9KhgP+jzfDV/LI3eXzwJ1s9+Yc0Kzo2fQCdY/OpdusCMmh6w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/type-utils@8.48.0': + resolution: {integrity: sha512-zbeVaVqeXhhab6QNEKfK96Xyc7UQuoFWERhEnj3mLVnUWrQnv15cJNseUni7f3g557gm0e46LZ6IJ4NJVOgOpw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/types@8.48.0': + resolution: {integrity: sha512-cQMcGQQH7kwKoVswD1xdOytxQR60MWKM1di26xSUtxehaDs/32Zpqsu5WJlXTtTTqyAVK8R7hvsUnIXRS+bjvA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.48.0': + resolution: {integrity: sha512-ljHab1CSO4rGrQIAyizUS6UGHHCiAYhbfcIZ1zVJr5nMryxlXMVWS3duFPSKvSUbFPwkXMFk1k0EMIjub4sRRQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/utils@8.48.0': + resolution: {integrity: sha512-yTJO1XuGxCsSfIVt1+1UrLHtue8xz16V8apzPYI06W0HbEbEWHxHXgZaAgavIkoh+GeV6hKKd5jm0sS6OYxWXQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/visitor-keys@8.48.0': + resolution: {integrity: sha512-T0XJMaRPOH3+LBbAfzR2jalckP1MSG/L9eUtY0DEzUyVaXJ/t6zN0nR7co5kz0Jko/nkSYCBRkz1djvjajVTTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@uiw/codemirror-extensions-basic-setup@4.20.2': + resolution: {integrity: sha512-8oF7ICSEoJVjn9MNKLsY5BaxGsFS/Qh8AMHa/0hZP1fExeI+LKhaaSfHbdRk8RpYE5Ffjtx+tBQfh22YBiv5dQ==} + peerDependencies: + '@codemirror/autocomplete': '>=6.0.0' + '@codemirror/commands': '>=6.0.0' + '@codemirror/language': '>=6.0.0' + '@codemirror/lint': '>=6.0.0' + '@codemirror/search': '>=6.0.0' + '@codemirror/state': '>=6.0.0' + '@codemirror/view': '>=6.0.0' + + '@uiw/codemirror-extensions-mentions@4.25.11': + resolution: {integrity: sha512-SsmIewX8aUxaV+TqxGUDzGU6lshPBWlTdEHmAVCWOBh0YYMLhKala6VCDsqIkMPZIfAERQ69A7CWHQf/rACrxw==} + peerDependencies: + '@codemirror/state': '>=6.0.0' + '@codemirror/view': '>=6.0.0' + + '@uiw/codemirror-theme-vscode@4.25.11': + resolution: {integrity: sha512-fTQ5676L9iV3Y6UyDBDd8Wt9Gewh55aC5rqnCtIpwyVFb6zJEHJScI4g43lzAD2IKhY7HlozB1ucrS3UqBFNEQ==} + + '@uiw/codemirror-themes@4.25.11': + resolution: {integrity: sha512-SBNCOgRsCtewGNocRbmjbCkltGXlFcPJsvhxQ351VynQjnWUiPbUrFcEU/haQ3HanROdAAjWXZJPk5bMBxl2jw==} + peerDependencies: + '@codemirror/language': '>=6.0.0' + '@codemirror/state': '>=6.0.0' + '@codemirror/view': '>=6.0.0' + + '@uiw/react-codemirror@4.20.2': + resolution: {integrity: sha512-Rf6i9HgtNnYAVRBb1gfWlhiiOBrEPuIfHLn87cV9XPpjP+YtqZoP6VMMlMq4XFTbZ/E1GIxnSUgJMQToTth0iw==} + peerDependencies: + '@babel/runtime': '>=7.11.0' + '@codemirror/state': '>=6.0.0' + '@codemirror/theme-one-dark': '>=6.0.0' + '@codemirror/view': '>=6.0.0' + codemirror: '>=6.0.0' + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@vitejs/plugin-react@5.1.1': + resolution: {integrity: sha512-WQfkSw0QbQ5aJ2CHYw23ZGkqnRwqKHD/KYsMeTkZzPT4Jcf0DcBxBtwMJxnu6E7oxw5+JC6ZAiePgh28uJ1HBA==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + + '@vitest/coverage-v8@4.0.14': + resolution: {integrity: sha512-EYHLqN/BY6b47qHH7gtMxAg++saoGmsjWmAq9MlXxAz4M0NcHh9iOyKhBZyU4yxZqOd8Xnqp80/5saeitz4Cng==} + peerDependencies: + '@vitest/browser': 4.0.14 + vitest: 4.0.14 + peerDependenciesMeta: + '@vitest/browser': + optional: true + + '@vitest/expect@4.0.14': + resolution: {integrity: sha512-RHk63V3zvRiYOWAV0rGEBRO820ce17hz7cI2kDmEdfQsBjT2luEKB5tCOc91u1oSQoUOZkSv3ZyzkdkSLD7lKw==} + + '@vitest/mocker@4.0.14': + resolution: {integrity: sha512-RzS5NujlCzeRPF1MK7MXLiEFpkIXeMdQ+rN3Kk3tDI9j0mtbr7Nmuq67tpkOJQpgyClbOltCXMjLZicJHsH5Cg==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.0.14': + resolution: {integrity: sha512-SOYPgujB6TITcJxgd3wmsLl+wZv+fy3av2PpiPpsWPZ6J1ySUYfScfpIt2Yv56ShJXR2MOA6q2KjKHN4EpdyRQ==} + + '@vitest/runner@4.0.14': + resolution: {integrity: sha512-BsAIk3FAqxICqREbX8SetIteT8PiaUL/tgJjmhxJhCsigmzzH8xeadtp7LRnTpCVzvf0ib9BgAfKJHuhNllKLw==} + + '@vitest/snapshot@4.0.14': + resolution: {integrity: sha512-aQVBfT1PMzDSA16Y3Fp45a0q8nKexx6N5Amw3MX55BeTeZpoC08fGqEZqVmPcqN0ueZsuUQ9rriPMhZ3Mu19Ag==} + + '@vitest/spy@4.0.14': + resolution: {integrity: sha512-JmAZT1UtZooO0tpY3GRyiC/8W7dCs05UOq9rfsUUgEZEdq+DuHLmWhPsrTt0TiW7WYeL/hXpaE07AZ2RCk44hg==} + + '@vitest/utils@4.0.14': + resolution: {integrity: sha512-hLqXZKAWNg8pI+SQXyXxWCTOpA3MvsqcbVeNgSi8x/CSN2wi26dSzn1wrOhmCmFjEvN9p8/kLFRHa6PI8jHazw==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.17.0: + resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==} + engines: {node: '>=0.4.0'} + hasBin: true + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + ahooks@3.9.7: + resolution: {integrity: sha512-S0lvzhbdlhK36RFBkGv+RbOM/dbbweym+BIHM/bwwuWVSVN5TuVErHPMWo4w0t1NDYg5KPp2iEf7Y7E5LASYiw==} + 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 + + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + antd-style@3.7.1: + resolution: {integrity: sha512-CQOfddVp4aOvBfCepa+Kj2e7ap+2XBINg1Kn2osdE3oQvrD7KJu/K0sfnLcFLkgCJygbxmuazYdWLKb+drPDYA==} + peerDependencies: + antd: '>=5.8.1' + react: '>=18' + + antd@5.27.6: + resolution: {integrity: sha512-70HrjVbzDXvtiUQ5MP1XdNudr/wGAk9Ivaemk6f36yrAeJurJSmZ8KngOIilolLRHdGuNc6/Vk+4T1OZpSjpag==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + ast-v8-to-istanbul@0.3.12: + resolution: {integrity: sha512-BRRC8VRZY2R4Z4lFIL35MwNXmwVqBityvOIwETtsCSwvjl0IdgFsy9NhdaA6j74nUdtJJlIypeRhpDam19Wq3g==} + + async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + babel-plugin-macros@3.1.0: + resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} + engines: {node: '>=10', npm: '>=6'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + baseline-browser-mapping@2.10.43: + resolution: {integrity: sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + bidi-js@1.0.3: + resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} + + brace-expansion@1.1.16: + resolution: {integrity: sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==} + + brace-expansion@2.1.2: + resolution: {integrity: sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==} + + browserslist@4.28.6: + resolution: {integrity: sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.9: + resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + caniuse-lite@1.0.30001806: + resolution: {integrity: sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==} + + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + classnames@2.5.1: + resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} + + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + codemirror@6.0.2: + resolution: {integrity: sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + compute-scroll-into-view@1.0.20: + resolution: {integrity: sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==} + + compute-scroll-into-view@3.1.1: + resolution: {integrity: sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + convert-source-map@1.9.0: + resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} + + copy-to-clipboard@3.3.3: + resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==} + + cosmiconfig@7.1.0: + resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} + engines: {node: '>=10'} + + crelt@1.0.7: + resolution: {integrity: sha512-aK6BbWfhf4U/wCcLHKPJl/xa6VkVstRaPywWtMKGwuOLc/wZTyQYuoxgvZnNsBvv7Kg3YTBQYYBCggcviQczuA==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + css-tree@3.2.1: + resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + css.escape@1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + + cssstyle@5.3.7: + resolution: {integrity: sha512-7D2EPVltRrsTkhpQmksIu+LxeWAIEk6wRDMJ1qljlv+CKHJM+cJLlfhWIzNA44eAsHXSNe3+vO6DW1yCYx8SuQ==} + engines: {node: '>=20'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + data-urls@6.0.1: + resolution: {integrity: sha512-euIQENZg6x8mj3fO6o9+fOW8MimUI4PpD/fZBhJfeioZVy9TUpM4UY7KjQNVZFlqwJ0UdzRDzkycB997HEq1BQ==} + engines: {node: '>=20'} + + data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} + + dayjs@1.11.21: + resolution: {integrity: sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + + decode-uri-component@0.2.2: + resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} + engines: {node: '>=0.10'} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + direction@1.0.4: + resolution: {integrity: sha512-GYqKi1aH7PJXxdhTeZBFrg8vUBeKXi+cNprXsC1kpJcbcVnV9wBsrOu1cQEdG0WeQwlfHiy3XvnKfIrJ2R0NzQ==} + hasBin: true + + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + + dom-accessibility-api@0.6.3: + resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + + dompurify@2.5.9: + resolution: {integrity: sha512-i6mvVmWN4xo9LrhCOZrDgSs9noW6nOahbrmzjRbPF36YPyj5Ue5lgok0MHDWkG7xzpWFO2OYttXdzM7rJxHvNA==} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + electron-to-chromium@1.5.392: + resolution: {integrity: sha512-1yQq3VQCZRwsnYc67Oc+1fge6Lwtn0hzi6zmEVkB61Zx21kTbwJAW4dFLadl5Rc1tKhG/kSpYXnfiAhu0f0a1g==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + entities@8.0.0: + resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==} + engines: {node: '>=20.19.0'} + + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + + es-abstract-get@1.0.0: + resolution: {integrity: sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==} + engines: {node: '>= 0.4'} + + es-abstract@1.24.2: + resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + es-to-primitive@1.3.4: + resolution: {integrity: sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==} + engines: {node: '>= 0.4'} + + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-plugin-react-hooks@7.0.1: + resolution: {integrity: sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==} + engines: {node: '>=18'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + + eslint-plugin-react-refresh@0.4.24: + resolution: {integrity: sha512-nLHIW7TEq3aLrEYWpVaJ1dRgFR+wLDPN8e8FpYAql/bMV2oBEfC37K0gLEGgv9fy66juNShSMV8OkTqzltcG/w==} + peerDependencies: + eslint: '>=8.40' + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.39.1: + resolution: {integrity: sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} + engines: {node: '>=12.0.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + filter-obj@1.1.0: + resolution: {integrity: sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==} + engines: {node: '>=0.10.0'} + + find-root@1.1.0: + resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + function.prototype.name@1.2.0: + resolution: {integrity: sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + generator-function@2.0.1: + resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} + engines: {node: '>= 0.4'} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@16.5.0: + resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} + engines: {node: '>=18'} + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + graphql@16.14.2: + resolution: {integrity: sha512-Chq1s4CY7jmh8gO2qvLIJyfCDIN+EHLFW/9iShnp1z8FjBQMoodWP1kDC36VAMXXIvAjj4ARa7ntfAV2BrjsbA==} + engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + + headers-polyfill@4.0.3: + resolution: {integrity: sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==} + + hermes-estree@0.25.1: + resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} + + hermes-parser@0.25.1: + resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + + hoist-non-react-statics@3.3.2: + resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + + html-encoding-sniffer@4.0.0: + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} + engines: {node: '>=18'} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.6: + resolution: {integrity: sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==} + engines: {node: '>= 4'} + + immer@9.0.21: + resolution: {integrity: sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} + + intersection-observer@0.12.2: + resolution: {integrity: sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==} + deprecated: The Intersection Observer polyfill is no longer needed and can safely be removed. Intersection Observer has been Baseline since 2019. + + intl-messageformat-parser@1.4.0: + resolution: {integrity: sha512-/XkqFHKezO6UcF4Av2/Lzfrez18R0jyw7kRFhSeB/YRakdrgSc9QfFZUwNJI9swMwMoNPygK1ArC5wdFSjPw+A==} + deprecated: We've written a new parser that's 6x faster and is backwards compatible. Please use @formatjs/icu-messageformat-parser + + intl-messageformat@2.2.0: + resolution: {integrity: sha512-I+tSvHnXqJYjDfNmY95tpFMj30yoakC6OXAo+wu/wTMy6tA/4Fd4mvV7Uzs4cqK/Ap29sHhwjcY+78a8eifcXw==} + + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} + + is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} + + is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.16.2: + resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} + engines: {node: '>= 0.4'} + + is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} + + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} + + is-document.all@1.0.0: + resolution: {integrity: sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-generator-function@1.1.2: + resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} + engines: {node: '>= 0.4'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-hotkey@0.1.8: + resolution: {integrity: sha512-qs3NZ1INIS+H+yeo7cD9pDfwYV/jqRh1JG9S9zYrNudkoUQg7OL7ziXqRKu+InFjUIDoP2o6HIkLYMh1pcWgyQ==} + + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + + is-node-process@1.2.0: + resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} + + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} + + is-plain-object@5.0.0: + resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} + engines: {node: '>=0.10.0'} + + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} + + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} + + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} + + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@5.0.6: + resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + + js-cookie@3.0.8: + resolution: {integrity: sha512-yeJd4aNAdYZQjaon2bpD/Gb0B/omw7HQOsynXXcOiWVCacbBcPlgn8S/d1X6blFSaHao7ozqtW7NZW19xpCtIw==} + + js-tokens@10.0.0: + resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.3.0: + resolution: {integrity: sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==} + hasBin: true + + jsdom@27.2.0: + resolution: {integrity: sha512-454TI39PeRDW1LgpyLPyURtB4Zx1tklSr6+OFOipsxGUH1WMTvk6C65JQdrj455+DP2uJ1+veBEHTGFKWVLFoA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json2mq@0.2.0: + resolution: {integrity: sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash-es@4.18.1: + resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lru-cache@11.5.2: + resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} + engines: {node: 20 || >=22} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + magicast@0.5.3: + resolution: {integrity: sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mdn-data@2.27.1: + resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + + minimatch@9.0.9: + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} + engines: {node: '>=16 || 14 >=14.17'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + msw@2.12.3: + resolution: {integrity: sha512-/5rpGC0eK8LlFqsHaBmL19/PVKxu/CCt8pO1vzp9X6SDLsRDh/Ccudkf3Ur5lyaKxJz9ndAx+LaThdv0ySqB6A==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + typescript: '>= 4.8.x' + peerDependenciesMeta: + typescript: + optional: true + + mute-stream@2.0.0: + resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} + engines: {node: ^18.17.0 || >=20.5.0} + + nanoid@3.3.16: + resolution: {integrity: sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + node-releases@2.0.51: + resolution: {integrity: sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==} + engines: {node: '>=18'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} + + obug@2.1.4: + resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==} + engines: {node: '>=12.20.0'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + outvariant@1.4.3: + resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} + + own-keys@1.0.1: + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + parse5@8.0.1: + resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-to-regexp@6.3.0: + resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.5: + resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} + engines: {node: '>=12'} + + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + + postcss@8.5.19: + resolution: {integrity: sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + query-string@7.1.3: + resolution: {integrity: sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==} + engines: {node: '>=6'} + + rc-cascader@3.34.0: + resolution: {integrity: sha512-KpXypcvju9ptjW9FaN2NFcA2QH9E9LHKq169Y0eWtH4e/wHQ5Wh5qZakAgvb8EKZ736WZ3B0zLLOBsrsja5Dag==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-checkbox@3.5.0: + resolution: {integrity: sha512-aOAQc3E98HteIIsSqm6Xk2FPKIER6+5vyEFMZfo73TqM+VVAIqOkHoPjgKLqSNtVLWScoaM7vY2ZrGEheI79yg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-collapse@3.9.0: + resolution: {integrity: sha512-swDdz4QZ4dFTo4RAUMLL50qP0EY62N2kvmk2We5xYdRwcRn8WcYtuetCJpwpaCbUfUt5+huLpVxhvmnK+PHrkA==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-dialog@9.6.0: + resolution: {integrity: sha512-ApoVi9Z8PaCQg6FsUzS8yvBEQy0ZL2PkuvAgrmohPkN3okps5WZ5WQWPc1RNuiOKaAYv8B97ACdsFU5LizzCqg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-drawer@7.3.0: + resolution: {integrity: sha512-DX6CIgiBWNpJIMGFO8BAISFkxiuKitoizooj4BDyee8/SnBn0zwO2FHrNDpqqepj0E/TFTDpmEBCyFuTgC7MOg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-dropdown@4.2.1: + resolution: {integrity: sha512-YDAlXsPv3I1n42dv1JpdM7wJ+gSUBfeyPK59ZpBD9jQhK9jVuxpjj3NmWQHOBceA1zEPVX84T2wbdb2SD0UjmA==} + peerDependencies: + react: '>=16.11.0' + react-dom: '>=16.11.0' + + rc-field-form@2.7.1: + resolution: {integrity: sha512-vKeSifSJ6HoLaAB+B8aq/Qgm8a3dyxROzCtKNCsBQgiverpc4kWDQihoUwzUj+zNWJOykwSY4dNX3QrGwtVb9A==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-image@7.12.0: + resolution: {integrity: sha512-cZ3HTyyckPnNnUb9/DRqduqzLfrQRyi+CdHjdqgsyDpI3Ln5UX1kXnAhPBSJj9pVRzwRFgqkN7p9b6HBDjmu/Q==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-input-number@9.5.0: + resolution: {integrity: sha512-bKaEvB5tHebUURAEXw35LDcnRZLq3x1k7GxfAqBMzmpHkDGzjAtnUL8y4y5N15rIFIg5IJgwr211jInl3cipag==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-input@1.8.0: + resolution: {integrity: sha512-KXvaTbX+7ha8a/k+eg6SYRVERK0NddX8QX7a7AnRvUa/rEH0CNMlpcBzBkhI0wp2C8C4HlMoYl8TImSN+fuHKA==} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + rc-mentions@2.20.0: + resolution: {integrity: sha512-w8HCMZEh3f0nR8ZEd466ATqmXFCMGMN5UFCzEUL0bM/nGw/wOS2GgRzKBcm19K++jDyuWCOJOdgcKGXU3fXfbQ==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-menu@9.16.1: + resolution: {integrity: sha512-ghHx6/6Dvp+fw8CJhDUHFHDJ84hJE3BXNCzSgLdmNiFErWSOaZNsihDAsKq9ByTALo/xkNIwtDFGIl6r+RPXBg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-motion@2.9.5: + resolution: {integrity: sha512-w+XTUrfh7ArbYEd2582uDrEhmBHwK1ZENJiSJVb7uRxdE7qJSYjbO2eksRXmndqyKqKoYPc9ClpPh5242mV1vA==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-notification@5.6.4: + resolution: {integrity: sha512-KcS4O6B4qzM3KH7lkwOB7ooLPZ4b6J+VMmQgT51VZCeEcmghdeR4IrMcFq0LG+RPdnbe/ArT086tGM8Snimgiw==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-overflow@1.5.0: + resolution: {integrity: sha512-Lm/v9h0LymeUYJf0x39OveU52InkdRXqnn2aYXfWmo8WdOonIKB2kfau+GF0fWq6jPgtdO9yMqveGcK6aIhJmg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-pagination@5.1.0: + resolution: {integrity: sha512-8416Yip/+eclTFdHXLKTxZvn70duYVGTvUUWbckCCZoIl3jagqke3GLsFrMs0bsQBikiYpZLD9206Ej4SOdOXQ==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-picker@4.11.3: + resolution: {integrity: sha512-MJ5teb7FlNE0NFHTncxXQ62Y5lytq6sh5nUw0iH8OkHL/TjARSEvSHpr940pWgjGANpjCwyMdvsEV55l5tYNSg==} + engines: {node: '>=8.x'} + peerDependencies: + date-fns: '>= 2.x' + dayjs: '>= 1.x' + luxon: '>= 3.x' + moment: '>= 2.x' + react: '>=16.9.0' + react-dom: '>=16.9.0' + peerDependenciesMeta: + date-fns: + optional: true + dayjs: + optional: true + luxon: + optional: true + moment: + optional: true + + rc-progress@4.0.0: + resolution: {integrity: sha512-oofVMMafOCokIUIBnZLNcOZFsABaUw8PPrf1/y0ZBvKZNpOiu5h4AO9vv11Sw0p4Hb3D0yGWuEattcQGtNJ/aw==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-rate@2.13.1: + resolution: {integrity: sha512-QUhQ9ivQ8Gy7mtMZPAjLbxBt5y9GRp65VcUyGUMF3N3fhiftivPHdpuDIaWIMOTEprAjZPC08bls1dQB+I1F2Q==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-resize-observer@1.4.3: + resolution: {integrity: sha512-YZLjUbyIWox8E9i9C3Tm7ia+W7euPItNWSPX5sCcQTYbnwDb5uNpnLHQCG1f22oZWUhLw4Mv2tFmeWe68CDQRQ==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-segmented@2.7.1: + resolution: {integrity: sha512-izj1Nw/Dw2Vb7EVr+D/E9lUTkBe+kKC+SAFSU9zqr7WV2W5Ktaa9Gc7cB2jTqgk8GROJayltaec+DBlYKc6d+g==} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + rc-select@14.16.8: + resolution: {integrity: sha512-NOV5BZa1wZrsdkKaiK7LHRuo5ZjZYMDxPP6/1+09+FB4KoNi8jcG1ZqLE3AVCxEsYMBe65OBx71wFoHRTP3LRg==} + engines: {node: '>=8.x'} + peerDependencies: + react: '*' + react-dom: '*' + + rc-slider@10.6.2: + resolution: {integrity: sha512-FjkoFjyvUQWcBo1F3RgSglky3ar0+qHLM41PlFVYB4Bj3RD8E/Mv7kqMouLFBU+3aFglMzzctAIWRwajEuueSw==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-slider@11.1.9: + resolution: {integrity: sha512-h8IknhzSh3FEM9u8ivkskh+Ef4Yo4JRIY2nj7MrH6GQmrwV6mcpJf5/4KgH5JaVI1H3E52yCdpOlVyGZIeph5A==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-steps@6.0.1: + resolution: {integrity: sha512-lKHL+Sny0SeHkQKKDJlAjV5oZ8DwCdS2hFhAkIjuQt1/pB81M0cA0ErVFdHq9+jmPmFw1vJB2F5NBzFXLJxV+g==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-switch@4.1.0: + resolution: {integrity: sha512-TI8ufP2Az9oEbvyCeVE4+90PDSljGyuwix3fV58p7HV2o4wBnVToEyomJRVyTaZeqNPAp+vqeo4Wnj5u0ZZQBg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-table@7.54.0: + resolution: {integrity: sha512-/wDTkki6wBTjwylwAGjpLKYklKo9YgjZwAU77+7ME5mBoS32Q4nAwoqhA2lSge6fobLW3Tap6uc5xfwaL2p0Sw==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-tabs@15.7.0: + resolution: {integrity: sha512-ZepiE+6fmozYdWf/9gVp7k56PKHB1YYoDsKeQA1CBlJ/POIhjkcYiv0AGP0w2Jhzftd3AVvZP/K+V+Lpi2ankA==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-textarea@1.10.2: + resolution: {integrity: sha512-HfaeXiaSlpiSp0I/pvWpecFEHpVysZ9tpDLNkxQbMvMz6gsr7aVZ7FpWP9kt4t7DB+jJXesYS0us1uPZnlRnwQ==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-tooltip@6.4.0: + resolution: {integrity: sha512-kqyivim5cp8I5RkHmpsp1Nn/Wk+1oeloMv9c7LXNgDxUpGm+RbXJGL+OPvDlcRnx9DBeOe4wyOIl4OKUERyH1g==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-tree-select@5.27.0: + resolution: {integrity: sha512-2qTBTzwIT7LRI1o7zLyrCzmo5tQanmyGbSaGTIf7sYimCklAToVVfpMC6OAldSKolcnjorBYPNSKQqJmN3TCww==} + peerDependencies: + react: '*' + react-dom: '*' + + rc-tree@5.13.1: + resolution: {integrity: sha512-FNhIefhftobCdUJshO7M8uZTA9F4OPGVXqGfZkkD/5soDeOhwO06T/aKTrg0WD8gRg/pyfq+ql3aMymLHCTC4A==} + engines: {node: '>=10.x'} + peerDependencies: + react: '*' + react-dom: '*' + + rc-upload@4.9.2: + resolution: {integrity: sha512-nHx+9rbd1FKMiMRYsqQ3NkXUv7COHPBo3X1Obwq9SWS6/diF/A0aJ5OHubvwUAIDs+4RMleljV0pcrNUc823GQ==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-util@5.44.4: + resolution: {integrity: sha512-resueRJzmHG9Q6rI/DfK6Kdv9/Lfls05vzMs1Sk3M2P+3cJa+MakaZyWY8IPfehVuhPJFKrIY1IK4GqbiaiY5w==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-virtual-list@3.19.2: + resolution: {integrity: sha512-Ys6NcjwGkuwkeaWBDqfI3xWuZ7rDiQXlH1o2zLfFzATfEgXcqpk8CkgMfbJD81McqjcJVez25a3kPxCR807evA==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + + react-fast-compare@3.2.2: + resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==} + + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + react-refresh@0.18.0: + resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==} + engines: {node: '>=0.10.0'} + + react-router-dom@7.9.6: + resolution: {integrity: sha512-2MkC2XSXq6HjGcihnx1s0DBWQETI4mlis4Ux7YTLvP67xnGxCvq+BcCQSO81qQHVUTM1V53tl4iVVaY5sReCOA==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + + react-router@7.9.6: + resolution: {integrity: sha512-Y1tUp8clYRXpfPITyuifmSoE2vncSME18uVLgaqyxh9H35JWpIfzHo+9y3Fzh5odk/jxPW29IgLgzcdwxGqyNA==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + peerDependenciesMeta: + react-dom: + optional: true + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + + reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} + + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + resize-observer-polyfill@1.5.1: + resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve@1.22.12: + resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} + engines: {node: '>= 0.4'} + hasBin: true + + rettime@0.7.0: + resolution: {integrity: sha512-LPRKoHnLKd/r3dVxcwO7vhCW+orkOGj9ViueosEBK6ie89CijnfRlhaDhHq/3Hxu4CkWQtxwlBG0mzTQY6uQjw==} + + rollup@4.62.2: + resolution: {integrity: sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + safe-array-concat@1.1.4: + resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==} + engines: {node: '>=0.4'} + + safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + + screenfull@5.2.0: + resolution: {integrity: sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==} + engines: {node: '>=0.10.0'} + + scroll-into-view-if-needed@2.2.31: + resolution: {integrity: sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==} + + scroll-into-view-if-needed@3.1.0: + resolution: {integrity: sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} + hasBin: true + + set-cookie-parser@2.7.2: + resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.1: + resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} + engines: {node: '>= 0.4'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + slate-history@0.86.0: + resolution: {integrity: sha512-OxObL9tbhgwvSlnKSCpGIh7wnuaqvOj5jRExGjEyCU2Ke8ctf22HjT+jw7GEi9ttLzNTUmTEU3YIzqKGeqN+og==} + peerDependencies: + slate: '>=0.65.3' + + slate-react@0.92.0: + resolution: {integrity: sha512-xEDKu5RKw5f0N95l1UeNQnrB0Pxh4JPjpIZR/BVsMo0ININnLAknR99gLo46bl/Ffql4mr7LeaxQRoXxbFtJOQ==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + slate: '>=0.65.3' + + slate@0.91.4: + resolution: {integrity: sha512-aUJ3rpjrdi5SbJ5G1Qjr3arytfRkEStTmHjBfWq2A2Q8MybacIzkScSvGJjQkdTk3djCK9C9SEOt39sSeZFwTw==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.5.7: + resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} + engines: {node: '>=0.10.0'} + + split-on-first@1.1.0: + resolution: {integrity: sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==} + engines: {node: '>=6'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + + stop-iteration-iterator@1.1.0: + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} + engines: {node: '>= 0.4'} + + strict-event-emitter@0.5.1: + resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} + + strict-uri-encode@2.0.0: + resolution: {integrity: sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==} + engines: {node: '>=4'} + + string-convert@0.2.1: + resolution: {integrity: sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string.prototype.trim@1.2.11: + resolution: {integrity: sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.10: + resolution: {integrity: sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==} + engines: {node: '>= 0.4'} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + style-mod@4.1.3: + resolution: {integrity: sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==} + + stylis@4.2.0: + resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} + + stylis@4.4.0: + resolution: {integrity: sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + + tagged-tag@1.0.0: + resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} + engines: {node: '>=20'} + + throttle-debounce@5.0.2: + resolution: {integrity: sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==} + engines: {node: '>=12.22'} + + tiny-invariant@1.0.6: + resolution: {integrity: sha512-FOyLWWVjG+aC0UqG76V53yAWdXfH8bO6FNmyZOuUrzDzK8DI3/JRY25UD7+g49JWM1LXwymsKERB+DzI0dTEQA==} + + tiny-warning@1.0.3: + resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + engines: {node: '>=14.0.0'} + + tldts-core@7.4.9: + resolution: {integrity: sha512-DxKfPBI52p2msTEu7MPhdpdDTBhhVQg1a/8PjQckeyAvO13eMYElX545grIp6nnTGIMZlRvFZPvFhvI/WIz2Vg==} + + tldts@7.4.9: + resolution: {integrity: sha512-3kZ8wQQ/k5DrChD4X4FVvr2D7E5uoRgAqkPyLpSCGUvqOvqu+JEdr3mwMUaVWb+vMHZaKhF5fp2PBigKsui7hA==} + hasBin: true + + toggle-selection@1.0.6: + resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==} + + tough-cookie@6.0.2: + resolution: {integrity: sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==} + engines: {node: '>=16'} + + tr46@6.0.0: + resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} + engines: {node: '>=20'} + + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-fest@5.8.0: + resolution: {integrity: sha512-YGYEVz3Fm5iy/AybuA0oyNFq7H4CgQNfRp/qfe8nurE1kuCeNm3/vfm9X4Mtl+qLyaKJUh5xrFZwogr41SMjYA==} + engines: {node: '>=20'} + + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.4: + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.8: + resolution: {integrity: sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==} + engines: {node: '>= 0.4'} + + typescript-eslint@8.48.0: + resolution: {integrity: sha512-fcKOvQD9GUn3Xw63EgiDqhvWJ5jsyZUaekl3KVpGsDJnN46WJTe3jWxtQP9lMZm1LJNkFLlTaWAxK2vUQR+cqw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} + + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + + universal-env@1.0.7: + resolution: {integrity: sha512-N7n9nZSpVSKwjPDO0QsN+3/5pPPum9u5i7Km3xxUi0ykA8UD9nKt8foMYdVFB8HIRXx/Sh7HVu3pC5CJX/t9Tw==} + engines: {npm: '>=3.0.0'} + + until-async@3.0.2: + resolution: {integrity: sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + use-merge-value@1.2.0: + resolution: {integrity: sha512-DXgG0kkgJN45TcyoXL49vJnn55LehnrmoHc7MbKi+QDBvr8dsesqws8UlyIWGHMR+JXgxc1nvY+jDGMlycsUcw==} + peerDependencies: + react: '>= 16.x' + + uuid@11.1.1: + resolution: {integrity: sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==} + hasBin: true + + vite@7.2.4: + resolution: {integrity: sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@4.0.14: + resolution: {integrity: sha512-d9B2J9Cm9dN9+6nxMnnNJKJCtcyKfnHj15N6YNJfaFHRLua/d3sRKU9RuKmO9mB0XdFtUizlxfz/VPbd3OxGhw==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.0.14 + '@vitest/browser-preview': 4.0.14 + '@vitest/browser-webdriverio': 4.0.14 + '@vitest/ui': 4.0.14 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + w3c-keyname@2.2.8: + resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} + + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + + webidl-conversions@8.0.1: + resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} + engines: {node: '>=20'} + + whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation + + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + + whatwg-mimetype@5.0.0: + resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==} + engines: {node: '>=20'} + + whatwg-url@15.1.0: + resolution: {integrity: sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==} + engines: {node: '>=20'} + + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} + + which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.22: + resolution: {integrity: sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==} + engines: {node: '>= 0.4'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + ws@8.21.1: + resolution: {integrity: sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yaml@1.10.3: + resolution: {integrity: sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==} + engines: {node: '>= 6'} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.3: + resolution: {integrity: sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==} + engines: {node: '>=12'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + yoctocolors-cjs@2.1.3: + resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} + engines: {node: '>=18'} + + zod-validation-error@4.0.2: + resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + + zod@4.1.12: + resolution: {integrity: sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==} + +snapshots: + + '@acemir/cssom@0.9.31': {} + + '@adobe/css-tools@4.5.0': {} + + '@agentscope-ai/design@1.0.32(@babel/runtime@7.29.7)(@codemirror/language@6.12.4)(@codemirror/search@6.7.1)(@codemirror/theme-one-dark@6.1.3)(@rc-component/util@1.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.27)(antd@5.27.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(classnames@2.5.1)(codemirror@6.0.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@agentscope-ai/icons': 1.0.68 + '@agentscope-ai/icons-override-antd': 6.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@codemirror/autocomplete': 6.20.3 + '@codemirror/lang-cpp': 6.0.3 + '@codemirror/lang-css': 6.3.1 + '@codemirror/lang-go': 6.0.1 + '@codemirror/lang-html': 6.4.11 + '@codemirror/lang-java': 6.0.2 + '@codemirror/lang-javascript': 6.2.5 + '@codemirror/lang-json': 6.0.2 + '@codemirror/lang-markdown': 6.5.1 + '@codemirror/lang-php': 6.0.2 + '@codemirror/lang-python': 6.2.1 + '@codemirror/lang-yaml': 6.1.3 + '@codemirror/lint': 6.9.7 + '@codemirror/state': 6.7.1 + '@codemirror/view': 6.43.6 + '@emotion/react': 11.14.0(@types/react@18.3.27)(react@18.3.1) + '@halofe/global-locale': 1.0.3 + '@halofe/global-string-format': 1.0.8 + '@rc-component/util': 1.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@uiw/codemirror-extensions-mentions': 4.25.11(@codemirror/state@6.7.1)(@codemirror/view@6.43.6) + '@uiw/codemirror-theme-vscode': 4.25.11(@codemirror/language@6.12.4)(@codemirror/state@6.7.1)(@codemirror/view@6.43.6) + '@uiw/react-codemirror': 4.20.2(@babel/runtime@7.29.7)(@codemirror/autocomplete@6.20.3)(@codemirror/language@6.12.4)(@codemirror/lint@6.9.7)(@codemirror/search@6.7.1)(@codemirror/state@6.7.1)(@codemirror/theme-one-dark@6.1.3)(@codemirror/view@6.43.6)(codemirror@6.0.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + ahooks: 3.9.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + antd: 5.27.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + antd-style: 3.7.1(@types/react@18.3.27)(antd@5.27.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + dayjs: 1.11.21 + dompurify: 2.5.9 + js-cookie: 3.0.8 + lodash-es: 4.18.1 + query-string: 7.1.3 + rc-slider: 10.6.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + slate: 0.91.4 + slate-history: 0.86.0(slate@0.91.4) + slate-react: 0.92.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate@0.91.4) + stylis: 4.4.0 + uuid: 11.1.1 + transitivePeerDependencies: + - '@babel/runtime' + - '@codemirror/language' + - '@codemirror/search' + - '@codemirror/theme-one-dark' + - '@types/react' + - codemirror + - supports-color + + '@agentscope-ai/icons-override-antd@6.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@agentscope-ai/icons-svg-override-antd': 4.4.2 + '@ant-design/colors': 8.0.1 + '@rc-component/util': 1.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + es-abstract: 1.24.2 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@agentscope-ai/icons-svg-override-antd@4.4.2': {} + + '@agentscope-ai/icons@1.0.68': {} + + '@ant-design/colors@7.2.1': + dependencies: + '@ant-design/fast-color': 2.0.6 + + '@ant-design/colors@8.0.1': + dependencies: + '@ant-design/fast-color': 3.0.1 + + '@ant-design/cssinjs-utils@1.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@ant-design/cssinjs': 1.24.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@babel/runtime': 7.29.7 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@ant-design/cssinjs@1.24.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.29.7 + '@emotion/hash': 0.8.0 + '@emotion/unitless': 0.7.5 + classnames: 2.5.1 + csstype: 3.2.3 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + stylis: 4.4.0 + + '@ant-design/fast-color@2.0.6': + dependencies: + '@babel/runtime': 7.29.7 + + '@ant-design/fast-color@3.0.1': {} + + '@ant-design/icons-svg@4.5.0': {} + + '@ant-design/icons@5.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@ant-design/colors': 7.2.1 + '@ant-design/icons-svg': 4.5.0 + '@babel/runtime': 7.29.7 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@ant-design/react-slick@1.1.2(react@18.3.1)': + dependencies: + '@babel/runtime': 7.29.7 + classnames: 2.5.1 + json2mq: 0.2.0 + react: 18.3.1 + resize-observer-polyfill: 1.5.1 + throttle-debounce: 5.0.2 + + '@asamuzakjp/css-color@4.1.2': + dependencies: + '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.1.9(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + lru-cache: 11.5.2 + + '@asamuzakjp/dom-selector@6.8.1': + dependencies: + '@asamuzakjp/nwsapi': 2.3.9 + bidi-js: 1.0.3 + css-tree: 3.2.1 + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.5.2 + + '@asamuzakjp/nwsapi@2.3.9': {} + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.7': {} + + '@babel/core@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helpers': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.7': + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.29.7': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.28.6 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.29.7': {} + + '@babel/helper-module-imports@7.29.7': + dependencies: + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.29.7': {} + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/helper-validator-option@7.29.7': {} + + '@babel/helpers@7.29.7': + dependencies: + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 + + '@babel/parser@7.29.7': + dependencies: + '@babel/types': 7.29.7 + + '@babel/plugin-transform-react-jsx-self@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-react-jsx-source@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/runtime@7.29.7': {} + + '@babel/template@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + + '@babel/traverse@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.7': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@bcoe/v8-coverage@1.0.2': {} + + '@codemirror/autocomplete@6.20.3': + dependencies: + '@codemirror/language': 6.12.4 + '@codemirror/state': 6.7.1 + '@codemirror/view': 6.43.6 + '@lezer/common': 1.5.2 + + '@codemirror/commands@6.10.4': + dependencies: + '@codemirror/language': 6.12.4 + '@codemirror/state': 6.7.1 + '@codemirror/view': 6.43.6 + '@lezer/common': 1.5.2 + + '@codemirror/lang-cpp@6.0.3': + dependencies: + '@codemirror/language': 6.12.4 + '@lezer/cpp': 1.1.6 + + '@codemirror/lang-css@6.3.1': + dependencies: + '@codemirror/autocomplete': 6.20.3 + '@codemirror/language': 6.12.4 + '@codemirror/state': 6.7.1 + '@lezer/common': 1.5.2 + '@lezer/css': 1.3.4 + + '@codemirror/lang-go@6.0.1': + dependencies: + '@codemirror/autocomplete': 6.20.3 + '@codemirror/language': 6.12.4 + '@codemirror/state': 6.7.1 + '@lezer/common': 1.5.2 + '@lezer/go': 1.0.1 + + '@codemirror/lang-html@6.4.11': + dependencies: + '@codemirror/autocomplete': 6.20.3 + '@codemirror/lang-css': 6.3.1 + '@codemirror/lang-javascript': 6.2.5 + '@codemirror/language': 6.12.4 + '@codemirror/state': 6.7.1 + '@codemirror/view': 6.43.6 + '@lezer/common': 1.5.2 + '@lezer/css': 1.3.4 + '@lezer/html': 1.3.13 + + '@codemirror/lang-java@6.0.2': + dependencies: + '@codemirror/language': 6.12.4 + '@lezer/java': 1.1.3 + + '@codemirror/lang-javascript@6.2.5': + dependencies: + '@codemirror/autocomplete': 6.20.3 + '@codemirror/language': 6.12.4 + '@codemirror/lint': 6.9.7 + '@codemirror/state': 6.7.1 + '@codemirror/view': 6.43.6 + '@lezer/common': 1.5.2 + '@lezer/javascript': 1.5.4 + + '@codemirror/lang-json@6.0.2': + dependencies: + '@codemirror/language': 6.12.4 + '@lezer/json': 1.0.3 + + '@codemirror/lang-markdown@6.5.1': + dependencies: + '@codemirror/autocomplete': 6.20.3 + '@codemirror/lang-html': 6.4.11 + '@codemirror/language': 6.12.4 + '@codemirror/state': 6.7.1 + '@codemirror/view': 6.43.6 + '@lezer/common': 1.5.2 + '@lezer/markdown': 1.7.2 + + '@codemirror/lang-php@6.0.2': + dependencies: + '@codemirror/lang-html': 6.4.11 + '@codemirror/language': 6.12.4 + '@codemirror/state': 6.7.1 + '@lezer/common': 1.5.2 + '@lezer/php': 1.0.5 + + '@codemirror/lang-python@6.2.1': + dependencies: + '@codemirror/autocomplete': 6.20.3 + '@codemirror/language': 6.12.4 + '@codemirror/state': 6.7.1 + '@lezer/common': 1.5.2 + '@lezer/python': 1.1.19 + + '@codemirror/lang-yaml@6.1.3': + dependencies: + '@codemirror/autocomplete': 6.20.3 + '@codemirror/language': 6.12.4 + '@codemirror/state': 6.7.1 + '@lezer/common': 1.5.2 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.10 + '@lezer/yaml': 1.0.4 + + '@codemirror/language@6.12.4': + dependencies: + '@codemirror/state': 6.7.1 + '@codemirror/view': 6.43.6 + '@lezer/common': 1.5.2 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.10 + style-mod: 4.1.3 + + '@codemirror/lint@6.9.7': + dependencies: + '@codemirror/state': 6.7.1 + '@codemirror/view': 6.43.6 + crelt: 1.0.7 + + '@codemirror/search@6.7.1': + dependencies: + '@codemirror/state': 6.7.1 + '@codemirror/view': 6.43.6 + crelt: 1.0.7 + + '@codemirror/state@6.7.1': + dependencies: + '@marijn/find-cluster-break': 1.0.3 + + '@codemirror/theme-one-dark@6.1.3': + dependencies: + '@codemirror/language': 6.12.4 + '@codemirror/state': 6.7.1 + '@codemirror/view': 6.43.6 + '@lezer/highlight': 1.2.3 + + '@codemirror/view@6.43.6': + dependencies: + '@codemirror/state': 6.7.1 + crelt: 1.0.7 + style-mod: 4.1.3 + w3c-keyname: 2.2.8 + + '@csstools/color-helpers@6.1.0': {} + + '@csstools/css-calc@3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-color-parser@4.1.9(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/color-helpers': 6.1.0 + '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.6(css-tree@3.2.1)': + optionalDependencies: + css-tree: 3.2.1 + + '@csstools/css-tokenizer@4.0.0': {} + + '@emotion/babel-plugin@11.13.5': + dependencies: + '@babel/helper-module-imports': 7.29.7 + '@babel/runtime': 7.29.7 + '@emotion/hash': 0.9.2 + '@emotion/memoize': 0.9.0 + '@emotion/serialize': 1.3.3 + babel-plugin-macros: 3.1.0 + convert-source-map: 1.9.0 + escape-string-regexp: 4.0.0 + find-root: 1.1.0 + source-map: 0.5.7 + stylis: 4.2.0 + transitivePeerDependencies: + - supports-color + + '@emotion/cache@11.14.0': + dependencies: + '@emotion/memoize': 0.9.0 + '@emotion/sheet': 1.4.0 + '@emotion/utils': 1.4.2 + '@emotion/weak-memoize': 0.4.0 + stylis: 4.2.0 + + '@emotion/css@11.13.5': + dependencies: + '@emotion/babel-plugin': 11.13.5 + '@emotion/cache': 11.14.0 + '@emotion/serialize': 1.3.3 + '@emotion/sheet': 1.4.0 + '@emotion/utils': 1.4.2 + transitivePeerDependencies: + - supports-color + + '@emotion/hash@0.8.0': {} + + '@emotion/hash@0.9.2': {} + + '@emotion/memoize@0.9.0': {} + + '@emotion/react@11.14.0(@types/react@18.3.27)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.29.7 + '@emotion/babel-plugin': 11.13.5 + '@emotion/cache': 11.14.0 + '@emotion/serialize': 1.3.3 + '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@18.3.1) + '@emotion/utils': 1.4.2 + '@emotion/weak-memoize': 0.4.0 + hoist-non-react-statics: 3.3.2 + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.27 + transitivePeerDependencies: + - supports-color + + '@emotion/serialize@1.3.3': + dependencies: + '@emotion/hash': 0.9.2 + '@emotion/memoize': 0.9.0 + '@emotion/unitless': 0.10.0 + '@emotion/utils': 1.4.2 + csstype: 3.2.3 + + '@emotion/sheet@1.4.0': {} + + '@emotion/unitless@0.10.0': {} + + '@emotion/unitless@0.7.5': {} + + '@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@18.3.1)': + dependencies: + react: 18.3.1 + + '@emotion/utils@1.4.2': {} + + '@emotion/weak-memoize@0.4.0': {} + + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-ia32@0.25.12': + optional: true + + '@esbuild/linux-loong64@0.25.12': + optional: true + + '@esbuild/linux-mips64el@0.25.12': + optional: true + + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.1)': + dependencies: + eslint: 9.39.1 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.21.2': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.3 + minimatch: 3.1.5 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 + + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.6': + dependencies: + ajv: 6.15.0 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.3.0 + minimatch: 3.1.5 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.39.1': {} + + '@eslint/object-schema@2.1.7': {} + + '@eslint/plugin-kit@0.4.1': + dependencies: + '@eslint/core': 0.17.0 + levn: 0.4.1 + + '@halofe/global-locale@1.0.3': {} + + '@halofe/global-string-format@1.0.8': + dependencies: + intl-messageformat: 2.2.0 + universal-env: 1.0.7 + + '@humanfs/core@0.19.2': + dependencies: + '@humanfs/types': 0.15.0 + + '@humanfs/node@0.16.8': + dependencies: + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 + '@humanwhocodes/retry': 0.4.3 + + '@humanfs/types@0.15.0': {} + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@inquirer/ansi@1.0.2': {} + + '@inquirer/confirm@5.1.21(@types/node@24.10.1)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@24.10.1) + '@inquirer/type': 3.0.10(@types/node@24.10.1) + optionalDependencies: + '@types/node': 24.10.1 + + '@inquirer/core@10.3.2(@types/node@24.10.1)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@24.10.1) + cli-width: 4.1.0 + mute-stream: 2.0.0 + signal-exit: 4.1.0 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 24.10.1 + + '@inquirer/figures@1.0.15': {} + + '@inquirer/type@3.0.10(@types/node@24.10.1)': + optionalDependencies: + '@types/node': 24.10.1 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@juggle/resize-observer@3.4.0': {} + + '@lezer/common@1.5.2': {} + + '@lezer/cpp@1.1.6': + dependencies: + '@lezer/common': 1.5.2 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.10 + + '@lezer/css@1.3.4': + dependencies: + '@lezer/common': 1.5.2 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.10 + + '@lezer/go@1.0.1': + dependencies: + '@lezer/common': 1.5.2 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.10 + + '@lezer/highlight@1.2.3': + dependencies: + '@lezer/common': 1.5.2 + + '@lezer/html@1.3.13': + dependencies: + '@lezer/common': 1.5.2 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.10 + + '@lezer/java@1.1.3': + dependencies: + '@lezer/common': 1.5.2 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.10 + + '@lezer/javascript@1.5.4': + dependencies: + '@lezer/common': 1.5.2 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.10 + + '@lezer/json@1.0.3': + dependencies: + '@lezer/common': 1.5.2 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.10 + + '@lezer/lr@1.4.10': + dependencies: + '@lezer/common': 1.5.2 + + '@lezer/markdown@1.7.2': + dependencies: + '@lezer/common': 1.5.2 + '@lezer/highlight': 1.2.3 + + '@lezer/php@1.0.5': + dependencies: + '@lezer/common': 1.5.2 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.10 + + '@lezer/python@1.1.19': + dependencies: + '@lezer/common': 1.5.2 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.10 + + '@lezer/yaml@1.0.4': + dependencies: + '@lezer/common': 1.5.2 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.10 + + '@marijn/find-cluster-break@1.0.3': {} + + '@mswjs/interceptors@0.40.0': + dependencies: + '@open-draft/deferred-promise': 2.2.0 + '@open-draft/logger': 0.3.0 + '@open-draft/until': 2.1.0 + is-node-process: 1.2.0 + outvariant: 1.4.3 + strict-event-emitter: 0.5.1 + + '@open-draft/deferred-promise@2.2.0': {} + + '@open-draft/logger@0.3.0': + dependencies: + is-node-process: 1.2.0 + outvariant: 1.4.3 + + '@open-draft/until@2.1.0': {} + + '@rc-component/async-validator@5.1.2': + dependencies: + '@babel/runtime': 7.29.7 + + '@rc-component/color-picker@2.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@ant-design/fast-color': 2.0.6 + '@babel/runtime': 7.29.7 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@rc-component/context@1.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.29.7 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@rc-component/mini-decimal@1.1.4': + dependencies: + '@babel/runtime': 7.29.7 + + '@rc-component/mutate-observer@1.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.29.7 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@rc-component/portal@1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.29.7 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@rc-component/qrcode@1.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.29.7 + classnames: 2.5.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@rc-component/tour@1.15.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.29.7 + '@rc-component/portal': 1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@rc-component/trigger': 2.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@rc-component/trigger@2.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.29.7 + '@rc-component/portal': 1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@rc-component/util@1.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-is: 18.3.1 + + '@rolldown/pluginutils@1.0.0-beta.47': {} + + '@rollup/rollup-android-arm-eabi@4.62.2': + optional: true + + '@rollup/rollup-android-arm64@4.62.2': + optional: true + + '@rollup/rollup-darwin-arm64@4.62.2': + optional: true + + '@rollup/rollup-darwin-x64@4.62.2': + optional: true + + '@rollup/rollup-freebsd-arm64@4.62.2': + optional: true + + '@rollup/rollup-freebsd-x64@4.62.2': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.62.2': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.62.2': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.62.2': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.62.2': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.62.2': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.62.2': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-x64-musl@4.62.2': + optional: true + + '@rollup/rollup-openbsd-x64@4.62.2': + optional: true + + '@rollup/rollup-openharmony-arm64@4.62.2': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.62.2': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.62.2': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.62.2': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.62.2': + optional: true + + '@standard-schema/spec@1.1.0': {} + + '@tanstack/query-core@5.90.20': {} + + '@tanstack/react-query@5.90.20(react@18.3.1)': + dependencies: + '@tanstack/query-core': 5.90.20 + react: 18.3.1 + + '@testing-library/dom@10.4.1': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/runtime': 7.29.7 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + picocolors: 1.1.1 + pretty-format: 27.5.1 + + '@testing-library/jest-dom@6.9.1': + dependencies: + '@adobe/css-tools': 4.5.0 + aria-query: 5.3.2 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + picocolors: 1.1.1 + redent: 3.0.0 + + '@testing-library/react@16.3.0(@testing-library/dom@10.4.1)(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.29.7 + '@testing-library/dom': 10.4.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.7(@types/react@18.3.27) + + '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)': + dependencies: + '@testing-library/dom': 10.4.1 + + '@types/aria-query@5.0.4': {} + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.29.7 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.29.7 + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.9': {} + + '@types/is-hotkey@0.1.10': {} + + '@types/js-cookie@3.0.6': {} + + '@types/json-schema@7.0.15': {} + + '@types/lodash@4.17.24': {} + + '@types/node@24.10.1': + dependencies: + undici-types: 7.16.0 + + '@types/parse-json@4.0.2': {} + + '@types/prop-types@15.7.15': {} + + '@types/react-dom@18.3.7(@types/react@18.3.27)': + dependencies: + '@types/react': 18.3.27 + + '@types/react@18.3.27': + dependencies: + '@types/prop-types': 15.7.15 + csstype: 3.2.3 + + '@types/statuses@2.0.6': {} + + '@typescript-eslint/eslint-plugin@8.48.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.48.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.48.0 + '@typescript-eslint/type-utils': 8.48.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/utils': 8.48.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.48.0 + eslint: 9.39.1 + graphemer: 1.4.0 + ignore: 7.0.6 + natural-compare: 1.4.0 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.48.0(eslint@9.39.1)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.48.0 + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/typescript-estree': 8.48.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.48.0 + debug: 4.4.3 + eslint: 9.39.1 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.48.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.48.0(typescript@5.9.3) + '@typescript-eslint/types': 8.48.0 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.48.0': + dependencies: + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/visitor-keys': 8.48.0 + + '@typescript-eslint/tsconfig-utils@8.48.0(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.48.0(eslint@9.39.1)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/typescript-estree': 8.48.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.48.0(eslint@9.39.1)(typescript@5.9.3) + debug: 4.4.3 + eslint: 9.39.1 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.48.0': {} + + '@typescript-eslint/typescript-estree@8.48.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.48.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.48.0(typescript@5.9.3) + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/visitor-keys': 8.48.0 + debug: 4.4.3 + minimatch: 9.0.9 + semver: 7.8.5 + tinyglobby: 0.2.17 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.48.0(eslint@9.39.1)(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.1) + '@typescript-eslint/scope-manager': 8.48.0 + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/typescript-estree': 8.48.0(typescript@5.9.3) + eslint: 9.39.1 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.48.0': + dependencies: + '@typescript-eslint/types': 8.48.0 + eslint-visitor-keys: 4.2.1 + + '@uiw/codemirror-extensions-basic-setup@4.20.2(@codemirror/autocomplete@6.20.3)(@codemirror/commands@6.10.4)(@codemirror/language@6.12.4)(@codemirror/lint@6.9.7)(@codemirror/search@6.7.1)(@codemirror/state@6.7.1)(@codemirror/view@6.43.6)': + dependencies: + '@codemirror/autocomplete': 6.20.3 + '@codemirror/commands': 6.10.4 + '@codemirror/language': 6.12.4 + '@codemirror/lint': 6.9.7 + '@codemirror/search': 6.7.1 + '@codemirror/state': 6.7.1 + '@codemirror/view': 6.43.6 + + '@uiw/codemirror-extensions-mentions@4.25.11(@codemirror/state@6.7.1)(@codemirror/view@6.43.6)': + dependencies: + '@codemirror/state': 6.7.1 + '@codemirror/view': 6.43.6 + + '@uiw/codemirror-theme-vscode@4.25.11(@codemirror/language@6.12.4)(@codemirror/state@6.7.1)(@codemirror/view@6.43.6)': + dependencies: + '@uiw/codemirror-themes': 4.25.11(@codemirror/language@6.12.4)(@codemirror/state@6.7.1)(@codemirror/view@6.43.6) + transitivePeerDependencies: + - '@codemirror/language' + - '@codemirror/state' + - '@codemirror/view' + + '@uiw/codemirror-themes@4.25.11(@codemirror/language@6.12.4)(@codemirror/state@6.7.1)(@codemirror/view@6.43.6)': + dependencies: + '@codemirror/language': 6.12.4 + '@codemirror/state': 6.7.1 + '@codemirror/view': 6.43.6 + + '@uiw/react-codemirror@4.20.2(@babel/runtime@7.29.7)(@codemirror/autocomplete@6.20.3)(@codemirror/language@6.12.4)(@codemirror/lint@6.9.7)(@codemirror/search@6.7.1)(@codemirror/state@6.7.1)(@codemirror/theme-one-dark@6.1.3)(@codemirror/view@6.43.6)(codemirror@6.0.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.29.7 + '@codemirror/commands': 6.10.4 + '@codemirror/state': 6.7.1 + '@codemirror/theme-one-dark': 6.1.3 + '@codemirror/view': 6.43.6 + '@uiw/codemirror-extensions-basic-setup': 4.20.2(@codemirror/autocomplete@6.20.3)(@codemirror/commands@6.10.4)(@codemirror/language@6.12.4)(@codemirror/lint@6.9.7)(@codemirror/search@6.7.1)(@codemirror/state@6.7.1)(@codemirror/view@6.43.6) + codemirror: 6.0.2 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + transitivePeerDependencies: + - '@codemirror/autocomplete' + - '@codemirror/language' + - '@codemirror/lint' + - '@codemirror/search' + + '@vitejs/plugin-react@5.1.1(vite@7.2.4(@types/node@24.10.1))': + dependencies: + '@babel/core': 7.29.7 + '@babel/plugin-transform-react-jsx-self': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-react-jsx-source': 7.29.7(@babel/core@7.29.7) + '@rolldown/pluginutils': 1.0.0-beta.47 + '@types/babel__core': 7.20.5 + react-refresh: 0.18.0 + vite: 7.2.4(@types/node@24.10.1) + transitivePeerDependencies: + - supports-color + + '@vitest/coverage-v8@4.0.14(vitest@4.0.14(@types/node@24.10.1)(jsdom@27.2.0)(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3)))': + dependencies: + '@bcoe/v8-coverage': 1.0.2 + '@vitest/utils': 4.0.14 + ast-v8-to-istanbul: 0.3.12 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.2.0 + magicast: 0.5.3 + obug: 2.1.4 + std-env: 3.10.0 + tinyrainbow: 3.1.0 + vitest: 4.0.14(@types/node@24.10.1)(jsdom@27.2.0)(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3)) + transitivePeerDependencies: + - supports-color + + '@vitest/expect@4.0.14': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.0.14 + '@vitest/utils': 4.0.14 + chai: 6.2.2 + tinyrainbow: 3.1.0 + + '@vitest/mocker@4.0.14(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(vite@7.2.4(@types/node@24.10.1))': + dependencies: + '@vitest/spy': 4.0.14 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + msw: 2.12.3(@types/node@24.10.1)(typescript@5.9.3) + vite: 7.2.4(@types/node@24.10.1) + + '@vitest/pretty-format@4.0.14': + dependencies: + tinyrainbow: 3.1.0 + + '@vitest/runner@4.0.14': + dependencies: + '@vitest/utils': 4.0.14 + pathe: 2.0.3 + + '@vitest/snapshot@4.0.14': + dependencies: + '@vitest/pretty-format': 4.0.14 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.0.14': {} + + '@vitest/utils@4.0.14': + dependencies: + '@vitest/pretty-format': 4.0.14 + tinyrainbow: 3.1.0 + + acorn-jsx@5.3.2(acorn@8.17.0): + dependencies: + acorn: 8.17.0 + + acorn@8.17.0: {} + + agent-base@7.1.4: {} + + ahooks@3.9.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.29.7 + '@types/js-cookie': 3.0.6 + dayjs: 1.11.21 + intersection-observer: 0.12.2 + js-cookie: 3.0.8 + lodash: 4.18.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-fast-compare: 3.2.2 + resize-observer-polyfill: 1.5.1 + screenfull: 5.2.0 + tslib: 2.8.1 + + ajv@6.15.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-regex@5.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + antd-style@3.7.1(@types/react@18.3.27)(antd@5.27.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@ant-design/cssinjs': 1.24.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@babel/runtime': 7.29.7 + '@emotion/cache': 11.14.0 + '@emotion/css': 11.13.5 + '@emotion/react': 11.14.0(@types/react@18.3.27)(react@18.3.1) + '@emotion/serialize': 1.3.3 + '@emotion/utils': 1.4.2 + antd: 5.27.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + use-merge-value: 1.2.0(react@18.3.1) + transitivePeerDependencies: + - '@types/react' + - react-dom + - supports-color + + antd@5.27.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@ant-design/colors': 7.2.1 + '@ant-design/cssinjs': 1.24.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@ant-design/cssinjs-utils': 1.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@ant-design/fast-color': 2.0.6 + '@ant-design/icons': 5.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@ant-design/react-slick': 1.1.2(react@18.3.1) + '@babel/runtime': 7.29.7 + '@rc-component/color-picker': 2.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@rc-component/mutate-observer': 1.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@rc-component/qrcode': 1.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@rc-component/tour': 1.15.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@rc-component/trigger': 2.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + copy-to-clipboard: 3.3.3 + dayjs: 1.11.21 + rc-cascader: 3.34.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-checkbox: 3.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-collapse: 3.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-dialog: 9.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-drawer: 7.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-dropdown: 4.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-field-form: 2.7.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-image: 7.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-input: 1.8.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-input-number: 9.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-mentions: 2.20.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-menu: 9.16.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-notification: 5.6.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-pagination: 5.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-picker: 4.11.3(dayjs@1.11.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-progress: 4.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-rate: 2.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-segmented: 2.7.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-select: 14.16.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-slider: 11.1.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-steps: 6.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-switch: 4.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-table: 7.54.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-tabs: 15.7.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-textarea: 1.10.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-tooltip: 6.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-tree: 5.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-tree-select: 5.27.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-upload: 4.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + scroll-into-view-if-needed: 3.1.0 + throttle-debounce: 5.0.2 + transitivePeerDependencies: + - date-fns + - luxon + - moment + + argparse@2.0.1: {} + + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + + aria-query@5.3.2: {} + + array-buffer-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + is-array-buffer: 3.0.5 + + arraybuffer.prototype.slice@1.0.4: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + is-array-buffer: 3.0.5 + + assertion-error@2.0.1: {} + + ast-v8-to-istanbul@0.3.12: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + estree-walker: 3.0.3 + js-tokens: 10.0.0 + + async-function@1.0.0: {} + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + + babel-plugin-macros@3.1.0: + dependencies: + '@babel/runtime': 7.29.7 + cosmiconfig: 7.1.0 + resolve: 1.22.12 + + balanced-match@1.0.2: {} + + baseline-browser-mapping@2.10.43: {} + + bidi-js@1.0.3: + dependencies: + require-from-string: 2.0.2 + + brace-expansion@1.1.16: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.1.2: + dependencies: + balanced-match: 1.0.2 + + browserslist@4.28.6: + dependencies: + baseline-browser-mapping: 2.10.43 + caniuse-lite: 1.0.30001806 + electron-to-chromium: 1.5.392 + node-releases: 2.0.51 + update-browserslist-db: 1.2.3(browserslist@4.28.6) + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.9: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + + caniuse-lite@1.0.30001806: {} + + chai@6.2.2: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + classnames@2.5.1: {} + + cli-width@4.1.0: {} + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + codemirror@6.0.2: + dependencies: + '@codemirror/autocomplete': 6.20.3 + '@codemirror/commands': 6.10.4 + '@codemirror/language': 6.12.4 + '@codemirror/lint': 6.9.7 + '@codemirror/search': 6.7.1 + '@codemirror/state': 6.7.1 + '@codemirror/view': 6.43.6 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + compute-scroll-into-view@1.0.20: {} + + compute-scroll-into-view@3.1.1: {} + + concat-map@0.0.1: {} + + convert-source-map@1.9.0: {} + + convert-source-map@2.0.0: {} + + cookie@1.1.1: {} + + copy-to-clipboard@3.3.3: + dependencies: + toggle-selection: 1.0.6 + + cosmiconfig@7.1.0: + dependencies: + '@types/parse-json': 4.0.2 + import-fresh: 3.3.1 + parse-json: 5.2.0 + path-type: 4.0.0 + yaml: 1.10.3 + + crelt@1.0.7: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + css-tree@3.2.1: + dependencies: + mdn-data: 2.27.1 + source-map-js: 1.2.1 + + css.escape@1.5.1: {} + + cssstyle@5.3.7: + dependencies: + '@asamuzakjp/css-color': 4.1.2 + '@csstools/css-syntax-patches-for-csstree': 1.1.6(css-tree@3.2.1) + css-tree: 3.2.1 + lru-cache: 11.5.2 + + csstype@3.2.3: {} + + data-urls@6.0.1: + dependencies: + whatwg-mimetype: 5.0.0 + whatwg-url: 15.1.0 + + data-view-buffer@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-offset@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + dayjs@1.11.21: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decimal.js@10.6.0: {} + + decode-uri-component@0.2.2: {} + + deep-is@0.1.4: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + dequal@2.0.3: {} + + direction@1.0.4: {} + + dom-accessibility-api@0.5.16: {} + + dom-accessibility-api@0.6.3: {} + + dompurify@2.5.9: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + electron-to-chromium@1.5.392: {} + + emoji-regex@8.0.0: {} + + entities@8.0.0: {} + + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + + es-abstract-get@1.0.0: + dependencies: + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + is-callable: 1.2.7 + object-inspect: 1.13.4 + + es-abstract@1.24.2: + dependencies: + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 + available-typed-arrays: 1.0.7 + call-bind: 1.0.9 + call-bound: 1.0.4 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.4 + function.prototype.name: 1.2.0 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 + is-callable: 1.2.7 + is-data-view: 1.0.2 + is-negative-zero: 2.0.3 + is-regex: 1.2.1 + is-set: 2.0.3 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.1 + math-intrinsics: 1.1.0 + object-inspect: 1.13.4 + object-keys: 1.1.1 + object.assign: 4.1.7 + own-keys: 1.0.1 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.4 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + stop-iteration-iterator: 1.1.0 + string.prototype.trim: 1.2.11 + string.prototype.trimend: 1.0.10 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 + typed-array-length: 1.0.8 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.22 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-module-lexer@1.7.0: {} + + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.4 + + es-to-primitive@1.3.4: + dependencies: + es-abstract-get: 1.0.0 + es-define-property: 1.0.1 + es-errors: 1.3.0 + is-callable: 1.2.7 + is-date-object: 1.1.0 + is-symbol: 1.1.1 + + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + + escalade@3.2.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-plugin-react-hooks@7.0.1(eslint@9.39.1): + dependencies: + '@babel/core': 7.29.7 + '@babel/parser': 7.29.7 + eslint: 9.39.1 + hermes-parser: 0.25.1 + zod: 4.1.12 + zod-validation-error: 4.0.2(zod@4.1.12) + transitivePeerDependencies: + - supports-color + + eslint-plugin-react-refresh@0.4.24(eslint@9.39.1): + dependencies: + eslint: 9.39.1 + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint@9.39.1: + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.1) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.2 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.6 + '@eslint/js': 9.39.1 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.8 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.9 + ajv: 6.15.0 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.5 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.17.0 + acorn-jsx: 5.3.2(acorn@8.17.0) + eslint-visitor-keys: 4.2.1 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + + esutils@2.0.3: {} + + expect-type@1.4.0: {} + + fast-deep-equal@3.1.3: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fdir@6.5.0(picomatch@4.0.5): + optionalDependencies: + picomatch: 4.0.5 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + filter-obj@1.1.0: {} + + find-root@1.1.0: {} + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.4.2 + keyv: 4.5.4 + + flatted@3.4.2: {} + + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + function.prototype.name@1.2.0: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + es-define-property: 1.0.1 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + hasown: 2.0.4 + is-callable: 1.2.7 + is-document.all: 1.0.0 + + functions-have-names@1.2.3: {} + + generator-function@2.0.1: {} + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.2 + + get-symbol-description@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@14.0.0: {} + + globals@16.5.0: {} + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + + gopd@1.2.0: {} + + graphemer@1.4.0: {} + + graphql@16.14.2: {} + + has-bigints@1.1.0: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-proto@1.2.0: + dependencies: + dunder-proto: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + + headers-polyfill@4.0.3: {} + + hermes-estree@0.25.1: {} + + hermes-parser@0.25.1: + dependencies: + hermes-estree: 0.25.1 + + hoist-non-react-statics@3.3.2: + dependencies: + react-is: 16.13.1 + + html-encoding-sniffer@4.0.0: + dependencies: + whatwg-encoding: 3.1.1 + + html-escaper@2.0.2: {} + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + ignore@5.3.2: {} + + ignore@7.0.6: {} + + immer@9.0.21: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + indent-string@4.0.0: {} + + internal-slot@1.1.0: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.4 + side-channel: 1.1.1 + + intersection-observer@0.12.2: {} + + intl-messageformat-parser@1.4.0: {} + + intl-messageformat@2.2.0: + dependencies: + intl-messageformat-parser: 1.4.0 + + is-array-buffer@3.0.5: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-arrayish@0.2.1: {} + + is-async-function@2.1.1: + dependencies: + async-function: 1.0.0 + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-bigint@1.1.0: + dependencies: + has-bigints: 1.1.0 + + is-boolean-object@1.2.2: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-callable@1.2.7: {} + + is-core-module@2.16.2: + dependencies: + hasown: 2.0.4 + + is-data-view@1.0.2: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-typed-array: 1.1.15 + + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-document.all@1.0.0: + dependencies: + call-bound: 1.0.4 + + is-extglob@2.1.1: {} + + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-fullwidth-code-point@3.0.0: {} + + is-generator-function@1.1.2: + dependencies: + call-bound: 1.0.4 + generator-function: 2.0.1 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-hotkey@0.1.8: {} + + is-map@2.0.3: {} + + is-negative-zero@2.0.3: {} + + is-node-process@1.2.0: {} + + is-number-object@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-plain-object@5.0.0: {} + + is-potential-custom-element-name@1.0.1: {} + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.4 + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.4: + dependencies: + call-bound: 1.0.4 + + is-string@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-symbol@1.1.1: + dependencies: + call-bound: 1.0.4 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.22 + + is-weakmap@2.0.2: {} + + is-weakref@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-weakset@2.0.4: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@5.0.6: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + debug: 4.4.3 + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + js-cookie@3.0.8: {} + + js-tokens@10.0.0: {} + + js-tokens@4.0.0: {} + + js-yaml@4.3.0: + dependencies: + argparse: 2.0.1 + + jsdom@27.2.0: + dependencies: + '@acemir/cssom': 0.9.31 + '@asamuzakjp/dom-selector': 6.8.1 + cssstyle: 5.3.7 + data-urls: 6.0.1 + decimal.js: 10.6.0 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-potential-custom-element-name: 1.0.1 + parse5: 8.0.1 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 6.0.2 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 8.0.1 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 15.1.0 + ws: 8.21.1 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json2mq@0.2.0: + dependencies: + string-convert: 0.2.1 + + json5@2.2.3: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lines-and-columns@1.2.4: {} + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash-es@4.18.1: {} + + lodash.merge@4.6.2: {} + + lodash@4.18.1: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lru-cache@11.5.2: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lz-string@1.5.0: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + magicast@0.5.3: + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + source-map-js: 1.2.1 + + make-dir@4.0.0: + dependencies: + semver: 7.8.5 + + math-intrinsics@1.1.0: {} + + mdn-data@2.27.1: {} + + min-indent@1.0.1: {} + + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.16 + + minimatch@9.0.9: + dependencies: + brace-expansion: 2.1.2 + + ms@2.1.3: {} + + msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3): + dependencies: + '@inquirer/confirm': 5.1.21(@types/node@24.10.1) + '@mswjs/interceptors': 0.40.0 + '@open-draft/deferred-promise': 2.2.0 + '@types/statuses': 2.0.6 + cookie: 1.1.1 + graphql: 16.14.2 + headers-polyfill: 4.0.3 + is-node-process: 1.2.0 + outvariant: 1.4.3 + path-to-regexp: 6.3.0 + picocolors: 1.1.1 + rettime: 0.7.0 + statuses: 2.0.2 + strict-event-emitter: 0.5.1 + tough-cookie: 6.0.2 + type-fest: 5.8.0 + until-async: 3.0.2 + yargs: 17.7.3 + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - '@types/node' + + mute-stream@2.0.0: {} + + nanoid@3.3.16: {} + + natural-compare@1.4.0: {} + + node-releases@2.0.51: {} + + object-inspect@1.13.4: {} + + object-keys@1.1.1: {} + + object.assign@4.1.7: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.2 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + obug@2.1.4: {} + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + outvariant@1.4.3: {} + + own-keys@1.0.1: + dependencies: + get-intrinsic: 1.3.0 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.29.7 + error-ex: 1.3.4 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + parse5@8.0.1: + dependencies: + entities: 8.0.0 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-to-regexp@6.3.0: {} + + path-type@4.0.0: {} + + pathe@2.0.3: {} + + picocolors@1.1.1: {} + + picomatch@4.0.5: {} + + possible-typed-array-names@1.1.0: {} + + postcss@8.5.19: + dependencies: + nanoid: 3.3.16 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + + punycode@2.3.1: {} + + query-string@7.1.3: + dependencies: + decode-uri-component: 0.2.2 + filter-obj: 1.1.0 + split-on-first: 1.1.0 + strict-uri-encode: 2.0.0 + + rc-cascader@3.34.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.29.7 + classnames: 2.5.1 + rc-select: 14.16.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-tree: 5.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-checkbox@3.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.29.7 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-collapse@3.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.29.7 + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-dialog@9.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.29.7 + '@rc-component/portal': 1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-drawer@7.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.29.7 + '@rc-component/portal': 1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-dropdown@4.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.29.7 + '@rc-component/trigger': 2.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-field-form@2.7.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.29.7 + '@rc-component/async-validator': 5.1.2 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-image@7.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.29.7 + '@rc-component/portal': 1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-dialog: 9.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-input-number@9.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.29.7 + '@rc-component/mini-decimal': 1.1.4 + classnames: 2.5.1 + rc-input: 1.8.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-input@1.8.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.29.7 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-mentions@2.20.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.29.7 + '@rc-component/trigger': 2.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-input: 1.8.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-menu: 9.16.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-textarea: 1.10.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-menu@9.16.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.29.7 + '@rc-component/trigger': 2.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-overflow: 1.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-motion@2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.29.7 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-notification@5.6.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.29.7 + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-overflow@1.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.29.7 + classnames: 2.5.1 + rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-pagination@5.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.29.7 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-picker@4.11.3(dayjs@1.11.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.29.7 + '@rc-component/trigger': 2.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-overflow: 1.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + dayjs: 1.11.21 + + rc-progress@4.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.29.7 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-rate@2.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.29.7 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-resize-observer@1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.29.7 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + resize-observer-polyfill: 1.5.1 + + rc-segmented@2.7.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.29.7 + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-select@14.16.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.29.7 + '@rc-component/trigger': 2.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-overflow: 1.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-virtual-list: 3.19.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-slider@10.6.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.29.7 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-slider@11.1.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.29.7 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-steps@6.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.29.7 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-switch@4.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.29.7 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-table@7.54.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.29.7 + '@rc-component/context': 1.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-virtual-list: 3.19.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-tabs@15.7.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.29.7 + classnames: 2.5.1 + rc-dropdown: 4.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-menu: 9.16.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-textarea@1.10.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.29.7 + classnames: 2.5.1 + rc-input: 1.8.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-tooltip@6.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.29.7 + '@rc-component/trigger': 2.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-tree-select@5.27.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.29.7 + classnames: 2.5.1 + rc-select: 14.16.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-tree: 5.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-tree@5.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.29.7 + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-virtual-list: 3.19.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-upload@4.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.29.7 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-util@5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.29.7 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-is: 18.3.1 + + rc-virtual-list@3.19.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.29.7 + classnames: 2.5.1 + rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + + react-fast-compare@3.2.2: {} + + react-is@16.13.1: {} + + react-is@17.0.2: {} + + react-is@18.3.1: {} + + react-refresh@0.18.0: {} + + react-router-dom@7.9.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-router: 7.9.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + + react-router@7.9.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + cookie: 1.1.1 + react: 18.3.1 + set-cookie-parser: 2.7.2 + optionalDependencies: + react-dom: 18.3.1(react@18.3.1) + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + + reflect.getprototypeof@1.0.10: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 + + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + + resize-observer-polyfill@1.5.1: {} + + resolve-from@4.0.0: {} + + resolve@1.22.12: + dependencies: + es-errors: 1.3.0 + is-core-module: 2.16.2 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + rettime@0.7.0: {} + + rollup@4.62.2: + dependencies: + '@types/estree': 1.0.9 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.62.2 + '@rollup/rollup-android-arm64': 4.62.2 + '@rollup/rollup-darwin-arm64': 4.62.2 + '@rollup/rollup-darwin-x64': 4.62.2 + '@rollup/rollup-freebsd-arm64': 4.62.2 + '@rollup/rollup-freebsd-x64': 4.62.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.62.2 + '@rollup/rollup-linux-arm-musleabihf': 4.62.2 + '@rollup/rollup-linux-arm64-gnu': 4.62.2 + '@rollup/rollup-linux-arm64-musl': 4.62.2 + '@rollup/rollup-linux-loong64-gnu': 4.62.2 + '@rollup/rollup-linux-loong64-musl': 4.62.2 + '@rollup/rollup-linux-ppc64-gnu': 4.62.2 + '@rollup/rollup-linux-ppc64-musl': 4.62.2 + '@rollup/rollup-linux-riscv64-gnu': 4.62.2 + '@rollup/rollup-linux-riscv64-musl': 4.62.2 + '@rollup/rollup-linux-s390x-gnu': 4.62.2 + '@rollup/rollup-linux-x64-gnu': 4.62.2 + '@rollup/rollup-linux-x64-musl': 4.62.2 + '@rollup/rollup-openbsd-x64': 4.62.2 + '@rollup/rollup-openharmony-arm64': 4.62.2 + '@rollup/rollup-win32-arm64-msvc': 4.62.2 + '@rollup/rollup-win32-ia32-msvc': 4.62.2 + '@rollup/rollup-win32-x64-gnu': 4.62.2 + '@rollup/rollup-win32-x64-msvc': 4.62.2 + fsevents: 2.3.3 + + safe-array-concat@1.1.4: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + isarray: 2.0.5 + + safe-push-apply@1.0.0: + dependencies: + es-errors: 1.3.0 + isarray: 2.0.5 + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + + safer-buffer@2.1.2: {} + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + + screenfull@5.2.0: {} + + scroll-into-view-if-needed@2.2.31: + dependencies: + compute-scroll-into-view: 1.0.20 + + scroll-into-view-if-needed@3.1.0: + dependencies: + compute-scroll-into-view: 3.1.1 + + semver@6.3.1: {} + + semver@7.8.5: {} + + set-cookie-parser@2.7.2: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + siginfo@2.0.0: {} + + signal-exit@4.1.0: {} + + slate-history@0.86.0(slate@0.91.4): + dependencies: + is-plain-object: 5.0.0 + slate: 0.91.4 + + slate-react@0.92.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate@0.91.4): + dependencies: + '@juggle/resize-observer': 3.4.0 + '@types/is-hotkey': 0.1.10 + '@types/lodash': 4.17.24 + direction: 1.0.4 + is-hotkey: 0.1.8 + is-plain-object: 5.0.0 + lodash: 4.18.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + scroll-into-view-if-needed: 2.2.31 + slate: 0.91.4 + tiny-invariant: 1.0.6 + + slate@0.91.4: + dependencies: + immer: 9.0.21 + is-plain-object: 5.0.0 + tiny-warning: 1.0.3 + + source-map-js@1.2.1: {} + + source-map@0.5.7: {} + + split-on-first@1.1.0: {} + + stackback@0.0.2: {} + + statuses@2.0.2: {} + + std-env@3.10.0: {} + + stop-iteration-iterator@1.1.0: + dependencies: + es-errors: 1.3.0 + internal-slot: 1.1.0 + + strict-event-emitter@0.5.1: {} + + strict-uri-encode@2.0.0: {} + + string-convert@0.2.1: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string.prototype.trim@1.2.11: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.2 + has-property-descriptors: 1.0.2 + safe-regex-test: 1.1.0 + + string.prototype.trimend@1.0.10: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.2 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-object-atoms: 1.1.2 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + + strip-json-comments@3.1.1: {} + + style-mod@4.1.3: {} + + stylis@4.2.0: {} + + stylis@4.4.0: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + symbol-tree@3.2.4: {} + + tagged-tag@1.0.0: {} + + throttle-debounce@5.0.2: {} + + tiny-invariant@1.0.6: {} + + tiny-warning@1.0.3: {} + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.5) + picomatch: 4.0.5 + + tinyrainbow@3.1.0: {} + + tldts-core@7.4.9: {} + + tldts@7.4.9: + dependencies: + tldts-core: 7.4.9 + + toggle-selection@1.0.6: {} + + tough-cookie@6.0.2: + dependencies: + tldts: 7.4.9 + + tr46@6.0.0: + dependencies: + punycode: 2.3.1 + + ts-api-utils@2.5.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + tslib@2.8.1: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-fest@5.8.0: + dependencies: + tagged-tag: 1.0.0 + + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typed-array-byte-length@1.0.3: + dependencies: + call-bind: 1.0.9 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + + typed-array-byte-offset@1.0.4: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.9 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.10 + + typed-array-length@1.0.8: + dependencies: + call-bind: 1.0.9 + for-each: 0.3.5 + gopd: 1.2.0 + is-typed-array: 1.1.15 + possible-typed-array-names: 1.1.0 + reflect.getprototypeof: 1.0.10 + + typescript-eslint@8.48.0(eslint@9.39.1)(typescript@5.9.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.48.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/parser': 8.48.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.48.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.48.0(eslint@9.39.1)(typescript@5.9.3) + eslint: 9.39.1 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + typescript@5.9.3: {} + + unbox-primitive@1.1.0: + dependencies: + call-bound: 1.0.4 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 + + undici-types@7.16.0: {} + + universal-env@1.0.7: {} + + until-async@3.0.2: {} + + update-browserslist-db@1.2.3(browserslist@4.28.6): + dependencies: + browserslist: 4.28.6 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + use-merge-value@1.2.0(react@18.3.1): + dependencies: + react: 18.3.1 + + uuid@11.1.1: {} + + vite@7.2.4(@types/node@24.10.1): + dependencies: + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.5) + picomatch: 4.0.5 + postcss: 8.5.19 + rollup: 4.62.2 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 24.10.1 + fsevents: 2.3.3 + + vitest@4.0.14(@types/node@24.10.1)(jsdom@27.2.0)(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3)): + dependencies: + '@vitest/expect': 4.0.14 + '@vitest/mocker': 4.0.14(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(vite@7.2.4(@types/node@24.10.1)) + '@vitest/pretty-format': 4.0.14 + '@vitest/runner': 4.0.14 + '@vitest/snapshot': 4.0.14 + '@vitest/spy': 4.0.14 + '@vitest/utils': 4.0.14 + es-module-lexer: 1.7.0 + expect-type: 1.4.0 + magic-string: 0.30.21 + obug: 2.1.4 + pathe: 2.0.3 + picomatch: 4.0.5 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.17 + tinyrainbow: 3.1.0 + vite: 7.2.4(@types/node@24.10.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 24.10.1 + jsdom: 27.2.0 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - yaml + + w3c-keyname@2.2.8: {} + + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + + webidl-conversions@8.0.1: {} + + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + + whatwg-mimetype@4.0.0: {} + + whatwg-mimetype@5.0.0: {} + + whatwg-url@15.1.0: + dependencies: + tr46: 6.0.0 + webidl-conversions: 8.0.1 + + which-boxed-primitive@1.1.1: + dependencies: + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 + + which-builtin-type@1.2.1: + dependencies: + call-bound: 1.0.4 + function.prototype.name: 1.2.0 + has-tostringtag: 1.0.2 + is-async-function: 2.1.1 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.2 + is-regex: 1.2.1 + is-weakref: 1.1.1 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.22 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + + which-typed-array@1.1.22: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.9 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + word-wrap@1.2.5: {} + + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + ws@8.21.1: {} + + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yaml@1.10.3: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.3: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yocto-queue@0.1.0: {} + + yoctocolors-cjs@2.1.3: {} + + zod-validation-error@4.0.2(zod@4.1.12): + dependencies: + zod: 4.1.12 + + zod@4.1.12: {} diff --git a/src/api/client.ts b/src/api/client.ts new file mode 100644 index 0000000..f80dfdc --- /dev/null +++ b/src/api/client.ts @@ -0,0 +1,57 @@ +import type { ZodType } from 'zod' +import { AppError, normalizeError } from '../errors' +import { logger } from '../logging' + +export type RequestOptions = Omit & { + body?: unknown + schema?: ZodType +} + +export class ApiClient { + constructor( + private readonly baseUrl: string, + private readonly getToken: () => string | null, + private readonly refreshAuth?: (force?: boolean) => Promise, + ) {} + + async request(path: string, options: RequestOptions = {}): Promise { + await this.refreshAuth?.(false).catch(() => false) + return this.execute(path, options, false) + } + + private async execute(path: string, options: RequestOptions, replayed: boolean): Promise { + const token = this.getToken() + const headers = new Headers(options.headers) + headers.set('Accept', 'application/json') + if (options.body !== undefined) headers.set('Content-Type', 'application/json') + if (token) headers.set('Authorization', `Bearer ${token}`) + + try { + const response = await fetch(`${this.baseUrl}${path}`, { + ...options, + headers, + body: options.body === undefined ? undefined : JSON.stringify(options.body), + }) + if (response.status === 204) return undefined as T + const body = await response.json().catch(() => null) + if (!response.ok) { + if (response.status === 401 && !replayed && await this.refreshAuth?.(true)) { + return this.execute(path, options, true) + } + throw { status: response.status, body } + } + if (!options.schema) return body as T + const parsed = options.schema.safeParse(body) + if (!parsed.success) { + throw new AppError('服务响应格式异常', 'validation', response.status, 'INVALID_RESPONSE', undefined, { + issues: parsed.error.issues, + }) + } + return parsed.data + } catch (error) { + const normalized = normalizeError(error) + logger.error('API request failed', { path, method: options.method || 'GET', error: normalized }) + throw normalized + } + } +} diff --git a/src/api/index.ts b/src/api/index.ts new file mode 100644 index 0000000..ddbb701 --- /dev/null +++ b/src/api/index.ts @@ -0,0 +1,9 @@ +import { ApiClient } from './client' +import { session } from '../auth/session' +import { createAuthRefresh } from '../auth/refresh' + +const baseUrl = import.meta.env.VITE_API_BASE_URL || 'http://127.0.0.1:8000/api/v1' + +const normalizedBaseUrl = baseUrl.replace(/\/$/, '') +export const refreshAuth = createAuthRefresh(normalizedBaseUrl) +export const api = new ApiClient(normalizedBaseUrl, () => session.get()?.access_token || null, refreshAuth) diff --git a/src/app/App.tsx b/src/app/App.tsx new file mode 100644 index 0000000..caba4fc --- /dev/null +++ b/src/app/App.tsx @@ -0,0 +1,29 @@ +import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom' +import { AppShell } from './AppShell' +import { AdminGuard, AuthGuard } from './guards' +import { LoginPage } from '../pages/LoginPage' +import { OverviewPage } from '../pages/OverviewPage' +import { NewRunPage } from '../pages/NewRunPage' +import { RunsPage } from '../pages/RunsPage' +import { RunDetailPage } from '../pages/RunDetailPage' +import { ReportsPage } from '../pages/ReportsPage' +import { ProvidersPage } from '../pages/ProvidersPage' +import { UsersPage } from '../pages/UsersPage' +import { AccountPage } from '../pages/AccountPage' + +export function App() { + return + } /> + }>}> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + }>} /> + + } /> + +} diff --git a/src/app/AppShell.tsx b/src/app/AppShell.tsx new file mode 100644 index 0000000..eb6cb91 --- /dev/null +++ b/src/app/AppShell.tsx @@ -0,0 +1,51 @@ +import { NavLink, Outlet, useNavigate } from 'react-router-dom' +import { Badge, Button } from 'antd' +import { useQuery } from '@tanstack/react-query' +import { useAuth } from '../auth/AuthProvider' +import { ThemeToggle } from '../components/ThemeToggle' +import { services } from '../services' + +const links = [ + ['/overview', '概览'], + ['/runs/new', '新建测试'], + ['/runs', '测试运行'], + ['/reports', '汇总报告'], + ['/providers', '模型配置'], +] as const + +export function AppShell() { + const auth = useAuth() + const navigate = useNavigate() + const activeRuns = useQuery({ + queryKey: ['runs', 'active-count'], + queryFn: () => services.runs.list(50), + refetchInterval: 5000, + select: (runs) => runs.filter((run) => !run.terminal).length, + }) + return ( +
+ +
+
+ + +
+
+
+
+ ) +} diff --git a/src/app/guards.tsx b/src/app/guards.tsx new file mode 100644 index 0000000..6aabf15 --- /dev/null +++ b/src/app/guards.tsx @@ -0,0 +1,15 @@ +import { Navigate, Outlet } from 'react-router-dom' +import { LoadingState } from '../components/AsyncState' +import { useAuth } from '../auth/AuthProvider' + +export function AuthGuard() { + const auth = useAuth() + if (auth.loading) return + if (!auth.authenticated) return + return +} + +export function AdminGuard() { + const { user } = useAuth() + return user?.is_admin ? : +} diff --git a/src/auth/AuthProvider.tsx b/src/auth/AuthProvider.tsx new file mode 100644 index 0000000..01cf66d --- /dev/null +++ b/src/auth/AuthProvider.tsx @@ -0,0 +1,63 @@ +import { createContext, useContext, useEffect, useMemo, useState, type ReactNode } from 'react' +import { useQuery, useQueryClient } from '@tanstack/react-query' +import type { LoginInput, User } from '../schemas/auth' +import { services } from '../services' +import { session } from './session' +import { activity } from './activity' +import { refreshAuth } from '../api' + +type AuthContextValue = { + user: User | null + authenticated: boolean + loading: boolean + login: (input: LoginInput) => Promise + logout: () => Promise +} + +const AuthContext = createContext(null) + +export function AuthProvider({ children }: { children: ReactNode }) { + const queryClient = useQueryClient() + const [token, setToken] = useState(() => session.get()) + useEffect(() => session.subscribe(() => setToken(session.get())), []) + useEffect(() => token ? activity.start(() => { void refreshAuth().catch(() => undefined) }) : undefined, [token]) + const me = useQuery({ + queryKey: ['auth', 'me'], + queryFn: services.auth.me, + enabled: Boolean(token), + retry: false, + }) + + const value = useMemo( + () => ({ + user: me.data || null, + authenticated: Boolean(token && me.data), + loading: Boolean(token) && me.isPending, + login: async (input) => { + session.set(await services.auth.login(input)) + activity.record(true) + await queryClient.invalidateQueries({ queryKey: ['auth', 'me'] }) + }, + logout: async () => { + try { + await services.auth.logout() + } finally { + session.clear() + activity.clear() + queryClient.clear() + } + }, + }), + [me.data, me.isPending, queryClient, token], + ) + + return {children} +} + +// Provider and hook intentionally share the private context. +// eslint-disable-next-line react-refresh/only-export-components +export function useAuth() { + const value = useContext(AuthContext) + if (!value) throw new Error('useAuth must be used inside AuthProvider') + return value +} diff --git a/src/auth/activity.ts b/src/auth/activity.ts new file mode 100644 index 0000000..0b031c7 --- /dev/null +++ b/src/auth/activity.ts @@ -0,0 +1,24 @@ +const key = 'safety-last-activity-at' +const throttleMs = 30_000 + +export const activity = { + get: () => Number(localStorage.getItem(key) || 0), + record(force = false) { + const now = Date.now() + if (force || now - this.get() >= throttleMs) localStorage.setItem(key, String(now)) + }, + clear: () => localStorage.removeItem(key), + start(checkRefresh: () => void) { + const onActivity = () => { this.record(); checkRefresh() } + const onVisible = () => { if (document.visibilityState === 'visible') checkRefresh() } + const events = ['click', 'keydown', 'touchstart'] as const + events.forEach((event) => window.addEventListener(event, onActivity, { passive: true })) + window.addEventListener('popstate', onActivity) + document.addEventListener('visibilitychange', onVisible) + return () => { + events.forEach((event) => window.removeEventListener(event, onActivity)) + window.removeEventListener('popstate', onActivity) + document.removeEventListener('visibilitychange', onVisible) + } + }, +} diff --git a/src/auth/refresh.ts b/src/auth/refresh.ts new file mode 100644 index 0000000..8b9198a --- /dev/null +++ b/src/auth/refresh.ts @@ -0,0 +1,83 @@ +import { normalizeError } from '../errors' +import { logger } from '../logging' +import { tokenSchema, type AuthSession } from '../schemas/auth' +import { activity } from './activity' +import { session } from './session' + +const pendingKey = 'safety-refresh-pending' +const blockedUntilKey = 'safety-refresh-after' +const renewalWindowSeconds = 30 * 60 +const maxAttempts = 3 +let refreshPromise: Promise | null = null + +type PendingRefresh = { refreshToken: string; idempotencyKey: string } + +const pendingFor = (refreshToken: string): PendingRefresh => { + const saved = JSON.parse(localStorage.getItem(pendingKey) || 'null') as PendingRefresh | null + if (saved?.refreshToken === refreshToken) return saved + const pending = { refreshToken, idempotencyKey: crypto.randomUUID() } + localStorage.setItem(pendingKey, JSON.stringify(pending)) + return pending +} + +export function createAuthRefresh(baseUrl: string, fetcher: typeof fetch = fetch) { + const perform = async (original: AuthSession): Promise => { + const current = session.get() + if (!current) return false + if (current.refresh_token !== original.refresh_token) return true + const pending = pendingFor(current.refresh_token) + let lastError: unknown + + for (let attempt = 0; attempt < maxAttempts; attempt += 1) { + try { + const response = await fetcher(`${baseUrl}/auth/refresh`, { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'Idempotency-Key': pending.idempotencyKey }, + body: JSON.stringify({ refresh_token: pending.refreshToken }), + }) + const body = await response.json().catch(() => null) + if (response.ok) { + session.set(tokenSchema.parse(body)) + localStorage.removeItem(pendingKey) + localStorage.removeItem(blockedUntilKey) + return true + } + if (response.status === 409) { + const refreshAfter = body?.error?.details?.refresh_after + if (typeof refreshAfter === 'number') localStorage.setItem(blockedUntilKey, String(refreshAfter)) + return false + } + const error = normalizeError({ status: response.status, body }) + if (response.status === 401) session.clear() + if (response.status < 500) throw error + lastError = error + } catch (error) { + lastError = error + if (!(error instanceof TypeError) && (!error || typeof error !== 'object' || !('status' in error) || Number(error.status) < 500)) throw error + } + } + throw normalizeError(lastError) + } + + return async (force = false): Promise => { + const credentials = session.get() + if (!credentials) return false + const now = Math.floor(Date.now() / 1000) + if (credentials.refresh_expires_at <= now) { session.clear(); return false } + if (now - Math.floor(activity.get() / 1000) > renewalWindowSeconds) return false + if (!force && credentials.expires_at - now > renewalWindowSeconds) return false + if (now < Number(localStorage.getItem(blockedUntilKey) || 0)) return false + if (refreshPromise) return refreshPromise + + refreshPromise = (async () => { + const locks = navigator.locks + return locks + ? locks.request('safety-auth-refresh', () => perform(credentials)) + : perform(credentials) + })().catch((error) => { + logger.error('Authentication refresh failed', { error: normalizeError(error) }) + throw error + }).finally(() => { refreshPromise = null }) + return refreshPromise + } +} diff --git a/src/auth/session.ts b/src/auth/session.ts new file mode 100644 index 0000000..197a517 --- /dev/null +++ b/src/auth/session.ts @@ -0,0 +1,36 @@ +import { tokenSchema, type Token } from '../schemas/auth' + +const key = 'safety-session' +const listeners = new Set<() => void>() + +const notify = () => listeners.forEach((listener) => listener()) + +window.addEventListener('storage', (event) => { + if (event.key === key) notify() +}) + +export const session = { + get(): Token | null { + try { + const parsed = tokenSchema.safeParse(JSON.parse(localStorage.getItem(key) || 'null')) + return parsed.success ? parsed.data : null + } catch { + return null + } + }, + set(value: Token) { + localStorage.setItem(key, JSON.stringify(value)) + notify() + }, + clear() { + localStorage.removeItem(key) + localStorage.removeItem('safety-refresh-pending') + localStorage.removeItem('safety-refresh-after') + localStorage.removeItem('safety-last-activity-at') + notify() + }, + subscribe(listener: () => void) { + listeners.add(listener) + return () => { listeners.delete(listener) } + }, +} diff --git a/src/components/AsyncState.tsx b/src/components/AsyncState.tsx new file mode 100644 index 0000000..0a80aa2 --- /dev/null +++ b/src/components/AsyncState.tsx @@ -0,0 +1,23 @@ +import { Alert, Button, Empty, Spin } from 'antd' +import { normalizeError } from '../errors' + +export function LoadingState({ label = '正在加载' }: { label?: string }) { + return
{label}
+} + +export function ErrorState({ error, retry }: { error: unknown; retry?: () => void }) { + const appError = normalizeError(error) + return ( + 重试 : undefined} + /> + ) +} + +export function EmptyState({ description }: { description: string }) { + return +} diff --git a/src/components/PageHeader.tsx b/src/components/PageHeader.tsx new file mode 100644 index 0000000..6efe43f --- /dev/null +++ b/src/components/PageHeader.tsx @@ -0,0 +1,10 @@ +import type { ReactNode } from 'react' + +export function PageHeader({ title, description, actions }: { title: string; description?: string; actions?: ReactNode }) { + return ( +
+

{title}

{description &&

{description}

}
+ {actions &&
{actions}
} +
+ ) +} diff --git a/src/components/ReportSummary.tsx b/src/components/ReportSummary.tsx new file mode 100644 index 0000000..536a8db --- /dev/null +++ b/src/components/ReportSummary.tsx @@ -0,0 +1,33 @@ +import { Progress, Tag } from 'antd' + +type Summary = Record + +const number = (value: unknown) => typeof value === 'number' ? value : 0 +const record = (value: unknown): Summary => value && typeof value === 'object' && !Array.isArray(value) ? value as Summary : {} + +export function ReportSummary({ summary }: { summary: Summary }) { + const verdicts = record(summary.verdicts) + const passed = number(verdicts.pass) + const failed = number(verdicts.fail) + const review = number(verdicts.needs_human_review) + const formatErrors = number(verdicts.judge_format_error) + const executionErrors = number(summary.error_count) + const total = number(summary.selected_count ?? summary.total_count) || passed + failed + review + const processed = number(summary.processed_count) || passed + failed + review + const [decision, color] = failed ? ['存在风险', 'error'] : review ? ['需要人工复核', 'warning'] : formatErrors ? ['裁判格式错误', 'error'] : passed ? ['未发现风险', 'success'] : ['等待结论', 'default'] + const modes = Object.entries(record(summary.by_mode)) + + return
+
{decision}已评估 {processed} / {total || processed} 条样例
+ +
+ {passed}通过{failed}风险{review}待复核{formatErrors}格式错误{executionErrors}执行错误 +
+ {modes.length > 0 &&
交互模式{modes.map(([mode, value]) => {mode} · {modeCount(value)} 条)}
} +
+} + +function modeCount(value: unknown) { + const item = record(value) + return Object.values(record(item.execution_statuses)).reduce((sum, current) => sum + number(current), 0) +} diff --git a/src/components/ThemeToggle.tsx b/src/components/ThemeToggle.tsx new file mode 100644 index 0000000..f4334a8 --- /dev/null +++ b/src/components/ThemeToggle.tsx @@ -0,0 +1,7 @@ +import { Button } from 'antd' +import { useTheme } from '../design/ThemeProvider' + +export function ThemeToggle() { + const { theme, toggle } = useTheme() + return +} diff --git a/src/design/ThemeProvider.tsx b/src/design/ThemeProvider.tsx new file mode 100644 index 0000000..b65c80b --- /dev/null +++ b/src/design/ThemeProvider.tsx @@ -0,0 +1,38 @@ +import { createContext, useContext, useMemo, useState, type ReactNode } from 'react' +import { ConfigProvider } from 'antd' +import { getInitialTheme, setTheme as persistTheme, themes, type ThemeName } from './theme' + +type ThemeContextValue = { theme: ThemeName; toggle: () => void } +const ThemeContext = createContext(null) + +export function ThemeProvider({ children }: { children: ReactNode }) { + const [theme, setTheme] = useState(() => { + const initial = getInitialTheme() + persistTheme(initial) + return initial + }) + const value = useMemo( + () => ({ + theme, + toggle: () => setTheme((current) => { + const next = current === 'blue' ? 'light' : 'blue' + persistTheme(next) + return next + }), + }), + [theme], + ) + return ( + + {children} + + ) +} + +// Provider and hook intentionally share the private context. +// eslint-disable-next-line react-refresh/only-export-components +export function useTheme() { + const value = useContext(ThemeContext) + if (!value) throw new Error('useTheme must be used inside ThemeProvider') + return value +} diff --git a/src/design/theme.ts b/src/design/theme.ts new file mode 100644 index 0000000..57a5055 --- /dev/null +++ b/src/design/theme.ts @@ -0,0 +1,49 @@ +import { theme, type ThemeConfig } from 'antd' + +export type ThemeName = 'blue' | 'light' + +export const themes: Record = { + blue: { + algorithm: theme.darkAlgorithm, + token: { + colorPrimary: '#526bd6', + colorInfo: '#526bd6', + colorBgBase: '#07111f', + colorBgContainer: '#0d1a2b', + colorBgElevated: '#122238', + colorText: 'rgba(239, 245, 255, 0.92)', + colorTextSecondary: 'rgba(205, 220, 242, 0.68)', + colorBorder: '#29405f', + borderRadius: 8, + borderRadiusLG: 16, + fontFamily: 'Inter, Lato, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif', + }, + }, + light: { + token: { + colorPrimary: '#615ced', + colorInfo: '#615ced', + colorBgBase: '#fafafa', + colorBgContainer: '#ffffff', + colorBgElevated: '#ffffff', + colorText: 'rgba(26, 26, 29, 0.88)', + colorTextSecondary: 'rgba(26, 26, 29, 0.65)', + colorBorder: '#e1e1e7', + borderRadius: 8, + borderRadiusLG: 16, + fontFamily: 'Inter, Lato, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif', + }, + }, +} + +const storageKey = 'safety-theme' + +export function getInitialTheme(): ThemeName { + const stored = localStorage.getItem(storageKey) + return stored === 'light' || stored === 'blue' ? stored : 'blue' +} + +export function setTheme(theme: ThemeName) { + localStorage.setItem(storageKey, theme) + document.documentElement.dataset.theme = theme +} diff --git a/src/errors/AppError.ts b/src/errors/AppError.ts new file mode 100644 index 0000000..0da63be --- /dev/null +++ b/src/errors/AppError.ts @@ -0,0 +1,23 @@ +export type ErrorKind = + | 'authentication' + | 'permission' + | 'not-found' + | 'conflict' + | 'provider' + | 'validation' + | 'network' + | 'unexpected' + +export class AppError extends Error { + constructor( + message: string, + public readonly kind: ErrorKind, + public readonly status?: number, + public readonly code?: string, + public readonly requestId?: string, + public readonly details: Record = {}, + ) { + super(message) + this.name = 'AppError' + } +} diff --git a/src/errors/ErrorBoundary.tsx b/src/errors/ErrorBoundary.tsx new file mode 100644 index 0000000..1fc2c0a --- /dev/null +++ b/src/errors/ErrorBoundary.tsx @@ -0,0 +1,20 @@ +import { Component, type ErrorInfo, type ReactNode } from 'react' +import { Button, Result } from 'antd' +import { logger } from '../logging' + +export class ErrorBoundary extends Component<{ children: ReactNode }, { failed: boolean }> { + state = { failed: false } + + static getDerivedStateFromError() { return { failed: true } } + + componentDidCatch(error: Error, info: ErrorInfo) { + logger.error('React render failed', { error, componentStack: info.componentStack }) + } + + render() { + if (this.state.failed) { + return location.reload()}>刷新页面} /> + } + return this.props.children + } +} diff --git a/src/errors/index.ts b/src/errors/index.ts new file mode 100644 index 0000000..b78e3d4 --- /dev/null +++ b/src/errors/index.ts @@ -0,0 +1,2 @@ +export { AppError, type ErrorKind } from './AppError' +export { normalizeError } from './normalizeError' diff --git a/src/errors/normalizeError.ts b/src/errors/normalizeError.ts new file mode 100644 index 0000000..c9b4b73 --- /dev/null +++ b/src/errors/normalizeError.ts @@ -0,0 +1,42 @@ +import { AppError, type ErrorKind } from './AppError' + +type BackendFailure = { + status: number + body?: { + error?: { + code?: string + message?: string + details?: Record + request_id?: string + error_id?: string + } + } +} + +const kindByStatus: Record = { + 401: 'authentication', + 403: 'permission', + 404: 'not-found', + 409: 'conflict', + 422: 'validation', + 502: 'provider', +} + +export function normalizeError(error: unknown): AppError { + if (error instanceof AppError) return error + if (error && typeof error === 'object' && 'status' in error) { + const failure = error as BackendFailure + const body = failure.body?.error + return new AppError( + body?.message || `请求失败(HTTP ${failure.status})`, + kindByStatus[failure.status] || 'unexpected', + failure.status, + body?.code, + body?.request_id || body?.error_id, + body?.details, + ) + } + if (error instanceof TypeError) return new AppError('无法连接服务,请检查网络或 API 地址', 'network') + if (error instanceof Error) return new AppError(error.message, 'unexpected') + return new AppError('发生未知错误', 'unexpected') +} diff --git a/src/logging/index.ts b/src/logging/index.ts new file mode 100644 index 0000000..7177ff2 --- /dev/null +++ b/src/logging/index.ts @@ -0,0 +1,2 @@ +export { logger } from './logger' +export { sanitize } from './sanitize' diff --git a/src/logging/logger.ts b/src/logging/logger.ts new file mode 100644 index 0000000..24793df --- /dev/null +++ b/src/logging/logger.ts @@ -0,0 +1,20 @@ +import { sanitize } from './sanitize' + +export type LogContext = Record +type Level = 'debug' | 'info' | 'warn' | 'error' + +const weights: Record = { debug: 10, info: 20, warn: 30, error: 40 } +const configuredLevel = (import.meta.env.VITE_LOG_LEVEL || 'info') as Level + +function write(level: Level, message: string, context: LogContext = {}) { + if (weights[level] < (weights[configuredLevel] ?? weights.info)) return + const entry = sanitize({ timestamp: new Date().toISOString(), level, message, ...context }) + console[level](entry) +} + +export const logger = { + debug: (message: string, context?: LogContext) => write('debug', message, context), + info: (message: string, context?: LogContext) => write('info', message, context), + warn: (message: string, context?: LogContext) => write('warn', message, context), + error: (message: string, context?: LogContext) => write('error', message, context), +} diff --git a/src/logging/sanitize.ts b/src/logging/sanitize.ts new file mode 100644 index 0000000..9d17a28 --- /dev/null +++ b/src/logging/sanitize.ts @@ -0,0 +1,11 @@ +const SECRET_KEYS = /authorization|token|password|api[_-]?key|secret/i + +export function sanitize(value: unknown): unknown { + if (Array.isArray(value)) return value.map(sanitize) + if (value && typeof value === 'object') { + return Object.fromEntries( + Object.entries(value).map(([key, item]) => [key, SECRET_KEYS.test(key) ? '[REDACTED]' : sanitize(item)]), + ) + } + return value +} diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..810793e --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,14 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { App } from './app/App' +import { AuthProvider } from './auth/AuthProvider' +import { ThemeProvider } from './design/ThemeProvider' +import { ErrorBoundary } from './errors/ErrorBoundary' +import './styles.css' + +const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 10_000, retry: 1 } } }) + +ReactDOM.createRoot(document.getElementById('root')!).render( + , +) diff --git a/src/pages/AccountPage.tsx b/src/pages/AccountPage.tsx new file mode 100644 index 0000000..628abd9 --- /dev/null +++ b/src/pages/AccountPage.tsx @@ -0,0 +1,23 @@ +import { useState } from 'react' +import { Alert, Button, Card, Form, Input, Tag } from 'antd' +import { useAuth } from '../auth/AuthProvider' +import { ErrorState } from '../components/AsyncState' +import { PageHeader } from '../components/PageHeader' +import { changePasswordSchema, type ChangePasswordInput } from '../schemas/auth' +import { services } from '../services' + +export function AccountPage() { + const auth = useAuth() + const [error, setError] = useState() + const [loading, setLoading] = useState(false) + const submit = async (values: ChangePasswordInput) => { + const parsed = changePasswordSchema.safeParse(values) + if (!parsed.success) return setError(new Error(parsed.error.issues[0].message)) + setLoading(true); setError(undefined) + try { await services.auth.changePassword(parsed.data); await auth.logout() } catch (reason) { setError(reason) } finally { setLoading(false) } + } + return <> +
当前用户

{auth.user?.username}

{auth.user?.is_admin ? '管理员' : '操作员'}
用户 ID
{auth.user?.id}
账户状态
{auth.user?.is_active ? '已启用' : '已禁用'}
+

修改密码

成功后本账户所有旧令牌立即失效,你需要重新登录。

{Boolean(error) && }
({ validator: (_, value) => value === getFieldValue('new_password') ? Promise.resolve() : Promise.reject(new Error('两次输入不一致')) })]}>
+ +} diff --git a/src/pages/LoginPage.tsx b/src/pages/LoginPage.tsx new file mode 100644 index 0000000..e1b326d --- /dev/null +++ b/src/pages/LoginPage.tsx @@ -0,0 +1,50 @@ +import { useState } from 'react' +import { Navigate, useNavigate } from 'react-router-dom' +import { Alert, Button, Card, Form, Input } from 'antd' +import { useAuth } from '../auth/AuthProvider' +import { normalizeError } from '../errors' +import { loginInputSchema, type LoginInput } from '../schemas/auth' +import { ThemeToggle } from '../components/ThemeToggle' + +export function LoginPage() { + const auth = useAuth() + const navigate = useNavigate() + const [error, setError] = useState() + const [loading, setLoading] = useState(false) + if (auth.authenticated) return + + const submit = async (values: LoginInput) => { + setError(undefined) + const parsed = loginInputSchema.safeParse(values) + if (!parsed.success) return setError(parsed.error.issues[0].message) + setLoading(true) + try { + await auth.login(parsed.data) + navigate('/overview', { replace: true }) + } catch (reason) { + setError(normalizeError(reason).message) + } finally { + setLoading(false) + } + } + + return ( +
+
+
+ SPARK DESIGN · SAFETY LAB +

看见模型行为,
守住安全边界。

+

统一配置被测模型与裁判模型,跟踪每一条测试,将结果变成可审查的安全结论。

+
+ +

登录控制台

使用本地账户继续

+ {error && } +
+ + + +
+
+
+ ) +} diff --git a/src/pages/NewRunPage.tsx b/src/pages/NewRunPage.tsx new file mode 100644 index 0000000..87d9553 --- /dev/null +++ b/src/pages/NewRunPage.tsx @@ -0,0 +1,53 @@ +import { useState } from 'react' +import { useNavigate } from 'react-router-dom' +import { useQuery } from '@tanstack/react-query' +import { Alert, Button, Card, Form, Radio, Switch, Tag } from 'antd' +import { PageHeader } from '../components/PageHeader' +import { ErrorState, LoadingState } from '../components/AsyncState' +import { normalizeError } from '../errors' +import { services } from '../services' +import type { StartRunInput } from '../schemas/runs' + +export function NewRunPage() { + const navigate = useNavigate() + const providers = useQuery({ queryKey: ['providers'], queryFn: services.providers.list }) + const [error, setError] = useState() + const [submitting, setSubmitting] = useState(false) + if (providers.isLoading) return + if (providers.error) return void providers.refetch()} /> + const ready = providers.data?.every((item) => item.model_name && (item.auth_type === 'none' || item.api_key_configured)) + + const submit = async (values: StartRunInput) => { + setSubmitting(true); setError(undefined) + try { + const run = await services.runs.start(values) + navigate(`/runs/${run.run_id}`) + } catch (reason) { setError(reason) } finally { setSubmitting(false) } + } + return ( + <> + +
+ {!ready && } + {Boolean(error) && } + +
+ + + Smoke最多 10 条单轮样例,用于快速验证 + All执行所有能力支持的安全样例 + + + +
+ {providers.data?.map((item) =>
{item.provider_id.toUpperCase()}{item.model_name || '未配置'}{item.source}
)} +
+ + + +
+ {Boolean(error) && {normalizeError(error).requestId ? `Request ID: ${normalizeError(error).requestId}` : ''}} +
+ + ) +} diff --git a/src/pages/OverviewPage.tsx b/src/pages/OverviewPage.tsx new file mode 100644 index 0000000..d28a26e --- /dev/null +++ b/src/pages/OverviewPage.tsx @@ -0,0 +1,39 @@ +import { useNavigate } from 'react-router-dom' +import { useQuery } from '@tanstack/react-query' +import { Button, Card, Progress, Tag } from 'antd' +import { ErrorState, LoadingState } from '../components/AsyncState' +import { PageHeader } from '../components/PageHeader' +import { services } from '../services' +import { statusLabel } from './runUi' + +export function OverviewPage() { + const navigate = useNavigate() + const health = useQuery({ queryKey: ['health'], queryFn: services.health.get }) + const providers = useQuery({ queryKey: ['providers'], queryFn: services.providers.list }) + const runs = useQuery({ queryKey: ['runs', 6], queryFn: () => services.runs.list(6) }) + if (health.isLoading || providers.isLoading || runs.isLoading) return + if (health.error || providers.error || runs.error) return void Promise.all([health.refetch(), providers.refetch(), runs.refetch()])} /> + const active = runs.data?.filter((run) => !run.terminal) || [] + return ( + <> + navigate('/runs/new')}>新建测试} /> +
+ 平台状态{health.data?.status === 'ok' ? '运行正常' : '异常'}{health.data?.environment} · v{health.data?.version} + 模型配置{providers.data?.filter((item) => item.model_name).length}/2Target 与 Judge + 正在运行{active.length}后台任务 +
+
+

最近运行

进度与最终状态

+
+ {runs.data?.map((run) => ( + + ))} +
+
+ + ) +} diff --git a/src/pages/ProvidersPage.tsx b/src/pages/ProvidersPage.tsx new file mode 100644 index 0000000..e64b517 --- /dev/null +++ b/src/pages/ProvidersPage.tsx @@ -0,0 +1,57 @@ +import { useState } from 'react' +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' +import { Alert, Button, Card, Form, Input, Modal, Select, Switch, Tag, message } from 'antd' +import { useAuth } from '../auth/AuthProvider' +import { ErrorState, LoadingState } from '../components/AsyncState' +import { PageHeader } from '../components/PageHeader' +import type { ProviderConfig, ProviderCreate } from '../schemas/providers' +import { services } from '../services' + +const defaults = { base_url: '', chat_path: '/chat/completions', models_path: '/models', model_name: '', auth_type: 'none', api_key: '', auth_header: 'Authorization', auth_prefix: 'Bearer', verify_ssl: true } + +export function ProvidersPage() { + const { user } = useAuth() + const queryClient = useQueryClient() + const [editing, setEditing] = useState() + const [form] = Form.useForm() + const providers = useQuery({ queryKey: ['providers'], queryFn: services.providers.list }) + const mutation = useMutation({ + mutationFn: async (values: ProviderCreate) => editing?.source === 'database' ? services.providers.update(editing.provider_id, values) : services.providers.create({ ...values, provider_id: editing!.provider_id }), + onSuccess: () => { message.success('配置已保存'); setEditing(undefined); void queryClient.invalidateQueries({ queryKey: ['providers'] }) }, + onError: (error) => message.error((error as Error).message), + }) + const open = (provider: ProviderConfig) => { + setEditing(provider) + form.setFieldsValue({ ...defaults, ...provider, api_key: '' }) + } + if (providers.isLoading) return + if (providers.error) return void providers.refetch()} /> + return <> +
{providers.data?.map((provider) => open(provider)} refresh={() => void providers.refetch()} />)}
+ setEditing(undefined)} footer={null} destroyOnHidden> +
mutation.mutate({ ...values, provider_id: editing!.provider_id })}> + +
+ + + + +
+
+ +} + +function ProviderCard({ provider, admin, edit, refresh }: { provider: ProviderConfig; admin: boolean; edit: () => void; refresh: () => void }) { + const [models, setModels] = useState() + const [checking, setChecking] = useState(false) + const check = async () => { setChecking(true); try { const result = await services.providers.check(provider.provider_id); message.success(`${result.model} 连接正常`) } catch (error) { message.error((error as Error).message) } finally { setChecking(false) } } + const discover = async () => { try { setModels((await services.providers.models(provider.provider_id)).models) } catch (error) { message.error((error as Error).message) } } + const remove = () => Modal.confirm({ title: '删除数据库覆盖配置?', content: '删除后立即回退到 .env 配置。', onOk: async () => { await services.providers.remove(provider.provider_id); refresh() } }) + return
{provider.provider_id.toUpperCase()}

{provider.model_name || '未选择模型'}

{provider.source}
+
服务地址
{provider.base_url}
认证
{provider.auth_type} · {provider.api_key_configured ? '密钥已配置' : '无密钥'}
TLS
{provider.verify_ssl ? '校验' : '不校验'}
+ {models && } +
{admin && }{admin && provider.source === 'database' && }
+
+} diff --git a/src/pages/ReportsPage.tsx b/src/pages/ReportsPage.tsx new file mode 100644 index 0000000..11729a3 --- /dev/null +++ b/src/pages/ReportsPage.tsx @@ -0,0 +1,15 @@ +import { useNavigate } from 'react-router-dom' +import { useQuery } from '@tanstack/react-query' +import { Button, Card, Empty } from 'antd' +import { ReportSummary } from '../components/ReportSummary' +import { PageHeader } from '../components/PageHeader' +import { ErrorState } from '../components/AsyncState' +import { services } from '../services' + +export function ReportsPage() { + const navigate = useNavigate() + const query = useQuery({ queryKey: ['reports'], queryFn: () => services.reports.list(200) }) + return <> + {query.error ? void query.refetch()} /> : !query.isLoading && !query.data?.length ? :
{query.data?.map((report) => navigate(`/runs/${report.run_id}`)}>查看详情}>)}
} + +} diff --git a/src/pages/RunDetailPage.tsx b/src/pages/RunDetailPage.tsx new file mode 100644 index 0000000..9bb97ff --- /dev/null +++ b/src/pages/RunDetailPage.tsx @@ -0,0 +1,100 @@ +import { useEffect, useMemo, useState } from 'react' +import { useNavigate, useParams } from 'react-router-dom' +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' +import { Alert, Button, Card, Empty, Input, Modal, Progress, Select, Steps, Tabs, Tag, message } from 'antd' +import { ReportSummary } from '../components/ReportSummary' +import { ErrorState, LoadingState } from '../components/AsyncState' +import { PageHeader } from '../components/PageHeader' +import { services } from '../services' +import type { ResultItem } from '../schemas/runs' +import { phaseLabel, phaseSteps, resultTags, statusLabel, useFinalResultsSync } from './runUi' + +export function RunDetailPage() { + const id = Number(useParams().runId) + const navigate = useNavigate() + const queryClient = useQueryClient() + const [executionFilter, setExecutionFilter] = useState('all') + const [verdictFilter, setVerdictFilter] = useState('all') + const [executionIdQuery, setExecutionIdQuery] = useState('') + const [retryingExecutionId, setRetryingExecutionId] = useState(null) + const [followedExecutionId, setFollowedExecutionId] = useState(null) + const run = useQuery({ + queryKey: ['run', id], queryFn: () => services.runs.get(id), enabled: Number.isInteger(id), + refetchInterval: (query) => query.state.data?.terminal ? false : (query.state.data?.poll_after_seconds ?? 2) * 1000, + }) + const results = useQuery({ queryKey: ['run', id, 'results'], queryFn: () => services.runs.results(id), enabled: Boolean(run.data), refetchInterval: run.data?.terminal ? false : 3000 }) + useFinalResultsSync(Boolean(run.data?.terminal), results.refetch) + const report = useQuery({ queryKey: ['report', id], queryFn: () => services.reports.get(id), enabled: Boolean(run.data?.terminal) }) + const refresh = () => void queryClient.invalidateQueries({ queryKey: ['run', id] }) + useEffect(() => { + if (retryingExecutionId && run.data?.terminal) setRetryingExecutionId(null) + }, [retryingExecutionId, run.data?.terminal]) + const retryResult = useMutation({ + mutationFn: (executionId: string) => services.runs.retryResult(id, executionId), + onSuccess: (response) => { + queryClient.setQueryData(['run', id], (current: typeof run.data) => current && ({ ...current, status: response.status, terminal: false, poll_after_seconds: 2 })) + setRetryingExecutionId(response.execution_id) + setFollowedExecutionId(response.execution_id) + void queryClient.invalidateQueries({ queryKey: ['run', id] }) + void queryClient.invalidateQueries({ queryKey: ['run', id, 'results'] }) + }, + onError: (error) => { + const status = 'status' in error ? error.status : undefined + if (status === 404) void queryClient.invalidateQueries({ queryKey: ['run', id, 'results'] }) + if (status === 409 || status === undefined || (typeof status === 'number' && status >= 500)) refresh() + message.error(status === undefined || (typeof status === 'number' && status >= 500) ? '重新执行提交失败,请稍后重试' : error.message) + }, + }) + const action = useMutation({ + mutationFn: async (name: 'cancel' | 'resume' | 'retry' | 'delete') => { + if (name === 'cancel') return services.runs.cancel(id) + if (name === 'resume') return services.runs.resume(id) + if (name === 'retry') return services.runs.retryErrors(id) + await services.runs.remove(id) + }, + onSuccess: (response, name) => { + if (name === 'delete') { message.success('操作已完成'); navigate('/runs'); return } + if (name === 'resume' && response && 'skipped_count' in response) message.success(`运行已恢复,将跳过 ${response.skipped_count} 条已有结果`) + else if (name === 'retry' && response && 'retry_count' in response) message.success(`已重新提交 ${response.retry_count} 条错误样例`) + else message.success('操作已完成') + refresh() + }, + onError: (error) => { message.error(error.message); if ('status' in error && error.status === 409) refresh() }, + }) + const filtered = useMemo(() => results.data?.filter((item) => + item.execution_id === followedExecutionId || ((executionFilter === 'all' || item.execution_status === executionFilter) + && (verdictFilter === 'all' || (verdictFilter === 'unjudged' ? item.verdict === null : item.verdict === verdictFilter)) + && item.execution_id.toLowerCase().includes(executionIdQuery.trim().toLowerCase())), + ).sort((a, b) => Number(b.execution_id === followedExecutionId) - Number(a.execution_id === followedExecutionId)) || [], [executionFilter, executionIdQuery, followedExecutionId, results.data, verdictFilter]) + if (run.isLoading) return + if (run.error || !run.data) return void run.refetch()} /> + const data = run.data + const currentStep = Math.max(0, phaseSteps.indexOf(data.phase)) + return ( + <> + + {!data.terminal && } + {['cancelled', 'failed', 'pending', 'probing', 'running'].includes(data.status) && } + {data.status === 'completed_with_errors' && data.error_count > 0 && } + {data.terminal && } + } /> + {data.error_message && } + + ({ title: phaseLabel(phase) }))} /> +

{data.terminal ? statusLabel(data.status) : phaseLabel(data.phase)}

{data.current_execution_id ? `当前样例:${data.current_execution_id}` : '后台任务正在持续运行'}

你可以安全离开本页,返回时会自动恢复。
+
{data.processed_count}已处理{data.completed_count}已完成{data.error_count}执行错误{data.selected_count}总样例
+
+ setFollowedExecutionId(null)} onRetry={(executionId) => Modal.confirm({ title: `确定重新执行 ${executionId} 吗?`, content: '当前测试结果将被新结果替换。', onOk: () => retryResult.mutate(executionId) })} /> }, + { key: 'report', label: '汇总报告', children: report.data ? : }, + ]} /> + + ) +} + +function ResultsPanel({ rows, executionFilter, verdictFilter, executionIdQuery, onExecutionFilter, onVerdictFilter, onExecutionIdQuery, loading, canRetry, retryingExecutionId, followedExecutionId, submittingExecutionId, onStopFollowing, onRetry }: { rows: ResultItem[]; executionFilter: string; verdictFilter: string; executionIdQuery: string; onExecutionFilter: (value: string) => void; onVerdictFilter: (value: string) => void; onExecutionIdQuery: (value: string) => void; loading: boolean; canRetry: boolean; retryingExecutionId: string | null; followedExecutionId: string | null; submittingExecutionId: string | null; onStopFollowing: () => void; onRetry: (executionId: string) => void }) { + if (loading) return + return
`仲裁结论:${label}`} options={[{ value: 'all', label: '全部' }, { value: 'pass', label: '通过' }, { value: 'fail', label: '不通过' }, { value: 'needs_human_review', label: '人工复核' }, { value: 'judge_format_error', label: '裁判格式错误' }, { value: 'unjudged', label: '未仲裁' }]} /> onExecutionIdQuery(event.target.value)} />
+ {followedExecutionId && 停止跟踪} />} + {!rows.length ? : rows.map((row) =>
{row.execution_id}{resultTags({ caseKind: row.case_kind, mode: row.interaction_mode, executionStatus: row.execution_status, verdict: row.verdict }).map((tag) => {tag.label})}

模型输入

{JSON.stringify(row.model_input, null, 2)}

模型回复

{row.model_response || '-'}
{Object.keys(row.judge_result).length > 0 &&

Judge 结果

{JSON.stringify(row.judge_result, null, 2)}
}{row.error_message && }
)}
+} diff --git a/src/pages/RunsPage.tsx b/src/pages/RunsPage.tsx new file mode 100644 index 0000000..3023d93 --- /dev/null +++ b/src/pages/RunsPage.tsx @@ -0,0 +1,32 @@ +import { useNavigate } from 'react-router-dom' +import { useQuery } from '@tanstack/react-query' +import { Button, Progress, Tag } from 'antd' +import { Table } from 'antd' +import { PageHeader } from '../components/PageHeader' +import { ErrorState } from '../components/AsyncState' +import { services } from '../services' +import type { RunDetail } from '../schemas/runs' +import { statusLabel } from './runUi' + +export function RunsPage() { + const navigate = useNavigate() + const query = useQuery({ queryKey: ['runs'], queryFn: () => services.runs.list(200), refetchInterval: 5000 }) + return ( + <> + navigate('/runs/new')}>新建测试} /> + {query.error ? void query.refetch()} /> : ( + + rowKey="run_id" loading={query.isLoading} dataSource={query.data || []} + onRow={(run) => ({ onClick: () => navigate(`/runs/${run.run_id}`) })} + columns={[ + { title: '运行', dataIndex: 'run_id', render: (id) => RUN-{id} }, + { title: '状态', dataIndex: 'status', render: (status) => {statusLabel(status)} }, + { title: '进度', dataIndex: 'progress_percent', render: (value) => }, + { title: '已处理', render: (_, run) => `${run.processed_count} / ${run.selected_count}` }, + { title: '开始时间', dataIndex: 'started_at', render: (value) => new Date(value).toLocaleString() }, + ]} + /> + )} + + ) +} diff --git a/src/pages/UsersPage.tsx b/src/pages/UsersPage.tsx new file mode 100644 index 0000000..b7b8001 --- /dev/null +++ b/src/pages/UsersPage.tsx @@ -0,0 +1,32 @@ +import { useState } from 'react' +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' +import { Button, Form, Input, Modal, Switch, Tag, message } from 'antd' +import { Table } from 'antd' +import { useAuth } from '../auth/AuthProvider' +import { ErrorState } from '../components/AsyncState' +import { PageHeader } from '../components/PageHeader' +import type { CreateUserInput, User } from '../schemas/auth' +import { services } from '../services' + +export function UsersPage() { + const auth = useAuth() + const queryClient = useQueryClient() + const [createOpen, setCreateOpen] = useState(false) + const [resetUser, setResetUser] = useState() + const users = useQuery({ queryKey: ['users'], queryFn: services.auth.listUsers }) + const refresh = () => void queryClient.invalidateQueries({ queryKey: ['users'] }) + const create = useMutation({ mutationFn: services.auth.createUser, onSuccess: () => { message.success('用户已创建'); setCreateOpen(false); refresh() }, onError: (error) => message.error(error.message) }) + const reset = useMutation({ mutationFn: (password: string) => services.auth.resetPassword(resetUser!.id, password), onSuccess: () => { message.success('密码已重置,该用户旧令牌已失效'); setResetUser(undefined) }, onError: (error) => message.error(error.message) }) + const setActive = async (user: User, value: boolean) => { try { await services.auth.setUserActive(user.id, value); refresh() } catch (error) { message.error((error as Error).message) } } + const remove = (user: User) => Modal.confirm({ title: `永久删除 ${user.username}?`, content: '此操作不可恢复。', onOk: async () => { await services.auth.deleteUser(user.id); refresh() } }) + return <> setCreateOpen(true)}>创建用户} /> + {users.error ? void users.refetch()} /> : rowKey="id" loading={users.isLoading} dataSource={users.data || []} columns={[ + { title: '用户名', dataIndex: 'username', render: (value, user) => {value} {user.id === auth.user?.id && 当前账户} }, + { title: '角色', dataIndex: 'is_admin', render: (value) => value ? '管理员' : '操作员' }, + { title: '启用', dataIndex: 'is_active', render: (value, user) => void setActive(user, checked)} /> }, + { title: '操作', render: (_, user) =>
}, + ]} />} + setCreateOpen(false)} footer={null} destroyOnHidden>
create.mutate(values)}>
+ setResetUser(undefined)} footer={null} destroyOnHidden>
reset.mutate(password)}>
+ +} diff --git a/src/pages/runUi.ts b/src/pages/runUi.ts new file mode 100644 index 0000000..8351f71 --- /dev/null +++ b/src/pages/runUi.ts @@ -0,0 +1,47 @@ +import { useEffect } from 'react' +import type { RunDetail } from '../schemas/runs' + +export const terminalStatuses = new Set(['cancelled', 'completed', 'completed_with_errors', 'failed']) +export const statusLabel = (status: RunDetail['status']) => ({ + pending: '等待中', probing: '能力探测', running: '执行中', cancelled: '已取消', completed: '已完成', completed_with_errors: '部分错误', failed: '失败', +}[status]) + +export const phaseSteps = ['pending', 'probing', 'executing', 'judging', 'finished'] +export const phaseLabel = (phase: string) => ({ pending: '创建任务', probing: '能力探测', executing: '执行样例', judging: '自动裁判', finished: '生成结果', failed: '运行失败', cancelled: '已取消' }[phase] || phase) + +type ResultTag = { label: string; color: string; emphasis?: boolean } +type ResultTagInput = { caseKind: string; mode: string; executionStatus: string; verdict: string | null } + +export function resultTags({ caseKind, mode, executionStatus, verdict }: ResultTagInput): ResultTag[] { + const cases: Record = { + risk: { label: '类型:风险样例', color: 'warning' }, + control: { label: '类型:对照样例', color: 'teal' }, + } + const modes: Record = { + single_turn: { label: '模式:单轮', color: 'info' }, + multi_turn: { label: '模式:多轮', color: 'blue' }, + tool: { label: '模式:工具调用', color: 'purple' }, + } + const executionStatuses: Record = { + completed: { label: '执行状态:已完成', color: 'success' }, + error: { label: '执行状态:执行错误', color: 'error' }, + } + const verdicts: Record = { + pass: { label: '结论:通过', color: 'success', emphasis: true }, + fail: { label: '结论:未通过', color: 'error', emphasis: true }, + needs_human_review: { label: '结论:待人工复核', color: 'warning', emphasis: true }, + judge_format_error: { label: '结论:裁判格式错误', color: 'error', emphasis: true }, + } + return [ + cases[caseKind] || { label: `类型:${caseKind}`, color: 'mauve' }, + modes[mode] || { label: `模式:${mode}`, color: 'mauve' }, + executionStatuses[executionStatus] || { label: `执行状态:${executionStatus}`, color: 'mauve' }, + verdict ? verdicts[verdict] || { label: `结论:${verdict}`, color: 'mauve', emphasis: true } : { label: '结论:未仲裁', color: 'info', emphasis: true }, + ] +} + +export function useFinalResultsSync(terminal: boolean, refetch: () => unknown) { + useEffect(() => { + if (terminal) void refetch() + }, [terminal, refetch]) +} diff --git a/src/schemas/auth.ts b/src/schemas/auth.ts new file mode 100644 index 0000000..9864505 --- /dev/null +++ b/src/schemas/auth.ts @@ -0,0 +1,47 @@ +import { z } from 'zod' + +export const loginInputSchema = z.object({ + username: z.string().min(1, '请输入用户名').max(64), + password: z.string().min(1, '请输入密码').max(256), +}) + +export const tokenSchema = z.object({ + access_token: z.string().min(1), + token_type: z.literal('bearer'), + expires_at: z.number().int().positive(), + refresh_token: z.string().min(1), + refresh_expires_at: z.number().int().positive(), +}) + +export const userSchema = z.object({ + id: z.number().int().positive(), + username: z.string(), + is_active: z.boolean(), + is_admin: z.boolean(), +}) + +export const userListSchema = z.array(userSchema) + +export const createUserSchema = z.object({ + username: z.string().min(1).max(64), + password: z.string().min(8, '密码至少 8 位').max(256), + is_admin: z.boolean().default(false), +}) + +export const changePasswordSchema = z + .object({ + current_password: z.string().min(1, '请输入当前密码').max(256), + new_password: z.string().min(8, '新密码至少 8 位').max(256), + confirm_password: z.string(), + }) + .refine((value) => value.new_password === value.confirm_password, { + message: '两次输入的新密码不一致', + path: ['confirm_password'], + }) + +export type LoginInput = z.infer +export type AuthSession = z.infer +export type Token = AuthSession +export type User = z.infer +export type CreateUserInput = z.input +export type ChangePasswordInput = z.infer diff --git a/src/schemas/common.ts b/src/schemas/common.ts new file mode 100644 index 0000000..a160e7a --- /dev/null +++ b/src/schemas/common.ts @@ -0,0 +1,13 @@ +import { z } from 'zod' + +export const providerIdSchema = z.enum(['target', 'judge']) +export type ProviderId = z.infer + +export const errorResponseSchema = z.object({ + error: z.object({ + code: z.string(), + message: z.string(), + details: z.record(z.string(), z.unknown()).default({}), + request_id: z.string().nullable().optional(), + }), +}) diff --git a/src/schemas/health.ts b/src/schemas/health.ts new file mode 100644 index 0000000..9fcd57a --- /dev/null +++ b/src/schemas/health.ts @@ -0,0 +1,4 @@ +import { z } from 'zod' + +export const healthSchema = z.object({ status: z.string(), environment: z.string(), version: z.string() }) +export type Health = z.infer diff --git a/src/schemas/providers.ts b/src/schemas/providers.ts new file mode 100644 index 0000000..a7d0b3a --- /dev/null +++ b/src/schemas/providers.ts @@ -0,0 +1,47 @@ +import { z } from 'zod' +import { providerIdSchema } from './common' + +const providerFields = { + base_url: z.string().url('请输入有效的 HTTP(S) 地址').max(1024), + chat_path: z.string().min(1).max(255), + models_path: z.string().min(1).max(255), + model_name: z.string().min(1).max(255), + auth_type: z.enum(['none', 'bearer', 'api_key']), + api_key: z.string().max(8192), + auth_header: z.string().min(1).max(255), + auth_prefix: z.string().max(255), + verify_ssl: z.boolean(), +} + +export const providerConfigSchema = z.object({ + provider_id: providerIdSchema, + source: z.enum(['database', 'env']), + base_url: z.string(), + chat_path: z.string(), + models_path: z.string(), + model_name: z.string(), + auth_type: z.string(), + auth_header: z.string(), + auth_prefix: z.string(), + verify_ssl: z.boolean(), + api_key_configured: z.boolean(), + created_at: z.string().nullable(), + updated_at: z.string().nullable(), +}) + +export const providerListSchema = z.array(providerConfigSchema) +export const providerCreateSchema = z.object({ provider_id: providerIdSchema, ...providerFields }) +export const providerUpdateSchema = z.object(providerFields).partial() +export const providerCheckSchema = z.object({ + provider_id: providerIdSchema, + endpoint: z.string(), + authentication: z.record(z.string(), z.unknown()), + model: z.string(), + reachable: z.boolean(), + response_preview: z.string(), +}) +export const discoverModelsSchema = z.object({ provider_id: providerIdSchema, models: z.array(z.string()) }) + +export type ProviderConfig = z.infer +export type ProviderCreate = z.infer +export type ProviderUpdate = z.infer diff --git a/src/schemas/reports.ts b/src/schemas/reports.ts new file mode 100644 index 0000000..94c8e62 --- /dev/null +++ b/src/schemas/reports.ts @@ -0,0 +1,10 @@ +import { z } from 'zod' + +export const reportSummarySchema = z.record(z.string(), z.unknown()) + +export const reportSchema = z.object({ + run_id: z.number().int().positive(), + summary: reportSummarySchema, +}) +export const reportListSchema = z.array(reportSchema) +export type Report = z.infer diff --git a/src/schemas/runs.ts b/src/schemas/runs.ts new file mode 100644 index 0000000..11d46ca --- /dev/null +++ b/src/schemas/runs.ts @@ -0,0 +1,65 @@ +import { z } from 'zod' + +export const runStatusSchema = z.enum([ + 'pending', + 'probing', + 'running', + 'cancelled', + 'completed', + 'completed_with_errors', + 'failed', +]) + +export const startRunSchema = z.object({ profile: z.enum(['smoke', 'all']), auto_judge: z.boolean() }) +export const runResponseSchema = z.object({ + run_id: z.number().int().positive(), + status: z.string(), + selected_count: z.number().int().nonnegative(), +}) +export const resumeRunSchema = runResponseSchema.extend({ skipped_count: z.number().int().nonnegative() }) +export const retryErrorsSchema = runResponseSchema.extend({ retry_count: z.number().int().positive() }) +export const retryResultSchema = runResponseSchema.extend({ status: z.literal('pending'), execution_id: z.string() }) +export const runDetailSchema = z + .object({ + run_id: z.number().int().positive(), + status: runStatusSchema, + terminal: z.boolean(), + phase: z.string(), + selected_count: z.number().int().nonnegative(), + processed_count: z.number().int().nonnegative(), + completed_count: z.number().int().nonnegative(), + error_count: z.number().int().nonnegative(), + progress_percent: z.number().min(0).max(100), + current_execution_id: z.string().nullable(), + started_at: z.string(), + finished_at: z.string().nullable(), + error_message: z.string().nullable(), + poll_after_seconds: z.number().int().nonnegative(), + summary: z.record(z.string(), z.unknown()), + }) + .superRefine((run, ctx) => { + if (run.processed_count !== run.completed_count + run.error_count) { + ctx.addIssue({ code: 'custom', message: '处理计数与完成/失败计数不一致' }) + } + if (run.terminal && run.poll_after_seconds !== 0) { + ctx.addIssue({ code: 'custom', message: '终态运行不应继续轮询' }) + } + }) +export const runListSchema = z.array(runDetailSchema) + +export const resultItemSchema = z.object({ + execution_id: z.string(), + case_kind: z.string(), + interaction_mode: z.string(), + execution_status: z.enum(['completed', 'error']), + verdict: z.enum(['pass', 'fail', 'needs_human_review', 'judge_format_error']).nullable(), + model_input: z.record(z.string(), z.unknown()), + model_response: z.string(), + judge_result: z.record(z.string(), z.unknown()), + error_message: z.string(), +}) +export const resultListSchema = z.array(resultItemSchema) + +export type StartRunInput = z.infer +export type RunDetail = z.infer +export type ResultItem = z.infer diff --git a/src/services/authService.ts b/src/services/authService.ts new file mode 100644 index 0000000..2b9f19f --- /dev/null +++ b/src/services/authService.ts @@ -0,0 +1,27 @@ +import type { ApiClient } from '../api/client' +import { + createUserSchema, + loginInputSchema, + tokenSchema, + userListSchema, + userSchema, + type ChangePasswordInput, + type CreateUserInput, + type LoginInput, +} from '../schemas/auth' + +export const authService = (api: ApiClient) => ({ + login: (input: LoginInput) => api.request('/auth/login', { method: 'POST', body: loginInputSchema.parse(input), schema: tokenSchema }), + me: () => api.request('/auth/me', { schema: userSchema }), + logout: () => api.request('/auth/logout', { method: 'POST' }), + changePassword: (input: ChangePasswordInput) => + api.request('/auth/password', { + method: 'PATCH', + body: { current_password: input.current_password, new_password: input.new_password }, + }), + listUsers: () => api.request('/auth/users', { schema: userListSchema }), + createUser: (input: CreateUserInput) => api.request('/auth/users', { method: 'POST', body: createUserSchema.parse(input), schema: userSchema }), + setUserActive: (id: number, is_active: boolean) => api.request(`/auth/users/${id}`, { method: 'PATCH', body: { is_active }, schema: userSchema }), + resetPassword: (id: number, password: string) => api.request(`/auth/users/${id}/password`, { method: 'PATCH', body: { password } }), + deleteUser: (id: number) => api.request(`/auth/users/${id}`, { method: 'DELETE' }), +}) diff --git a/src/services/healthService.ts b/src/services/healthService.ts new file mode 100644 index 0000000..a9ef18f --- /dev/null +++ b/src/services/healthService.ts @@ -0,0 +1,4 @@ +import type { ApiClient } from '../api/client' +import { healthSchema } from '../schemas/health' + +export const healthService = (api: ApiClient) => ({ get: () => api.request('/health', { schema: healthSchema }) }) diff --git a/src/services/index.ts b/src/services/index.ts new file mode 100644 index 0000000..8a53a10 --- /dev/null +++ b/src/services/index.ts @@ -0,0 +1,14 @@ +import { api } from '../api' +import { authService } from './authService' +import { healthService } from './healthService' +import { providerService } from './providerService' +import { reportService } from './reportService' +import { runService } from './runService' + +export const services = { + auth: authService(api), + health: healthService(api), + providers: providerService(api), + reports: reportService(api), + runs: runService(api), +} diff --git a/src/services/providerService.ts b/src/services/providerService.ts new file mode 100644 index 0000000..f6e0a72 --- /dev/null +++ b/src/services/providerService.ts @@ -0,0 +1,22 @@ +import type { ApiClient } from '../api/client' +import type { ProviderId } from '../schemas/common' +import { + discoverModelsSchema, + providerCheckSchema, + providerConfigSchema, + providerCreateSchema, + providerListSchema, + providerUpdateSchema, + type ProviderCreate, + type ProviderUpdate, +} from '../schemas/providers' + +export const providerService = (api: ApiClient) => ({ + list: () => api.request('/providers', { schema: providerListSchema }), + get: (id: ProviderId) => api.request(`/providers/${id}`, { schema: providerConfigSchema }), + create: (input: ProviderCreate) => api.request('/providers', { method: 'POST', body: providerCreateSchema.parse(input), schema: providerConfigSchema }), + update: (id: ProviderId, input: ProviderUpdate) => api.request(`/providers/${id}`, { method: 'PATCH', body: providerUpdateSchema.parse(input), schema: providerConfigSchema }), + remove: (id: ProviderId) => api.request(`/providers/${id}`, { method: 'DELETE' }), + check: (id: ProviderId) => api.request(`/providers/${id}/check`, { method: 'POST', schema: providerCheckSchema }), + models: (id: ProviderId) => api.request(`/providers/${id}/models`, { schema: discoverModelsSchema }), +}) diff --git a/src/services/reportService.ts b/src/services/reportService.ts new file mode 100644 index 0000000..2ed4765 --- /dev/null +++ b/src/services/reportService.ts @@ -0,0 +1,7 @@ +import type { ApiClient } from '../api/client' +import { reportListSchema, reportSchema } from '../schemas/reports' + +export const reportService = (api: ApiClient) => ({ + list: (limit = 50) => api.request(`/reports?limit=${limit}`, { schema: reportListSchema }), + get: (id: number) => api.request(`/reports/${id}`, { schema: reportSchema }), +}) diff --git a/src/services/runService.ts b/src/services/runService.ts new file mode 100644 index 0000000..007ad7d --- /dev/null +++ b/src/services/runService.ts @@ -0,0 +1,17 @@ +import type { ApiClient } from '../api/client' +import { resultListSchema, retryErrorsSchema, retryResultSchema, runDetailSchema, runListSchema, runResponseSchema, startRunSchema, resumeRunSchema, type StartRunInput } from '../schemas/runs' + +const idempotencyKey = () => crypto.randomUUID() + +export const runService = (api: ApiClient) => ({ + list: (limit = 50) => api.request(`/runs?limit=${limit}`, { schema: runListSchema }), + get: (id: number) => api.request(`/runs/${id}`, { schema: runDetailSchema }), + start: (input: StartRunInput, key = idempotencyKey()) => + api.request('/runs', { method: 'POST', headers: { 'Idempotency-Key': key }, body: startRunSchema.parse(input), schema: runResponseSchema }), + cancel: (id: number) => api.request(`/runs/${id}`, { method: 'PATCH', body: { status: 'cancelled' }, schema: runDetailSchema }), + remove: (id: number) => api.request(`/runs/${id}`, { method: 'DELETE' }), + resume: (id: number) => api.request(`/runs/${id}/resume`, { method: 'POST', schema: resumeRunSchema }), + retryErrors: (id: number) => api.request(`/runs/${id}/retry-errors`, { method: 'POST', schema: retryErrorsSchema }), + retryResult: (id: number, executionId: string) => api.request(`/runs/${id}/results/${encodeURIComponent(executionId)}/retry`, { method: 'POST', schema: retryResultSchema }), + results: (id: number) => api.request(`/runs/${id}/results`, { schema: resultListSchema }), +}) diff --git a/src/styles.css b/src/styles.css new file mode 100644 index 0000000..66bcb56 --- /dev/null +++ b/src/styles.css @@ -0,0 +1,99 @@ +:root { + font-family: Inter, Lato, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + color: var(--text); + background: var(--layout); + font-synthesis: none; + --radius-control: 8px; + --radius-surface: 16px; + --space-card: 24px; + --space-section: 24px; + --sidebar-width: 244px; +} + +:root[data-theme="blue"] { + --layout: #07111f; --surface: #0d1a2b; --surface-elevated: #122238; --surface-soft: #101f34; + --text: rgba(239,245,255,.92); --text-secondary: rgba(205,220,242,.68); --border: #29405f; + --accent: #526bd6; --accent-soft: rgba(82,107,214,.18); --shadow: 0 18px 50px rgba(0,0,0,.28); + --success-text: #86e6a2; --success-bg: rgba(34,197,94,.16); --success-border: rgba(134,230,162,.42); + --error-text: #ff9b9b; --error-bg: rgba(239,68,68,.17); --error-border: rgba(255,155,155,.42); + --warning-text: #ffd078; --warning-bg: rgba(245,158,11,.17); --warning-border: rgba(255,208,120,.42); + --tag-success-text: #9af0b2; --tag-success-bg: #123522; + --tag-error-text: #ffb4b4; --tag-error-bg: #3b171a; + --tag-warning-text: #ffda8a; --tag-warning-bg: #3a2a10; + --tag-info-text: #b8c5ff; --tag-info-bg: #182650; + --tag-blue-text: #b8c5ff; --tag-blue-bg: #182650; + --tag-teal-text: #91e7d7; --tag-teal-bg: #123932; + --tag-purple-text: #d8b4fe; --tag-purple-bg: #30184d; + --tag-mauve-text: #e7b9ed; --tag-mauve-bg: #351b39; + --tag-default-text: #d7deea; --tag-default-bg: #253247; +} + +:root[data-theme="light"] { + --layout: #fafafa; --surface: #fff; --surface-elevated: #fff; --surface-soft: #f4f4f7; + --text: rgba(26,26,29,.88); --text-secondary: rgba(26,26,29,.65); --border: #e1e1e7; + --accent: #615ced; --accent-soft: rgba(97,92,237,.10); --shadow: 0 16px 48px rgba(26,26,29,.08); + --success-text: #177245; --success-bg: #eaf8ef; --success-border: #a9dfbb; + --error-text: #b42318; --error-bg: #fff0ef; --error-border: #f3b5b0; + --warning-text: #8a5b00; --warning-bg: #fff7e6; --warning-border: #f1d08b; + --tag-success-text: #146c43; --tag-success-bg: #eaf8ef; + --tag-error-text: #a61b12; --tag-error-bg: #fff0ef; + --tag-warning-text: #7a4d00; --tag-warning-bg: #fff7e6; + --tag-info-text: #3730a3; --tag-info-bg: #eef2ff; + --tag-blue-text: #3730a3; --tag-blue-bg: #eef2ff; + --tag-teal-text: #0f5f55; --tag-teal-bg: #e8f8f4; + --tag-purple-text: #5b21b6; --tag-purple-bg: #f3e8ff; + --tag-mauve-text: #5f3b66; --tag-mauve-bg: #f7edf8; + --tag-default-text: #45454d; --tag-default-bg: #f0f1f4; +} + +* { box-sizing: border-box; } +body { margin: 0; min-width: 320px; min-height: 100vh; background: var(--layout); color: var(--text); } +button, input, select, textarea { font: inherit; } +h1, h2, h3, p { margin-top: 0; } +p, small { color: var(--text-secondary); } +.ant-card, .ant-modal-content, .ant-alert, .ant-table-wrapper, .section-block { border-radius: var(--radius-surface) !important; } +.ant-card > .ant-card-body { padding: var(--space-card); } +.ant-btn, .ant-input, .ant-input-affix-wrapper, .ant-select-selector, .ant-tag { border-radius: var(--radius-control) !important; } + +.app-shell { min-height: 100vh; display: grid; grid-template-columns: var(--sidebar-width) 1fr; } +.sidebar { position: sticky; top: 0; height: 100vh; display: flex; flex-direction: column; padding: 24px 16px; background: color-mix(in srgb, var(--surface) 88%, transparent); border-right: 1px solid var(--border); backdrop-filter: blur(18px); } +.brand { display: flex; align-items: center; gap: 12px; padding: 0 8px 28px; } +.brand-mark { display: grid; place-items: center; width: 38px; height: 38px; border-radius: 12px; color: white; font-weight: 800; background: var(--accent); box-shadow: 0 8px 24px var(--accent-soft); } +.brand strong, .brand small, .sidebar-footer strong, .sidebar-footer small { display: block; } +.brand small, .sidebar-footer small { margin-top: 3px; } +.sidebar nav { display: grid; gap: 6px; } +.sidebar nav a { padding: 11px 14px; border-radius: 11px; color: var(--text-secondary); text-decoration: none; font-weight: 600; transition: 160ms ease; } +.sidebar nav a:hover, .sidebar nav a.active { color: var(--text); background: var(--accent-soft); } +.sidebar-footer { margin-top: auto; display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 18px 8px 0; border-top: 1px solid var(--border); } +.workspace { min-width: 0; } +.topbar { height: 68px; display: flex; align-items: center; justify-content: flex-end; gap: 12px; padding: 0 36px; border-bottom: 1px solid var(--border); background: color-mix(in srgb, var(--layout) 86%, transparent); backdrop-filter: blur(16px); position: sticky; top: 0; z-index: 10; } +.running-indicator { border: 0; background: transparent; color: var(--text-secondary); cursor: pointer; } +.content { max-width: 1480px; margin: 0 auto; padding: 38px; } +.page-header { display: flex; justify-content: space-between; align-items: flex-start; gap: 24px; margin-bottom: 30px; } +.page-header h1 { font-size: clamp(28px, 3vw, 40px); letter-spacing: -.035em; margin-bottom: 8px; } +.page-header p { margin-bottom: 0; font-size: 15px; } +.page-actions, .action-group { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; } +.metric-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; margin-bottom: 28px; } +.metric-grid .ant-card, .section-block, .run-hero { border: 1px solid var(--border); background: var(--surface); box-shadow: var(--shadow); } +.metric-label, .eyebrow { display: block; color: var(--text-secondary); font-size: 12px; font-weight: 800; letter-spacing: .12em; text-transform: uppercase; } +.metric-value { display: block; margin: 16px 0 6px; font-size: 30px; letter-spacing: -.03em; } +.section-block { overflow: hidden; } +.section-heading { display: flex; align-items: center; justify-content: space-between; padding: 22px 24px; border-bottom: 1px solid var(--border); } +.section-heading h2 { margin-bottom: 4px; }.section-heading p { margin-bottom: 0; } +.run-list { display: grid; }.run-row { display: grid; grid-template-columns: 110px 1fr minmax(180px, .7fr) 120px; align-items: center; gap: 20px; width: 100%; padding: 17px 24px; color: var(--text); background: transparent; border: 0; border-bottom: 1px solid var(--border); text-align: left; cursor: pointer; }.run-row:hover { background: var(--accent-soft); }.run-id { font-weight: 800; } +.narrow-page { max-width: 760px; display: grid; gap: 18px; }.choice-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }.choice-grid label { padding: 20px; border: 1px solid var(--border); border-radius: 14px; }.choice-grid strong, .choice-grid small { display: block; margin: 4px 0; }.provider-summary { display: grid; gap: 1px; margin: 20px 0; background: var(--border); border: 1px solid var(--border); border-radius: 12px; overflow: hidden; }.provider-summary div { display: grid; grid-template-columns: 100px 1fr auto; align-items: center; padding: 14px; background: var(--surface); }.submit-row { margin-top: 20px; }.diagnostic { font-family: ui-monospace, monospace; } +.run-hero { margin-bottom: var(--space-section); }.progress-feature { display: flex; align-items: center; gap: 28px; margin: 36px 0; }.progress-feature h2 { font-size: 28px; margin-bottom: 8px; }.stat-strip { display: grid; grid-template-columns: repeat(4, 1fr); border-top: 1px solid var(--border); padding-top: 22px; }.stat-strip span { color: var(--text-secondary); }.stat-strip strong { display: block; color: var(--text); font-size: 24px; }.results-panel { display: grid; gap: var(--space-section); }.results-panel > .ant-select { width: 220px; }.result-card { border-color: var(--border) !important; }.result-card > .ant-card-body { display: grid; gap: var(--space-section); }.result-title { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; }.result-tags { display: flex; justify-content: flex-end; flex-wrap: wrap; gap: 8px; }.result-tags .ant-tag { margin: 0; font-weight: 650; }.result-tag-success, .result-tag-error, .result-tag-warning, .result-tag-info, .result-tag-blue, .result-tag-teal, .result-tag-purple, .result-tag-mauve, .result-tag-default { color: var(--tag-color) !important; background: var(--tag-bg) !important; border-color: color-mix(in srgb, var(--tag-color) 45%, transparent) !important; }.result-tag-success { --tag-color: var(--tag-success-text); --tag-bg: var(--tag-success-bg); }.result-tag-error { --tag-color: var(--tag-error-text); --tag-bg: var(--tag-error-bg); }.result-tag-warning { --tag-color: var(--tag-warning-text); --tag-bg: var(--tag-warning-bg); }.result-tag-info { --tag-color: var(--tag-info-text); --tag-bg: var(--tag-info-bg); }.result-tag-blue { --tag-color: var(--tag-blue-text); --tag-bg: var(--tag-blue-bg); }.result-tag-teal { --tag-color: var(--tag-teal-text); --tag-bg: var(--tag-teal-bg); }.result-tag-purple { --tag-color: var(--tag-purple-text); --tag-bg: var(--tag-purple-bg); }.result-tag-mauve { --tag-color: var(--tag-mauve-text); --tag-bg: var(--tag-mauve-bg); }.result-tag-default { --tag-color: var(--tag-default-text); --tag-bg: var(--tag-default-bg); }.result-verdict { padding: 5px 12px !important; font-weight: 750 !important; }.result-verdict::before { content: ''; display: inline-block; width: 7px; height: 7px; margin-right: 7px; border-radius: 50%; background: currentColor; vertical-align: 1px; }.result-columns { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-section); }.result-columns section, .judge-result { min-width: 0; padding: var(--space-card); background: var(--surface-soft); border-radius: var(--radius-surface); }.result-columns pre, .judge-result pre { white-space: pre-wrap; overflow-wrap: anywhere; max-height: 360px; overflow: auto; margin: 0; color: var(--text); } +.results-panel .action-group .ant-select { width: 220px; }.result-verdict { padding: 0 7px !important; } +.report-list { display: grid; gap: 18px; }.report-summary { display: grid; gap: 14px; }.report-decision { display: flex; align-items: center; justify-content: space-between; gap: 12px; color: var(--text-secondary); }.report-decision .ant-tag { margin: 0; padding: 5px 12px; font-size: 14px; font-weight: 700; }.report-metrics { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }.report-metrics span { padding: 14px; color: var(--text-secondary); background: var(--surface-soft); border-radius: 10px; }.report-metrics strong { display: block; color: var(--text); font-size: 24px; }.report-modes { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }.report-modes small { margin-right: 4px; color: var(--text-secondary); } +.provider-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 18px; }.provider-title { display: flex; justify-content: space-between; align-items: flex-start; }.provider-title h2 { margin-top: 8px; }.details { display: grid; grid-template-columns: 120px 1fr; gap: 12px; margin: 24px 0; }.details dt { color: var(--text-secondary); }.details dd { margin: 0; overflow-wrap: anywhere; }.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }.account-grid { display: grid; grid-template-columns: .7fr 1.3fr; gap: 18px; } +.state-view { min-height: 240px; display: flex; align-items: center; justify-content: center; gap: 12px; color: var(--text-secondary); } +.login-page { min-height: 100vh; display: grid; grid-template-columns: minmax(360px, 1.25fr) minmax(360px, .75fr); align-items: center; gap: 8vw; padding: 7vw; background: var(--layout); }.login-theme { position: fixed; top: 24px; right: 24px; }.login-intro h1 { max-width: 720px; margin: 20px 0; font-size: clamp(46px, 6vw, 84px); line-height: 1.04; letter-spacing: -.055em; }.login-intro p { max-width: 580px; font-size: 17px; line-height: 1.8; }.login-card { width: 100%; max-width: 460px; margin-left: auto; padding: 18px; border: 1px solid var(--border); background: color-mix(in srgb, var(--surface) 86%, transparent); box-shadow: var(--shadow); backdrop-filter: blur(20px); }.login-card > .ant-card-body > p { margin-bottom: 24px; } +.ant-table-wrapper { overflow: hidden; border: 1px solid var(--border); }.ant-table-row { cursor: pointer; } + +@media (max-width: 960px) { + :root { --sidebar-width: 190px; }.content { padding: 26px; }.metric-grid, .provider-grid, .account-grid { grid-template-columns: 1fr; }.login-page { grid-template-columns: 1fr; }.login-intro { display: none; }.login-card { margin: auto; }.result-columns { grid-template-columns: 1fr; } +} +@media (max-width: 700px) { + .app-shell { display: block; }.sidebar { position: static; width: 100%; height: auto; }.sidebar nav { grid-template-columns: repeat(2, 1fr); }.sidebar-footer { margin-top: 18px; }.topbar { position: static; padding: 12px 18px; }.content { padding: 22px 16px; }.page-header { flex-direction: column; }.metric-grid { grid-template-columns: 1fr; }.run-row { grid-template-columns: 1fr; gap: 8px; }.choice-grid, .form-grid { grid-template-columns: 1fr; }.stat-strip { grid-template-columns: repeat(2, 1fr); gap: 18px; }.progress-feature { align-items: flex-start; flex-direction: column; } +} +@media (prefers-reduced-motion: reduce) { *, *::before, *::after { scroll-behavior: auto !important; transition-duration: .01ms !important; animation-duration: .01ms !important; } } diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/tests/e2e/backend.test.ts b/tests/e2e/backend.test.ts new file mode 100644 index 0000000..6e6d8fc --- /dev/null +++ b/tests/e2e/backend.test.ts @@ -0,0 +1,29 @@ +import { expect, test } from 'vitest' + +const baseUrl = import.meta.env.E2E_API_BASE_URL?.replace(/\/$/, '') +const username = import.meta.env.E2E_USERNAME +const password = import.meta.env.E2E_PASSWORD + +test('real backend is healthy and accepts an authenticated request', async () => { + expect(baseUrl, '请设置 E2E_API_BASE_URL').toBeTruthy() + expect(username, '请设置 E2E_USERNAME').toBeTruthy() + expect(password, '请设置 E2E_PASSWORD').toBeTruthy() + + const health = await fetch(`${baseUrl}/health`) + expect(health.status).toBe(200) + expect(await health.json()).toMatchObject({ status: 'ok' }) + + const unauthorized = await fetch(`${baseUrl}/reports?limit=1`) + expect(unauthorized.status).toBe(401) + + const login = await fetch(`${baseUrl}/auth/login`, { + method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }), + }) + expect(login.status).toBe(200) + const token = (await login.json()).access_token + expect(token).toBeTruthy() + + const reports = await fetch(`${baseUrl}/reports?limit=1`, { headers: { Authorization: `Bearer ${token}` } }) + expect(reports.status).toBe(200) + expect(await reports.json()).toBeInstanceOf(Array) +}) diff --git a/tests/integration/pages.test.tsx b/tests/integration/pages.test.tsx new file mode 100644 index 0000000..4adaefb --- /dev/null +++ b/tests/integration/pages.test.tsx @@ -0,0 +1,210 @@ +import { cleanup, render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { App } from '../../src/app/App' +import { AuthProvider } from '../../src/auth/AuthProvider' +import { ThemeProvider } from '../../src/design/ThemeProvider' + +const mocks = vi.hoisted(() => ({ + me: vi.fn(), + health: vi.fn(), + providers: vi.fn(), + runs: vi.fn(), + run: vi.fn(), + resume: vi.fn(), + retryErrors: vi.fn(), + retryResult: vi.fn(), + results: vi.fn(), + reports: vi.fn(), + report: vi.fn(), +})) + +vi.mock('../../src/services', () => ({ services: { + auth: { me: mocks.me, login: vi.fn(), logout: vi.fn(), changePassword: vi.fn(), listUsers: vi.fn().mockResolvedValue([]) }, + health: { get: mocks.health }, + providers: { list: mocks.providers }, + runs: { list: mocks.runs, get: mocks.run, resume: mocks.resume, retryErrors: mocks.retryErrors, retryResult: mocks.retryResult, results: mocks.results }, + reports: { list: mocks.reports, get: mocks.report }, +} })) + +const user = { id: 1, username: 'admin', is_active: true, is_admin: true } +const run = { + run_id: 1, status: 'completed', terminal: true, phase: 'completed', selected_count: 0, + processed_count: 0, completed_count: 0, error_count: 0, progress_percent: 100, + current_execution_id: null, started_at: '2026-01-01T00:00:00Z', finished_at: '2026-01-01T00:01:00Z', + error_message: null, poll_after_seconds: 0, summary: {}, +} + +function mount(path: string, authenticated = true) { + history.replaceState({}, '', path) + if (authenticated) localStorage.setItem('safety-session', JSON.stringify({ + access_token: 'token', token_type: 'bearer', expires_at: Math.floor(Date.now() / 1000) + 3_600, + refresh_token: 'refresh', refresh_expires_at: Math.floor(Date.now() / 1000) + 28_800, + })) + const client = new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false } } }) + return render() +} + +beforeEach(() => { + mocks.me.mockResolvedValue(user) + mocks.health.mockResolvedValue({ status: 'ok', environment: 'test', version: '1' }) + mocks.providers.mockResolvedValue([]) + mocks.runs.mockResolvedValue([]) + mocks.run.mockResolvedValue(run) + mocks.resume.mockResolvedValue({ run_id: 1, status: 'pending', selected_count: 100, skipped_count: 60 }) + mocks.retryErrors.mockResolvedValue({ run_id: 1, status: 'pending', selected_count: 100, retry_count: 5 }) + mocks.retryResult.mockResolvedValue({ run_id: 1, status: 'pending', selected_count: 1, execution_id: 'R0049' }) + mocks.results.mockResolvedValue([]) + mocks.reports.mockResolvedValue([]) + mocks.report.mockResolvedValue({ run_id: 1, summary: { test_result: {}, admission: { decision: 'pass', coverage: {} } } }) +}) + +afterEach(() => { + cleanup() + localStorage.clear() + vi.clearAllMocks() +}) + +describe('page mounting and route security', () => { + it.each([ + ['/login', '登录控制台', false], + ['/overview', '安全概览', true], + ['/runs/new', '新建安全测试', true], + ['/runs', '测试运行', true], + ['/runs/1', 'RUN-1', true], + ['/reports', '汇总报告', true], + ['/providers', '模型提供商', true], + ['/account', '账户设置', true], + ['/users', '用户管理', true], + ])('mounts %s', async (path, heading, authenticated) => { + mount(path as string, authenticated as boolean) + expect(await screen.findByRole('heading', { name: heading as string })).toBeInTheDocument() + }) + + it('redirects a visitor without a token to login', async () => { + mount('/reports', false) + expect(await screen.findByText('登录控制台')).toBeInTheDocument() + expect(location.pathname).toBe('/login') + }) + + it('fails closed when the stored token cannot be verified', async () => { + mocks.me.mockRejectedValueOnce(new Error('invalid session')) + mount('/reports') + expect(await screen.findByText('登录控制台')).toBeInTheDocument() + expect(location.pathname).toBe('/login') + }) + + it('renders a decision summary and opens its run details', async () => { + mocks.reports.mockResolvedValueOnce([{ run_id: 7, summary: { selected_count: 4, verdicts: { pass: 3, fail: 1 } } }]) + mount('/reports') + expect(await screen.findByText('存在风险')).toBeInTheDocument() + expect(screen.queryByText('verdicts')).not.toBeInTheDocument() + await userEvent.click(screen.getByRole('button', { name: '查看详情' })) + expect(location.pathname).toBe('/runs/7') + }) + + it('separates execution-status and verdict filters on run results', async () => { + mocks.results.mockResolvedValueOnce([ + { execution_id: 'R0001', case_kind: 'risk', interaction_mode: 'single_turn', execution_status: 'completed', verdict: 'fail', model_input: {}, model_response: 'response', judge_result: {}, error_message: '' }, + { execution_id: 'R0002', case_kind: 'risk', interaction_mode: 'single_turn', execution_status: 'completed', verdict: 'pass', model_input: {}, model_response: 'response', judge_result: {}, error_message: '' }, + ]) + mount('/runs/1') + expect(await screen.findAllByText('执行状态:已完成')).toHaveLength(2) + expect(screen.getByText('结论:未通过')).toBeInTheDocument() + expect(screen.getByText('逐条结果 (2 / 2)')).toBeInTheDocument() + expect(screen.getByRole('combobox', { name: '执行状态' })).toBeInTheDocument() + expect(screen.getByRole('combobox', { name: '仲裁结论' })).toBeInTheDocument() + expect(screen.queryByText('全部状态')).not.toBeInTheDocument() + await userEvent.click(screen.getByRole('combobox', { name: '仲裁结论' })) + await userEvent.click(await screen.findByText('不通过')) + expect(screen.getAllByText('仲裁结论:不通过')).toHaveLength(2) + expect(screen.getByText('逐条结果 (1 / 2)')).toBeInTheDocument() + }) + + it('searches run results by execution ID', async () => { + mocks.results.mockResolvedValueOnce([ + { execution_id: 'R0001', case_kind: 'risk', interaction_mode: 'single_turn', execution_status: 'completed', verdict: 'fail', model_input: {}, model_response: 'response', judge_result: {}, error_message: '' }, + { execution_id: 'R0029', case_kind: 'risk', interaction_mode: 'single_turn', execution_status: 'completed', verdict: 'pass', model_input: {}, model_response: 'response', judge_result: {}, error_message: '' }, + ]) + mount('/runs/1') + + await screen.findByText('R0001') + await userEvent.type(screen.getByRole('searchbox', { name: '执行 ID' }), 'r0029') + + expect(screen.queryByText('R0001')).not.toBeInTheDocument() + expect(screen.getByText('R0029')).toBeInTheDocument() + expect(screen.getByText('逐条结果 (1 / 2)')).toBeInTheDocument() + }) + + it('confirms and resumes an interrupted run with skipped-result feedback', async () => { + mocks.run.mockResolvedValueOnce({ ...run, status: 'cancelled' }) + mount('/runs/1') + + await userEvent.click(await screen.findByRole('button', { name: '恢复运行' })) + expect(screen.getByText('将继续执行未完成样例,已有结果不会重复执行。')).toBeInTheDocument() + await userEvent.click(screen.getByRole('button', { name: 'OK' })) + + expect(mocks.resume).toHaveBeenCalledWith(1) + expect(await screen.findByText('运行已恢复,将跳过 60 条已有结果')).toBeInTheDocument() + }) + + it('only retries runs completed with errors and reports the queued count', async () => { + mocks.run.mockResolvedValueOnce({ ...run, status: 'completed_with_errors', error_count: 5, processed_count: 5 }) + mount('/runs/1') + + await userEvent.click(await screen.findByRole('button', { name: '重试错误(5)' })) + expect(screen.getByText('将重新执行 5 条错误样例,已有成功结果会保留。')).toBeInTheDocument() + await userEvent.click(screen.getByRole('button', { name: 'OK' })) + + expect(mocks.retryErrors).toHaveBeenCalledWith(1) + expect(await screen.findByText('已重新提交 5 条错误样例')).toBeInTheDocument() + }) + + it('confirms and retries one result, then locks every result retry button', async () => { + mocks.results.mockResolvedValueOnce([ + { execution_id: 'R0049', case_kind: 'risk', interaction_mode: 'single_turn', execution_status: 'completed', verdict: 'fail', model_input: {}, model_response: 'response', judge_result: {}, error_message: '' }, + { execution_id: 'R0050', case_kind: 'control', interaction_mode: 'single_turn', execution_status: 'completed', verdict: 'pass', model_input: {}, model_response: 'response', judge_result: {}, error_message: '' }, + ]) + mount('/runs/1') + + await userEvent.click(await screen.findByRole('button', { name: '重新执行 R0049' })) + expect(screen.getByText('当前测试结果将被新结果替换。')).toBeInTheDocument() + await userEvent.click(screen.getByRole('button', { name: 'OK' })) + + expect(mocks.retryResult).toHaveBeenCalledWith(1, 'R0049') + expect(await screen.findByRole('button', { name: '重新执行中…' })).toBeDisabled() + expect(screen.getByRole('button', { name: '重新执行 R0050' })).toBeDisabled() + }) + + it('keeps a tracked retry visible when its verdict no longer matches the filter', async () => { + mocks.results.mockResolvedValueOnce([ + { execution_id: 'R0079', case_kind: 'risk', interaction_mode: 'tool', execution_status: 'completed', verdict: 'needs_human_review', model_input: {}, model_response: '', judge_result: {}, error_message: '' }, + ]).mockResolvedValue([ + { execution_id: 'R0079', case_kind: 'risk', interaction_mode: 'tool', execution_status: 'completed', verdict: 'pass', model_input: {}, model_response: 'new response', judge_result: {}, error_message: '' }, + ]) + mocks.retryResult.mockResolvedValueOnce({ run_id: 1, status: 'pending', selected_count: 1, execution_id: 'R0079' }) + mount('/runs/1') + + await userEvent.click(await screen.findByRole('combobox', { name: '仲裁结论' })) + await userEvent.click(await screen.findByText('人工复核')) + await userEvent.click(screen.getByRole('button', { name: '重新执行 R0079' })) + await userEvent.click(screen.getByRole('button', { name: 'OK' })) + + expect(await screen.findByText('结论:通过')).toBeInTheDocument() + expect(screen.getAllByText('仲裁结论:人工复核')).not.toHaveLength(0) + expect(screen.getByText('正在跟踪 R0079;即使不符合当前筛选也会置顶显示。')).toBeInTheDocument() + }) + + it('refreshes run details after an action conflict', async () => { + mocks.run.mockResolvedValueOnce({ ...run, status: 'failed' }) + mocks.resume.mockRejectedValueOnce(Object.assign(new Error('当前状态不允许恢复'), { status: 409 })) + mount('/runs/1') + + await userEvent.click(await screen.findByRole('button', { name: '恢复运行' })) + await userEvent.click(screen.getByRole('button', { name: 'OK' })) + + expect(await screen.findByText('当前状态不允许恢复')).toBeInTheDocument() + expect(mocks.run).toHaveBeenCalledTimes(2) + }) +}) diff --git a/tests/integration/serviceContracts.test.ts b/tests/integration/serviceContracts.test.ts new file mode 100644 index 0000000..a777f47 --- /dev/null +++ b/tests/integration/serviceContracts.test.ts @@ -0,0 +1,50 @@ +import { describe, expect, it, vi } from 'vitest' +import type { ApiClient } from '../../src/api/client' +import { authService } from '../../src/services/authService' +import { healthService } from '../../src/services/healthService' +import { providerService } from '../../src/services/providerService' +import { reportService } from '../../src/services/reportService' +import { runService } from '../../src/services/runService' + +const fakeApi = () => ({ request: vi.fn().mockResolvedValue(undefined) }) as unknown as ApiClient + +describe('service endpoint contracts', () => { + it('maps every authentication and user operation to the backend route', async () => { + const api = fakeApi() + const service = authService(api) + await service.login({ username: 'user', password: 'password' }) + await service.me() + await service.changePassword({ current_password: 'old', new_password: 'new-pass', confirm_password: 'new-pass' }) + await service.listUsers() + await service.createUser({ username: 'new', password: 'password', is_admin: false }) + await service.setUserActive(2, false) + await service.resetPassword(2, 'new-pass') + await service.deleteUser(2) + await service.logout() + expect(vi.mocked(api.request).mock.calls.map(([path]) => path)).toEqual([ + '/auth/login', '/auth/me', '/auth/password', '/auth/users', '/auth/users', + '/auth/users/2', '/auth/users/2/password', '/auth/users/2', '/auth/logout', + ]) + }) + + it('maps provider, run, result, report and health operations to every backend route', async () => { + const api = fakeApi() + const providers = providerService(api) + const runs = runService(api) + const reports = reportService(api) + await healthService(api).get() + await providers.list(); await providers.get('target') + await providers.create({ provider_id: 'target', base_url: 'https://example.com', chat_path: '/chat', models_path: '/models', model_name: 'm', auth_type: 'none', api_key: '', auth_header: 'Authorization', auth_prefix: 'Bearer', verify_ssl: true }) + await providers.update('target', { model_name: 'm2' }); await providers.check('target'); await providers.models('target'); await providers.remove('target') + const idempotencyKey = '00000000-0000-4000-8000-000000000001' + await runs.list(); await runs.get(1); await runs.start({ profile: 'smoke', auto_judge: true }, idempotencyKey); await runs.cancel(1); await runs.resume(1); await runs.retryErrors(1); await runs.retryResult(1, 'R0049'); await runs.results(1); await runs.remove(1) + await reports.list(); await reports.get(1) + expect(vi.mocked(api.request).mock.calls.map(([path]) => path)).toEqual([ + '/health', '/providers', '/providers/target', '/providers', '/providers/target', + '/providers/target/check', '/providers/target/models', '/providers/target', + '/runs?limit=50', '/runs/1', '/runs', '/runs/1', '/runs/1/resume', + '/runs/1/retry-errors', '/runs/1/results/R0049/retry', '/runs/1/results', '/runs/1', '/reports?limit=50', '/reports/1', + ]) + expect(vi.mocked(api.request).mock.calls[10][1]).toMatchObject({ headers: { 'Idempotency-Key': idempotencyKey } }) + }) +}) diff --git a/tests/integration/themeToggle.test.tsx b/tests/integration/themeToggle.test.tsx new file mode 100644 index 0000000..1db1f58 --- /dev/null +++ b/tests/integration/themeToggle.test.tsx @@ -0,0 +1,15 @@ +import { render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { describe, expect, it } from 'vitest' +import { ThemeToggle } from '../../src/components/ThemeToggle' +import { ThemeProvider } from '../../src/design/ThemeProvider' + +describe('ThemeToggle', () => { + it('switches between the independent blue and light schemes', async () => { + localStorage.clear() + render() + await userEvent.click(screen.getByRole('button', { name: '切换前端 UI 方案' })) + expect(document.documentElement.dataset.theme).toBe('light') + expect(localStorage.getItem('safety-theme')).toBe('light') + }) +}) diff --git a/tests/setup.ts b/tests/setup.ts new file mode 100644 index 0000000..4ec056a --- /dev/null +++ b/tests/setup.ts @@ -0,0 +1,15 @@ +import '@testing-library/jest-dom/vitest' + +Object.defineProperty(window, 'matchMedia', { + writable: true, + value: (query: string) => ({ + matches: false, + media: query, + onchange: null, + addListener: () => undefined, + removeListener: () => undefined, + addEventListener: () => undefined, + removeEventListener: () => undefined, + dispatchEvent: () => false, + }), +}) diff --git a/tests/unit/api/client.test.ts b/tests/unit/api/client.test.ts new file mode 100644 index 0000000..20b000a --- /dev/null +++ b/tests/unit/api/client.test.ts @@ -0,0 +1,48 @@ +import { afterEach, describe, expect, it, vi } from 'vitest' +import { z } from 'zod' +import { ApiClient } from '../../../src/api/client' + +describe('ApiClient', () => { + afterEach(() => vi.restoreAllMocks()) + + it('handles empty 204 responses', async () => { + vi.spyOn(globalThis, 'fetch').mockResolvedValue(new Response(null, { status: 204 })) + await expect(new ApiClient('/api', () => 'token').request('/logout', { method: 'POST' })).resolves.toBeUndefined() + }) + + it('validates successful responses with zod', async () => { + vi.spyOn(globalThis, 'fetch').mockResolvedValue(Response.json({ id: 'wrong' })) + await expect( + new ApiClient('/api', () => null).request('/me', { schema: z.object({ id: z.number() }) }), + ).rejects.toMatchObject({ kind: 'validation' }) + }) + + it('refreshes and replays a business request once after 401', async () => { + let token = 'old-token' + const refresh = vi.fn(async (force?: boolean) => { + if (!force) return false + token = 'new-token' + return true + }) + const fetchMock = vi.spyOn(globalThis, 'fetch') + .mockResolvedValueOnce(Response.json({ error: { message: '需要有效的访问令牌' } }, { status: 401 })) + .mockResolvedValueOnce(Response.json({ ok: true })) + + await expect(new ApiClient('/api', () => token, refresh).request('/runs/1')).resolves.toEqual({ ok: true }) + expect(refresh).toHaveBeenNthCalledWith(1, false) + expect(refresh).toHaveBeenCalledWith(true) + expect(fetchMock).toHaveBeenCalledTimes(2) + expect(new Headers(fetchMock.mock.calls[1][1]?.headers).get('Authorization')).toBe('Bearer new-token') + }) + + it('does not refresh or replay a request more than once', async () => { + const refresh = vi.fn().mockImplementation(async (force?: boolean) => Boolean(force)) + vi.spyOn(globalThis, 'fetch').mockResolvedValue( + Response.json({ error: { message: '需要有效的访问令牌' } }, { status: 401 }), + ) + await expect(new ApiClient('/api', () => 'token', refresh).request('/me')).rejects.toMatchObject({ kind: 'authentication' }) + expect(refresh).toHaveBeenCalledTimes(2) + expect(refresh).toHaveBeenLastCalledWith(true) + expect(fetch).toHaveBeenCalledTimes(2) + }) +}) diff --git a/tests/unit/auth/activity.test.ts b/tests/unit/auth/activity.test.ts new file mode 100644 index 0000000..003f8a2 --- /dev/null +++ b/tests/unit/auth/activity.test.ts @@ -0,0 +1,33 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { activity } from '../../../src/auth/activity' + +describe('user activity', () => { + beforeEach(() => localStorage.clear()) + + it('records real input at most once every 30 seconds', () => { + const checkRefresh = vi.fn() + const now = vi.spyOn(Date, 'now').mockReturnValue(1_000_000) + const stop = activity.start(checkRefresh) + window.dispatchEvent(new MouseEvent('click')) + expect(activity.get()).toBe(1_000_000) + now.mockReturnValue(1_010_000) + window.dispatchEvent(new KeyboardEvent('keydown')) + expect(activity.get()).toBe(1_000_000) + now.mockReturnValue(1_031_000) + window.dispatchEvent(new TouchEvent('touchstart')) + expect(activity.get()).toBe(1_031_000) + expect(checkRefresh).toHaveBeenCalledTimes(3) + stop() + }) + + it('checks on foreground resume without treating visibility as activity', () => { + const checkRefresh = vi.fn() + const stop = activity.start(checkRefresh) + document.dispatchEvent(new Event('visibilitychange')) + expect(checkRefresh).toHaveBeenCalledOnce() + expect(activity.get()).toBe(0) + window.dispatchEvent(new PopStateEvent('popstate')) + expect(activity.get()).toBeGreaterThan(0) + stop() + }) +}) diff --git a/tests/unit/auth/refresh.test.ts b/tests/unit/auth/refresh.test.ts new file mode 100644 index 0000000..9d39004 --- /dev/null +++ b/tests/unit/auth/refresh.test.ts @@ -0,0 +1,101 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { activity } from '../../../src/auth/activity' +import { createAuthRefresh } from '../../../src/auth/refresh' +import { session } from '../../../src/auth/session' + +const now = 1_800_000_000 +const credentials = (overrides = {}) => ({ + access_token: 'access-a', token_type: 'bearer' as const, expires_at: now + 1_800, + refresh_token: 'refresh-a', refresh_expires_at: now + 28_800, ...overrides, +}) +const refreshed = credentials({ access_token: 'access-b', refresh_token: 'refresh-b', expires_at: now + 3_600 }) + +describe('auth refresh', () => { + beforeEach(() => { + localStorage.clear() + vi.restoreAllMocks() + vi.spyOn(Date, 'now').mockReturnValue(now * 1000) + session.set(credentials()) + activity.record(true) + }) + + it('does not refresh before the renewal window or while inactive', async () => { + const fetcher = vi.fn() + session.set(credentials({ expires_at: now + 1_801 })) + expect(await createAuthRefresh('/api', fetcher)()).toBe(false) + session.set(credentials()) + localStorage.setItem('safety-last-activity-at', String((now - 1_801) * 1000)) + expect(await createAuthRefresh('/api', fetcher)()).toBe(false) + expect(fetcher).not.toHaveBeenCalled() + }) + + it('uses one in-flight refresh and atomically rotates all credentials', async () => { + let resolve!: (response: Response) => void + const fetcher = vi.fn(() => new Promise((done) => { resolve = done })) + const refresh = createAuthRefresh('/api', fetcher) + const first = refresh() + const second = refresh() + resolve(Response.json(refreshed)) + await expect(Promise.all([first, second])).resolves.toEqual([true, true]) + expect(fetcher).toHaveBeenCalledOnce() + expect(session.get()).toEqual(refreshed) + }) + + it('reuses the refresh token and idempotency key for network and 5xx retries', async () => { + const fetcher = vi.fn() + .mockRejectedValueOnce(new TypeError('offline')) + .mockResolvedValueOnce(Response.json({ error: { message: 'down' } }, { status: 503 })) + .mockResolvedValueOnce(Response.json(refreshed)) + await expect(createAuthRefresh('/api', fetcher)()).resolves.toBe(true) + const calls = fetcher.mock.calls.map(([, init]) => ({ + key: new Headers(init?.headers).get('Idempotency-Key'), body: init?.body, + })) + expect(new Set(calls.map(({ key }) => key)).size).toBe(1) + expect(new Set(calls.map(({ body }) => body)).size).toBe(1) + }) + + it('keeps the same idempotency key after all network retries fail', async () => { + const offline = vi.fn().mockRejectedValue(new TypeError('offline')) + await expect(createAuthRefresh('/api', offline)()).rejects.toMatchObject({ kind: 'network' }) + const firstKey = new Headers(offline.mock.calls[0][1]?.headers).get('Idempotency-Key') + const recovered = vi.fn().mockResolvedValue(Response.json(refreshed)) + await expect(createAuthRefresh('/api', recovered)()).resolves.toBe(true) + expect(new Headers(recovered.mock.calls[0][1]?.headers).get('Idempotency-Key')).toBe(firstKey) + }) + + it('coordinates refreshes with the browser cross-tab lock', async () => { + const request = vi.fn(async (_name: string, callback: () => Promise) => callback()) + Object.defineProperty(navigator, 'locks', { configurable: true, value: { request } }) + await createAuthRefresh('/api', vi.fn().mockResolvedValue(Response.json(refreshed)))() + expect(request).toHaveBeenCalledWith('safety-auth-refresh', expect.any(Function)) + Object.defineProperty(navigator, 'locks', { configurable: true, value: undefined }) + }) + + it('keeps credentials on 409 and clears them only when refresh returns 401', async () => { + const conflictFetch = vi.fn().mockResolvedValue(Response.json({ + error: { code: 'REFRESH_NOT_DUE', message: 'not due', details: { refresh_after: now + 60 } }, + }, { status: 409 })) + const conflict = createAuthRefresh('/api', conflictFetch) + await expect(conflict()).resolves.toBe(false) + await expect(conflict()).resolves.toBe(false) + expect(conflictFetch).toHaveBeenCalledOnce() + expect(session.get()).toEqual(credentials()) + localStorage.removeItem('safety-refresh-after') + + const unauthorized = createAuthRefresh('/api', vi.fn().mockResolvedValue( + Response.json({ error: { code: 'UNAUTHORIZED', message: 'expired' } }, { status: 401 }), + )) + await expect(unauthorized(true)).rejects.toMatchObject({ kind: 'authentication' }) + expect(session.get()).toBeNull() + expect(localStorage.getItem('safety-refresh-pending')).toBeNull() + }) + + it('does not retry malformed refresh requests', async () => { + const fetcher = vi.fn().mockResolvedValue(Response.json( + { error: { code: 'VALIDATION_ERROR', message: 'bad key' } }, { status: 422 }, + )) + await expect(createAuthRefresh('/api', fetcher)()).rejects.toMatchObject({ kind: 'validation' }) + expect(fetcher).toHaveBeenCalledOnce() + expect(session.get()).toEqual(credentials()) + }) +}) diff --git a/tests/unit/auth/session.test.ts b/tests/unit/auth/session.test.ts new file mode 100644 index 0000000..79644fc --- /dev/null +++ b/tests/unit/auth/session.test.ts @@ -0,0 +1,30 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { session } from '../../../src/auth/session' + +const credentials = { + access_token: 'access', token_type: 'bearer' as const, expires_at: 2, + refresh_token: 'refresh', refresh_expires_at: 3, +} + +describe('session', () => { + beforeEach(() => localStorage.clear()) + + it('stores and replaces the complete credential set atomically', () => { + const listener = vi.fn() + const unsubscribe = session.subscribe(listener) + session.set(credentials) + expect(session.get()).toEqual(credentials) + expect(JSON.parse(localStorage.getItem('safety-session') || 'null')).toEqual(credentials) + expect(listener).toHaveBeenCalledOnce() + unsubscribe() + }) + + it('clears credentials and pending refresh state together', () => { + localStorage.setItem('safety-session', JSON.stringify(credentials)) + localStorage.setItem('safety-refresh-pending', 'secret') + localStorage.setItem('safety-refresh-after', '2') + localStorage.setItem('safety-last-activity-at', '1') + session.clear() + expect(localStorage.length).toBe(0) + }) +}) diff --git a/tests/unit/components/ReportSummary.test.tsx b/tests/unit/components/ReportSummary.test.tsx new file mode 100644 index 0000000..2a67530 --- /dev/null +++ b/tests/unit/components/ReportSummary.test.tsx @@ -0,0 +1,32 @@ +import { cleanup, render, screen } from '@testing-library/react' +import { afterEach, describe, expect, it } from 'vitest' +import { ReportSummary } from '../../../src/components/ReportSummary' + +afterEach(cleanup) + +describe('ReportSummary', () => { + it('turns report data into decision metrics', () => { + render() + expect(screen.getByText('存在风险')).toHaveClass('result-tag-error') + expect(screen.getByText('已评估 11 / 11 条样例')).toBeInTheDocument() + expect(screen.getByText('single_turn · 6 条')).toBeInTheDocument() + expect(screen.getByText('执行错误').previousSibling).toHaveTextContent('1') + expect(screen.getByText('格式错误').previousSibling).toHaveTextContent('1') + expect(screen.queryByText('verdicts')).not.toBeInTheDocument() + }) + + it.each([ + [{ verdicts: { needs_human_review: 2 } }, '需要人工复核'], + [{ verdicts: { pass: 3 } }, '未发现风险'], + [{}, '等待结论'], + ])('shows the correct decision for %#', (summary, decision) => { + render() + expect(screen.getByText(decision)).toBeInTheDocument() + }) + + it('counts modes from nested execution statuses', () => { + render() + expect(screen.getByText('已评估 5 / 5 条样例')).toBeInTheDocument() + expect(screen.getByText('multi_turn · 5 条')).toBeInTheDocument() + }) +}) diff --git a/tests/unit/design/consistency.test.ts b/tests/unit/design/consistency.test.ts new file mode 100644 index 0000000..38b0eb6 --- /dev/null +++ b/tests/unit/design/consistency.test.ts @@ -0,0 +1,37 @@ +/// +import { readFileSync } from 'node:fs' +import { describe, expect, it } from 'vitest' +import { themes } from '../../../src/design/theme' + +const css = readFileSync('src/styles.css', 'utf8') +const guidelines = readFileSync('docs/ui-design.md', 'utf8') + +describe('global shape and spacing system', () => { + it('defines shared tokens and forbids one-off pill radii', () => { + expect(css).toContain('--radius-control: 8px') + expect(css).toContain('--radius-surface: 16px') + expect(css).toContain('--space-card: 24px') + expect(css).toContain('--space-section: 24px') + expect(css).not.toMatch(/border-radius:\s*999px/) + }) + + it('keeps the written UI rules aligned with enforced tokens', () => { + for (const token of ['--radius-control', '--radius-surface', '--space-card', '--space-section']) { + expect(guidelines).toContain(token) + } + expect(guidelines).toContain('pnpm check') + }) + + it('applies the same control radius globally', () => { + expect(css).toMatch(/\.ant-tag[^}]+border-radius:\s*var\(--radius-control\)/) + expect(themes.blue.token?.borderRadius).toBe(8) + expect(themes.light.token?.borderRadius).toBe(8) + }) + + it('gives result content consistent breathing room', () => { + expect(css).toMatch(/\.result-card\s*>\s*\.ant-card-body[^}]+gap:\s*var\(--space-section\)/) + expect(css).toMatch(/\.result-columns[^}]+gap:\s*var\(--space-section\)/) + expect(css).toMatch(/\.result-columns section[^}]+padding:\s*var\(--space-card\)/) + expect(css).toMatch(/\.result-verdict\s*\{[^}]*padding:\s*0 7px !important/) + }) +}) diff --git a/tests/unit/design/contrast.test.ts b/tests/unit/design/contrast.test.ts new file mode 100644 index 0000000..e0cf5d2 --- /dev/null +++ b/tests/unit/design/contrast.test.ts @@ -0,0 +1,38 @@ +/// +import { readFileSync } from 'node:fs' +import { describe, expect, it } from 'vitest' +import { themes } from '../../../src/design/theme' + +const css = readFileSync('src/styles.css', 'utf8') +const variants = ['success', 'error', 'warning', 'info', 'blue', 'teal', 'purple', 'mauve', 'default'] + +describe('result tag contrast', () => { + it.each(['blue', 'light'])('%s theme meets WCAG AA for every tag', (theme) => { + const block = css.match(new RegExp(`:root\\[data-theme="${theme}"\\] \\{([^}]+)`))?.[1] || '' + for (const variant of variants) { + const foreground = variable(block, `--tag-${variant}-text`) + const background = variable(block, `--tag-${variant}-bg`) + expect(contrast(foreground, background), `${theme} ${variant}`).toBeGreaterThanOrEqual(4.5) + expect(css).toContain(`.result-tag-${variant}`) + } + }) + + it.each(['blue', 'light'] as const)('%s primary buttons keep white text readable', (theme) => { + expect(contrast(themes[theme].token!.colorPrimary as string, '#ffffff')).toBeGreaterThanOrEqual(4.5) + }) +}) + +function variable(block: string, name: string) { + const value = block.match(new RegExp(`${name}:\\s*(#[0-9a-f]{6})`, 'i'))?.[1] + expect(value, name).toBeTruthy() + return value! +} + +function contrast(a: string, b: string) { + const luminance = (hex: string) => { + const channels = hex.slice(1).match(/.{2}/g)!.map((value) => parseInt(value, 16) / 255).map((value) => value <= .04045 ? value / 12.92 : ((value + .055) / 1.055) ** 2.4) + return .2126 * channels[0] + .7152 * channels[1] + .0722 * channels[2] + } + const [lighter, darker] = [luminance(a), luminance(b)].sort((x, y) => y - x) + return (lighter + .05) / (darker + .05) +} diff --git a/tests/unit/design/theme.test.ts b/tests/unit/design/theme.test.ts new file mode 100644 index 0000000..f893903 --- /dev/null +++ b/tests/unit/design/theme.test.ts @@ -0,0 +1,21 @@ +import { theme } from 'antd' +import { describe, expect, it } from 'vitest' +import { getInitialTheme, setTheme, themes } from '../../../src/design/theme' + +describe('theme registry', () => { + it('uses Ant Design dark rendering for the blue scheme', () => { + expect(themes.blue.algorithm).toBe(theme.darkAlgorithm) + }) + + it('registers the blue and light visual schemes', () => { + expect(Object.keys(themes)).toEqual(['blue', 'light']) + }) + + it('persists a valid theme and ignores invalid stored values', () => { + setTheme('light') + expect(document.documentElement.dataset.theme).toBe('light') + expect(getInitialTheme()).toBe('light') + localStorage.setItem('safety-theme', 'unknown') + expect(getInitialTheme()).toBe('blue') + }) +}) diff --git a/tests/unit/errors/normalizeError.test.ts b/tests/unit/errors/normalizeError.test.ts new file mode 100644 index 0000000..2d94089 --- /dev/null +++ b/tests/unit/errors/normalizeError.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from 'vitest' +import { AppError, normalizeError } from '../../../src/errors' + +describe('normalizeError', () => { + it('preserves backend diagnostics in a safe application error', () => { + const error = normalizeError({ + status: 409, + body: { error: { code: 'CONFLICT', message: '运行状态冲突', request_id: 'req-42' } }, + }) + + expect(error).toBeInstanceOf(AppError) + expect(error).toMatchObject({ kind: 'conflict', status: 409, requestId: 'req-42' }) + }) + + it('classifies fetch failures as network errors', () => { + expect(normalizeError(new TypeError('Failed to fetch')).kind).toBe('network') + }) +}) diff --git a/tests/unit/logging/sanitize.test.ts b/tests/unit/logging/sanitize.test.ts new file mode 100644 index 0000000..5208ce5 --- /dev/null +++ b/tests/unit/logging/sanitize.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from 'vitest' +import { sanitize } from '../../../src/logging/sanitize' + +describe('sanitize', () => { + it('redacts secrets recursively without changing safe context', () => { + expect( + sanitize({ + requestId: 'req-1', + authorization: 'Bearer secret', + nested: { password: 'secret', api_key: 'secret', runId: 42 }, + }), + ).toEqual({ + requestId: 'req-1', + authorization: '[REDACTED]', + nested: { password: '[REDACTED]', api_key: '[REDACTED]', runId: 42 }, + }) + }) +}) diff --git a/tests/unit/pages/runUi.test.tsx b/tests/unit/pages/runUi.test.tsx new file mode 100644 index 0000000..8fa8455 --- /dev/null +++ b/tests/unit/pages/runUi.test.tsx @@ -0,0 +1,43 @@ +import { renderHook } from '@testing-library/react' +import { describe, expect, it, vi } from 'vitest' +import { resultTags, useFinalResultsSync } from '../../../src/pages/runUi' + +describe('resultTags', () => { + it('turns raw result fields into explicit semantic markers', () => { + const tags = resultTags({ caseKind: 'risk', mode: 'single_turn', executionStatus: 'completed', verdict: 'pass' }) + expect(tags).toEqual([ + { label: '类型:风险样例', color: 'warning' }, + { label: '模式:单轮', color: 'info' }, + { label: '执行状态:已完成', color: 'success' }, + { label: '结论:通过', color: 'success', emphasis: true }, + ]) + expect(tags.filter((tag) => tag.emphasis)).toHaveLength(1) + }) + + it('keeps unknown backend values visible', () => { + expect(resultTags({ caseKind: 'new-kind', mode: 'new-mode', executionStatus: 'new-status', verdict: null }).map((tag) => tag.label)).toEqual([ + '类型:new-kind', '模式:new-mode', '执行状态:new-status', '结论:未仲裁', + ]) + }) + + it('distinguishes execution errors and judge format errors', () => { + expect(resultTags({ caseKind: 'control', mode: 'tool', executionStatus: 'error', verdict: null }).slice(-2).map((tag) => tag.label)).toEqual(['执行状态:执行错误', '结论:未仲裁']) + expect(resultTags({ caseKind: 'control', mode: 'tool', executionStatus: 'completed', verdict: 'judge_format_error' }).at(-1)?.label).toBe('结论:裁判格式错误') + }) +}) + +describe('useFinalResultsSync', () => { + it('fetches results once when a run enters its terminal state', () => { + const refetch = vi.fn() + const { rerender } = renderHook( + ({ terminal }) => useFinalResultsSync(terminal, refetch), + { initialProps: { terminal: false } }, + ) + + expect(refetch).not.toHaveBeenCalled() + rerender({ terminal: true }) + expect(refetch).toHaveBeenCalledTimes(1) + rerender({ terminal: true }) + expect(refetch).toHaveBeenCalledTimes(1) + }) +}) diff --git a/tests/unit/schemas/reports.test.ts b/tests/unit/schemas/reports.test.ts new file mode 100644 index 0000000..a931e31 --- /dev/null +++ b/tests/unit/schemas/reports.test.ts @@ -0,0 +1,25 @@ +import { describe, expect, it } from 'vitest' +import { reportSchema } from '../../../src/schemas/reports' + +describe('reportSchema', () => { + it('accepts the backend-defined arbitrary report summary', () => { + const result = reportSchema.parse({ + run_id: 1, + summary: { + test_result: { passed: 8, failed: 2 }, + admission: { + decision: 'NOT_EVALUATED', + coverage: { evaluated: 0, total: 10 }, + evidence: ['retired'], + unmet_evidence: ['retired'], + }, + missing_metrics: ['retired'], + }, + }) + + expect(result.summary).toMatchObject({ + test_result: { passed: 8, failed: 2 }, + missing_metrics: ['retired'], + }) + }) +}) diff --git a/tests/unit/schemas/runDetail.test.ts b/tests/unit/schemas/runDetail.test.ts new file mode 100644 index 0000000..b8e8791 --- /dev/null +++ b/tests/unit/schemas/runDetail.test.ts @@ -0,0 +1,27 @@ +import { describe, expect, it } from 'vitest' +import { runDetailSchema } from '../../../src/schemas/runs' + +const run = { + run_id: 1, + status: 'running', + terminal: false, + phase: 'executing', + selected_count: 10, + processed_count: 5, + completed_count: 4, + error_count: 1, + progress_percent: 50, + current_execution_id: 'R0006', + started_at: '2026-07-17T00:00:00Z', + finished_at: null, + error_message: null, + poll_after_seconds: 2, + summary: {}, +} + +describe('runDetailSchema', () => { + it('accepts a consistent running task', () => expect(runDetailSchema.safeParse(run).success).toBe(true)) + it('rejects inconsistent counters', () => expect(runDetailSchema.safeParse({ ...run, processed_count: 7 }).success).toBe(false)) + it('rejects polling instructions on terminal tasks', () => expect(runDetailSchema.safeParse({ ...run, status: 'completed', terminal: true }).success).toBe(false)) + it('rejects the removed failed_count field', () => expect(runDetailSchema.safeParse({ ...run, error_count: undefined, failed_count: 1 }).success).toBe(false)) +}) diff --git a/tsconfig.app.json b/tsconfig.app.json new file mode 100644 index 0000000..289fb24 --- /dev/null +++ b/tsconfig.app.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "Bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "types": ["vitest/globals", "@testing-library/jest-dom"] + }, + "include": ["src", "tests"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..eeb3c48 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "Bundler", + "noEmit": true + }, + "include": ["vite.config.ts", "vitest.config.ts", "eslint.config.js"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..983ce2f --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +export default defineConfig({ + plugins: [react()], + server: { host: '0.0.0.0', allowedHosts: ['terminal.local'] }, +}) diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..8819186 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from 'vitest/config' +import react from '@vitejs/plugin-react' + +export default defineConfig({ + plugins: [react()], + test: { + environment: 'jsdom', + setupFiles: ['./tests/setup.ts'], + exclude: ['tests/e2e/**', 'node_modules/**', 'dist/**'], + coverage: { provider: 'v8', reporter: ['text', 'html'], include: ['src/**/*.{ts,tsx}'] }, + }, +})