diff --git a/README.md b/README.md index cc7f44b..f8968b9 100644 --- a/README.md +++ b/README.md @@ -10,17 +10,17 @@ ## 基础设施 -| 组件 | 版本 | 用途 | -|------|------|------| -| Go | 1.25+ | 后端运行时 | -| Node.js | 18+ | 前端构建 | -| PostgreSQL | 16+ | 业务数据持久化 | -| Redis | 7+ | 会话状态、缓存 | -| MinIO / S3 | — | 简历、音频文件存储 | -| Milvus | 2.4+ | 题库向量检索 | -| Elasticsearch | 8.13+ | 题库关键词/标签检索 | -| RabbitMQ | 3.13+(可选) | 面试完成事件、报告生成 | -| Ollama | 可选 | 本地 LLM / Embedding | +| 组件 | 版本 | 用途 | 模式 | +|------|------|------|------| +| Go | 1.25+ | 后端运行时 | 必须 | +| Node.js | 18+ | 前端构建 | 必须 | +| PostgreSQL | 16+ | 业务数据持久化 | 必须 | +| Redis | 7+ | 会话状态、缓存 | 必须 | +| MinIO / S3 | — | 简历、音频文件存储 | 必须 | +| RabbitMQ | 3.13+ | 面试完成事件、报告生成 | 必须 | +| Milvus | 2.4+ | 题库向量检索 | RAG 模式 | +| Elasticsearch | 8.13+ | 题库关键词/标签检索 | RAG 模式 | +| Ollama | 可选 | 本地 LLM / Embedding | 可选 | ## LLM 提供商(至少配一个) @@ -46,30 +46,46 @@ React 19 · Vite · TypeScript · Zustand · Monaco Editor · Recharts # 快速启动 -## 1. 拉起基础设施 +## 1. 配置环境变量 ```bash -docker-compose up -d +cp .env.example .env +# 编辑 .env,至少填写一个 LLM 提供商的 API Key ``` -一键启动 PostgreSQL、Redis、MinIO、Milvus、Elasticsearch、RabbitMQ。 +## 2. 启动基础设施 + +系统提供两种启动模式,按机器配置选择: -## 2. 配置环境变量 +### Wiki 模式(推荐,2C2G 可用) + +出题由 LLM 直接生成,不依赖向量检索。只启动 Postgres、Redis、MinIO、RabbitMQ。 ```bash -cp .env.example .env -# 编辑 .env,至少填写一个 LLM 提供商的 API Key +bash start-wiki.sh ``` -## 3. 启动后端 +### RAG 模式(需要 8G+ 内存) + +启用 Milvus + Elasticsearch 向量/关键词检索题库。首次启动需拉取较大镜像。 ```bash -go run ./cmd +bash start-rag.sh ``` -默认监听 `:8080`,启动时自动执行数据库迁移。 +> 使用 RAG 模式前需先采集 wiki 知识库: +> ```bash +> bash collect-wiki.sh --limit 5 # 先小批量验证 +> bash collect-wiki.sh # 全量导入 +> ``` + +### 可选:本地 LLM(Ollama) + +```bash +docker compose --profile ollama up -d ollama +``` -## 4. 启动前端 +## 3. 启动前端 ```bash cd frontend && npm install && npm run dev diff --git a/collect-wiki.sh b/collect-wiki.sh index 03a14fc..787a4a5 100644 --- a/collect-wiki.sh +++ b/collect-wiki.sh @@ -16,10 +16,9 @@ if [ ! -f .env ]; then exit 1 fi -# collector 非 dry-run 时需要 PG + RabbitMQ(写 bank_questions + 投向量化任务)。 -# 注:向量化任务最终供 RAG 用,RAG 未实现时这些任务会堆在 MQ 里,不影响 wiki 产出(questions + index.md)。 echo "[collect-wiki] 启动采集所需基础设施(postgres redis minio rabbitmq)..." -docker compose up -d --wait postgres redis minio minio-init rabbitmq +docker compose up -d --wait postgres redis minio rabbitmq +docker compose up -d minio-init echo "[collect-wiki] 开始采集(首次会 git clone 小林 CS-Base 到 internal/wiki/raw/)..." go run ./tools/collector "$@" diff --git a/docker-compose.yaml b/docker-compose.yaml index fbce818..30aa126 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -108,10 +108,11 @@ services: - ai-interview-net # ───────────────────────────────────────── - # Milvus(向量数据库) + # Milvus(向量数据库)【rag profile】 # 依赖:etcd(元数据) + 业务 MinIO(对象存储,milvus bucket) # ───────────────────────────────────────── etcd: + profiles: ["rag"] image: quay.io/coreos/etcd:v3.5.14 container_name: ai-interview-etcd restart: unless-stopped @@ -136,6 +137,7 @@ services: - ai-interview-net milvus: + profiles: ["rag"] image: milvusdb/milvus:v2.4.17 container_name: ai-interview-milvus restart: unless-stopped @@ -168,9 +170,10 @@ services: - ai-interview-net # ───────────────────────────────────────── - # Elasticsearch(关键词 / 标签检索) + # Elasticsearch(关键词 / 标签检索)【rag profile】 # ───────────────────────────────────────── elasticsearch: + profiles: ["rag"] image: elasticsearch:8.13.4 container_name: ai-interview-es restart: unless-stopped @@ -191,10 +194,11 @@ services: - ai-interview-net # ───────────────────────────────────────── - # Ollama(本地 LLM / embedding,可选) - # 不需要本地模型时可注释掉整个服务 + # Ollama(本地 LLM / embedding)【ollama profile】 + # 不需要本地模型时可不传 --profile ollama # ───────────────────────────────────────── ollama: + profiles: ["ollama"] image: ollama/ollama:latest container_name: ai-interview-ollama restart: unless-stopped diff --git a/docu/wechat_group.png b/docu/wechat_group.png index 263b955..4127cc3 100644 Binary files a/docu/wechat_group.png and b/docu/wechat_group.png differ diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 65debda..19f0210 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -42,15 +42,15 @@ } }, "node_modules/@adobe/css-tools": { - "version": "4.4.4", - "resolved": "https://registry.npmmirror.com/@adobe/css-tools/-/css-tools-4.4.4.tgz", - "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/@adobe/css-tools/-/css-tools-4.5.0.tgz", + "integrity": "sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==", "dev": true, "license": "MIT" }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "resolved": "https://registry.npmmirror.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", "dev": true, "license": "MIT", @@ -113,13 +113,13 @@ "license": "MIT" }, "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", + "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -128,9 +128,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", "dev": true, "license": "MIT", "engines": { @@ -138,21 +138,21 @@ } }, "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", + "@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/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.1.0", @@ -169,14 +169,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -186,14 +186,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -203,9 +203,9 @@ } }, "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmmirror.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", "dev": true, "license": "MIT", "engines": { @@ -213,29 +213,29 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -245,9 +245,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "dev": true, "license": "MIT", "engines": { @@ -255,9 +255,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "dev": true, "license": "MIT", "engines": { @@ -265,9 +265,9 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", "dev": true, "license": "MIT", "engines": { @@ -275,27 +275,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.29.2", - "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.29.2.tgz", - "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0" + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.29.2", - "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.2.tgz", - "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.29.0" + "@babel/types": "^7.29.7" }, "bin": { "parser": "bin/babel-parser.js" @@ -305,9 +305,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.29.2", - "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.29.2.tgz", - "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", "dev": true, "license": "MIT", "engines": { @@ -315,33 +315,33 @@ } }, "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", + "@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.3.1" }, "engines": { @@ -349,14 +349,14 @@ } }, "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -396,9 +396,9 @@ } }, "node_modules/@csstools/css-calc": { - "version": "3.2.0", - "resolved": "https://registry.npmmirror.com/@csstools/css-calc/-/css-calc-3.2.0.tgz", - "integrity": "sha512-bR9e6o2BDB12jzN/gIbjHa5wLJ4UjD1CB9pM7ehlc0ddk6EBz+yYS1EV2MF55/HUxrHcB/hehAyt5vhsA3hx7w==", + "version": "3.2.1", + "resolved": "https://registry.npmmirror.com/@csstools/css-calc/-/css-calc-3.2.1.tgz", + "integrity": "sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==", "dev": true, "funding": [ { @@ -420,9 +420,9 @@ } }, "node_modules/@csstools/css-color-parser": { - "version": "4.1.0", - "resolved": "https://registry.npmmirror.com/@csstools/css-color-parser/-/css-color-parser-4.1.0.tgz", - "integrity": "sha512-U0KhLYmy2GVj6q4T3WaAe6NPuFYCPQoE3b0dRGxejWDgcPp8TP7S5rVdM5ZrFaqu4N67X8YaPBw14dQSYx3IyQ==", + "version": "4.1.8", + "resolved": "https://registry.npmmirror.com/@csstools/css-color-parser/-/css-color-parser-4.1.8.tgz", + "integrity": "sha512-3chWb7PRLijpJpPIKkDxdu6IBeO5MrFACND57On0j8OPpc0wZibcGc3xAHrSEbOx/KDRyMHoIxGn0w1PhXMYHw==", "dev": true, "funding": [ { @@ -437,7 +437,7 @@ "license": "MIT", "dependencies": { "@csstools/color-helpers": "^6.0.2", - "@csstools/css-calc": "^3.2.0" + "@csstools/css-calc": "^3.2.1" }, "engines": { "node": ">=20.19.0" @@ -471,9 +471,9 @@ } }, "node_modules/@csstools/css-syntax-patches-for-csstree": { - "version": "1.1.3", - "resolved": "https://registry.npmmirror.com/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.3.tgz", - "integrity": "sha512-SH60bMfrRCJF3morcdk57WklujF4Jr/EsQUzqkarfHXEFcAR1gg7fS/chAE922Sehgzc1/+Tz5H3Ypa1HiEKrg==", + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.5.tgz", + "integrity": "sha512-oNjBvzLq2GPZtJphCjLqXow/cHySHSgtxvKZb7OqSZ/xHgw6NWNhfad+6AB9cLeVm6eA9d/qMll3JdEHjy6M+A==", "dev": true, "funding": [ { @@ -607,9 +607,9 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.5.5", - "resolved": "https://registry.npmmirror.com/@eslint/config-helpers/-/config-helpers-0.5.5.tgz", - "integrity": "sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==", + "version": "0.6.0", + "resolved": "https://registry.npmmirror.com/@eslint/config-helpers/-/config-helpers-0.6.0.tgz", + "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -664,9 +664,9 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.7.1", - "resolved": "https://registry.npmmirror.com/@eslint/plugin-kit/-/plugin-kit-0.7.1.tgz", - "integrity": "sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==", + "version": "0.7.2", + "resolved": "https://registry.npmmirror.com/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", + "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -678,9 +678,9 @@ } }, "node_modules/@exodus/bytes": { - "version": "1.15.0", - "resolved": "https://registry.npmmirror.com/@exodus/bytes/-/bytes-1.15.0.tgz", - "integrity": "sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==", + "version": "1.15.1", + "resolved": "https://registry.npmmirror.com/@exodus/bytes/-/bytes-1.15.1.tgz", + "integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==", "dev": true, "license": "MIT", "engines": { @@ -835,14 +835,14 @@ } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.4", - "resolved": "https://registry.npmmirror.com/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", - "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz", + "integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@tybys/wasm-util": "^0.10.1" + "@tybys/wasm-util": "^0.10.2" }, "funding": { "type": "github", @@ -855,7 +855,7 @@ }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, "license": "MIT", @@ -869,7 +869,7 @@ }, "node_modules/@nodelib/fs.stat": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, "license": "MIT", @@ -879,7 +879,7 @@ }, "node_modules/@nodelib/fs.walk": { "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, "license": "MIT", @@ -892,9 +892,9 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.127.0", - "resolved": "https://registry.npmmirror.com/@oxc-project/types/-/types-0.127.0.tgz", - "integrity": "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==", + "version": "0.133.0", + "resolved": "https://registry.npmmirror.com/@oxc-project/types/-/types-0.133.0.tgz", + "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", "dev": true, "license": "MIT", "funding": { @@ -909,9 +909,9 @@ "license": "MIT" }, "node_modules/@reduxjs/toolkit": { - "version": "2.11.2", - "resolved": "https://registry.npmmirror.com/@reduxjs/toolkit/-/toolkit-2.11.2.tgz", - "integrity": "sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==", + "version": "2.12.0", + "resolved": "https://registry.npmmirror.com/@reduxjs/toolkit/-/toolkit-2.12.0.tgz", + "integrity": "sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw==", "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.0.0", @@ -935,9 +935,9 @@ } }, "node_modules/@reduxjs/toolkit/node_modules/immer": { - "version": "11.1.4", - "resolved": "https://registry.npmmirror.com/immer/-/immer-11.1.4.tgz", - "integrity": "sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw==", + "version": "11.1.8", + "resolved": "https://registry.npmmirror.com/immer/-/immer-11.1.8.tgz", + "integrity": "sha512-/tbkHMW7y10Lx6i1crLjD4/OhNkRG+Fo7byZHtah0547nIeXYcpIXaUh0IAQY6gO5459qpGGYapcEOHtFXkIuA==", "license": "MIT", "funding": { "type": "opencollective", @@ -945,9 +945,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmmirror.com/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.17.tgz", - "integrity": "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==", + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz", + "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", "cpu": [ "arm64" ], @@ -962,9 +962,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmmirror.com/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.17.tgz", - "integrity": "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==", + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz", + "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", "cpu": [ "arm64" ], @@ -979,9 +979,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmmirror.com/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.17.tgz", - "integrity": "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==", + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz", + "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", "cpu": [ "x64" ], @@ -996,9 +996,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmmirror.com/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.17.tgz", - "integrity": "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==", + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz", + "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", "cpu": [ "x64" ], @@ -1013,9 +1013,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.17.tgz", - "integrity": "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==", + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz", + "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", "cpu": [ "arm" ], @@ -1030,9 +1030,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==", + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz", + "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", "cpu": [ "arm64" ], @@ -1047,9 +1047,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.17.tgz", - "integrity": "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==", + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz", + "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", "cpu": [ "arm64" ], @@ -1064,9 +1064,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==", + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz", + "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", "cpu": [ "ppc64" ], @@ -1081,9 +1081,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==", + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz", + "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", "cpu": [ "s390x" ], @@ -1098,9 +1098,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==", + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz", + "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", "cpu": [ "x64" ], @@ -1115,9 +1115,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.17.tgz", - "integrity": "sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==", + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz", + "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", "cpu": [ "x64" ], @@ -1132,9 +1132,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmmirror.com/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.17.tgz", - "integrity": "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==", + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz", + "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", "cpu": [ "arm64" ], @@ -1149,9 +1149,9 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmmirror.com/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.17.tgz", - "integrity": "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==", + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz", + "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", "cpu": [ "wasm32" ], @@ -1168,9 +1168,9 @@ } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmmirror.com/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.17.tgz", - "integrity": "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==", + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz", + "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", "cpu": [ "arm64" ], @@ -1184,27 +1184,10 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmmirror.com/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.17.tgz", - "integrity": "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.7", - "resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.7.tgz", - "integrity": "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==", + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", "dev": true, "license": "MIT" }, @@ -1311,9 +1294,9 @@ } }, "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmmirror.com/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "version": "0.10.2", + "resolved": "https://registry.npmmirror.com/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", "dev": true, "license": "MIT", "optional": true, @@ -1418,9 +1401,9 @@ "license": "MIT" }, "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "version": "1.0.9", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "dev": true, "license": "MIT" }, @@ -1432,19 +1415,19 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.12.2", - "resolved": "https://registry.npmmirror.com/@types/node/-/node-24.12.2.tgz", - "integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==", + "version": "24.13.2", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-24.13.2.tgz", + "integrity": "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "undici-types": "~7.18.0" } }, "node_modules/@types/react": { - "version": "19.2.14", - "resolved": "https://registry.npmmirror.com/@types/react/-/react-19.2.14.tgz", - "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "version": "19.2.17", + "resolved": "https://registry.npmmirror.com/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", "devOptional": true, "license": "MIT", "dependencies": { @@ -1493,17 +1476,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.59.1", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.1.tgz", - "integrity": "sha512-BOziFIfE+6osHO9FoJG4zjoHUcvI7fTNBSpdAwrNH0/TLvzjsk2oo8XSSOT2HhqUyhZPfHv4UOffoJ9oEEQ7Ag==", + "version": "8.61.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.61.1.tgz", + "integrity": "sha512-ZPlVl3PB3et/59Ne0fv/sci6ZXz4T4Hp4nTJ56i/Y0gR89ARb+KphojTq6j+56E5PIezmOIOOWyY+aWQFd+IkQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.59.1", - "@typescript-eslint/type-utils": "8.59.1", - "@typescript-eslint/utils": "8.59.1", - "@typescript-eslint/visitor-keys": "8.59.1", + "@typescript-eslint/scope-manager": "8.61.1", + "@typescript-eslint/type-utils": "8.61.1", + "@typescript-eslint/utils": "8.61.1", + "@typescript-eslint/visitor-keys": "8.61.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -1516,7 +1499,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.59.1", + "@typescript-eslint/parser": "^8.61.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } @@ -1532,16 +1515,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.59.1", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-8.59.1.tgz", - "integrity": "sha512-HDQH9O/47Dxi1ceDhBXdaldtf/WV9yRYMjbjCuNk3qnaTD564qwv61Y7+gTxwxRKzSrgO5uhtw584igXVuuZkA==", + "version": "8.61.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-8.61.1.tgz", + "integrity": "sha512-PJ5vePq5/ognBbrIcoC5+SHO5dfpeLPzP9FpLkzWrguoYQEeeSjlJpVwOpo1JRSTEi7dRcwNy4h4dzV70PqHcg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.59.1", - "@typescript-eslint/types": "8.59.1", - "@typescript-eslint/typescript-estree": "8.59.1", - "@typescript-eslint/visitor-keys": "8.59.1", + "@typescript-eslint/scope-manager": "8.61.1", + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/typescript-estree": "8.61.1", + "@typescript-eslint/visitor-keys": "8.61.1", "debug": "^4.4.3" }, "engines": { @@ -1557,14 +1540,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.59.1", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/project-service/-/project-service-8.59.1.tgz", - "integrity": "sha512-+MuHQlHiEr00Of/IQbE/MmEoi44znZHbR/Pz7Opq4HryUOlRi+/44dro9Ycy8Fyo+/024IWtw8m4JUMCGTYxDg==", + "version": "8.61.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/project-service/-/project-service-8.61.1.tgz", + "integrity": "sha512-PrC4JYGmR241lYnfhmKGTXkFqv8+ymbTFgSAY0fVXpY82/QkMw5TZPl+vGzuDDU2QYJk9fIDOBTntF+yDv9LEA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.59.1", - "@typescript-eslint/types": "^8.59.1", + "@typescript-eslint/tsconfig-utils": "^8.61.1", + "@typescript-eslint/types": "^8.61.1", "debug": "^4.4.3" }, "engines": { @@ -1579,14 +1562,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.59.1", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-8.59.1.tgz", - "integrity": "sha512-LwuHQI4pDOYVKvmH2dkaJo6YZCSgouVgnS/z7yBPKBMvgtBvyLqiLy9Z6b7+m/TRcX1NFYUqZetI5Y+aT4GEfg==", + "version": "8.61.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-8.61.1.tgz", + "integrity": "sha512-L2bdIeoQS8FlKAvONAr20w6OcLXeB+qiDKbAooS9A0Ben+iSIkBef0FxqwKWYqt5sa0i4KJtxVyVmhMylKzF5w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.59.1", - "@typescript-eslint/visitor-keys": "8.59.1" + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/visitor-keys": "8.61.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1597,9 +1580,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.59.1", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.1.tgz", - "integrity": "sha512-/0nEyPbX7gRsk0Uwfe4ALwwgxuA66d/l2mhRDNlAvaj4U3juhUtJNq0DsY8M2AYwwb9rEq2hrC3IcIcEt++iJA==", + "version": "8.61.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.61.1.tgz", + "integrity": "sha512-UN/H4di+OO7EWx2ovME+8t31YO+KVnK0RRKEHR3kOt21/Ay8BOq3M1OMvWs5vNiqcFCYGYoxK3MXPZzmMUE+yg==", "dev": true, "license": "MIT", "engines": { @@ -1614,15 +1597,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.59.1", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-8.59.1.tgz", - "integrity": "sha512-klWPBR2ciQHS3f++ug/mVnWKPjBUo7icEL3FAO1lhAR1Z1i5NQYZ1EannMSRYcq5qCv5wNALlXr6fksRHyYl7w==", + "version": "8.61.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-8.61.1.tgz", + "integrity": "sha512-GYRicKmVK0C4fsKgaACaknOUAq9Oa2kwsjnpFhFcS/5p4Ht5IP9OVLbgIgcK4SRk92nVHFluurg1lumD9dBcLw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.59.1", - "@typescript-eslint/typescript-estree": "8.59.1", - "@typescript-eslint/utils": "8.59.1", + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/typescript-estree": "8.61.1", + "@typescript-eslint/utils": "8.61.1", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -1639,9 +1622,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.59.1", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/types/-/types-8.59.1.tgz", - "integrity": "sha512-ZDCjgccSdYPw5Bxh+my4Z0lJU96ZDN7jbBzvmEn0FZx3RtU1C7VWl6NbDx94bwY3V5YsgwRzJPOgeY2Q/nLG8A==", + "version": "8.61.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/types/-/types-8.61.1.tgz", + "integrity": "sha512-G+CRlPqLv7Bz1IZVs03x5K59F1veqL0EJUROAdGhKsEq8qOiRiZbI+HUojPq5l0fEGOKModD9br6lObhB8zkoA==", "dev": true, "license": "MIT", "engines": { @@ -1653,16 +1636,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.59.1", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.1.tgz", - "integrity": "sha512-OUd+vJS05sSkOip+BkZ/2NS8RMxrAAJemsC6vU3kmfLyeaJT0TftHkV9mcx2107MmsBVXXexhVu4F0TZXyMl4g==", + "version": "8.61.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.61.1.tgz", + "integrity": "sha512-u+oQD3BqYWPc8YV9Zab4vaJElJuwOLPRc10Jm1o/qS+6Qwen14HCWwx0Seo4LnSn2wxea2Ik8DxPt2/FHmuhrg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.59.1", - "@typescript-eslint/tsconfig-utils": "8.59.1", - "@typescript-eslint/types": "8.59.1", - "@typescript-eslint/visitor-keys": "8.59.1", + "@typescript-eslint/project-service": "8.61.1", + "@typescript-eslint/tsconfig-utils": "8.61.1", + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/visitor-keys": "8.61.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -1681,9 +1664,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.8.5", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -1694,16 +1677,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.59.1", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-8.59.1.tgz", - "integrity": "sha512-3pIeoXhCeYH9FSCBI8P3iNwJlGuzPlYKkTlen2O9T1DSeeg8UG8jstq6BLk+Mda0qup7mgk4z4XL4OzRaxZ8LA==", + "version": "8.61.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-8.61.1.tgz", + "integrity": "sha512-1+P/3Dj6jvtybE1q0HQ6yBt/gq+oKJyLdEv4HdnqasaEXRSYCAsD59mXEVQnM/ULNdQxbX77tdG4jPRjIS6knA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.59.1", - "@typescript-eslint/types": "8.59.1", - "@typescript-eslint/typescript-estree": "8.59.1" + "@typescript-eslint/scope-manager": "8.61.1", + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/typescript-estree": "8.61.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1718,13 +1701,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.59.1", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.1.tgz", - "integrity": "sha512-LdDNl6C5iJExcM0Yh0PwAIBb9PrSiCsWamF/JyEZawm3kFDnRoaq3LGE4bpyRao/fWeGKKyw7icx0YxrLFC5Cg==", + "version": "8.61.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.61.1.tgz", + "integrity": "sha512-6fJ9MHWtK14C1DSkiMlHUSOmrVebL7150xZJBlJiL62jjhIA4JmOq6flwBgDxIdBKKdoiZRel+dfPD5MLfny3w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/types": "8.61.1", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -1736,13 +1719,13 @@ } }, "node_modules/@vitejs/plugin-react": { - "version": "6.0.1", - "resolved": "https://registry.npmmirror.com/@vitejs/plugin-react/-/plugin-react-6.0.1.tgz", - "integrity": "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==", + "version": "6.0.2", + "resolved": "https://registry.npmmirror.com/@vitejs/plugin-react/-/plugin-react-6.0.2.tgz", + "integrity": "sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg==", "dev": true, "license": "MIT", "dependencies": { - "@rolldown/pluginutils": "1.0.0-rc.7" + "@rolldown/pluginutils": "^1.0.0" }, "engines": { "node": "^20.19.0 || >=22.12.0" @@ -1762,16 +1745,16 @@ } }, "node_modules/@vitest/expect": { - "version": "4.1.5", - "resolved": "https://registry.npmmirror.com/@vitest/expect/-/expect-4.1.5.tgz", - "integrity": "sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==", + "version": "4.1.9", + "resolved": "https://registry.npmmirror.com/@vitest/expect/-/expect-4.1.9.tgz", + "integrity": "sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.5", - "@vitest/utils": "4.1.5", + "@vitest/spy": "4.1.9", + "@vitest/utils": "4.1.9", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" }, @@ -1780,13 +1763,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "4.1.5", - "resolved": "https://registry.npmmirror.com/@vitest/mocker/-/mocker-4.1.5.tgz", - "integrity": "sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==", + "version": "4.1.9", + "resolved": "https://registry.npmmirror.com/@vitest/mocker/-/mocker-4.1.9.tgz", + "integrity": "sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.5", + "@vitest/spy": "4.1.9", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -1807,9 +1790,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.1.5", - "resolved": "https://registry.npmmirror.com/@vitest/pretty-format/-/pretty-format-4.1.5.tgz", - "integrity": "sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==", + "version": "4.1.9", + "resolved": "https://registry.npmmirror.com/@vitest/pretty-format/-/pretty-format-4.1.9.tgz", + "integrity": "sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==", "dev": true, "license": "MIT", "dependencies": { @@ -1820,13 +1803,13 @@ } }, "node_modules/@vitest/runner": { - "version": "4.1.5", - "resolved": "https://registry.npmmirror.com/@vitest/runner/-/runner-4.1.5.tgz", - "integrity": "sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==", + "version": "4.1.9", + "resolved": "https://registry.npmmirror.com/@vitest/runner/-/runner-4.1.9.tgz", + "integrity": "sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.5", + "@vitest/utils": "4.1.9", "pathe": "^2.0.3" }, "funding": { @@ -1834,14 +1817,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.1.5", - "resolved": "https://registry.npmmirror.com/@vitest/snapshot/-/snapshot-4.1.5.tgz", - "integrity": "sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==", + "version": "4.1.9", + "resolved": "https://registry.npmmirror.com/@vitest/snapshot/-/snapshot-4.1.9.tgz", + "integrity": "sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.5", - "@vitest/utils": "4.1.5", + "@vitest/pretty-format": "4.1.9", + "@vitest/utils": "4.1.9", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -1850,9 +1833,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.1.5", - "resolved": "https://registry.npmmirror.com/@vitest/spy/-/spy-4.1.5.tgz", - "integrity": "sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==", + "version": "4.1.9", + "resolved": "https://registry.npmmirror.com/@vitest/spy/-/spy-4.1.9.tgz", + "integrity": "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==", "dev": true, "license": "MIT", "funding": { @@ -1860,13 +1843,13 @@ } }, "node_modules/@vitest/ui": { - "version": "4.1.5", - "resolved": "https://registry.npmmirror.com/@vitest/ui/-/ui-4.1.5.tgz", - "integrity": "sha512-3Z9HNFiV0IF1fk0JPiK+7kE1GcaIPefQQIBYur6PM5yFIq6agys3uqP/0t966e1wXfmjbRCHDe7qW236Xjwnag==", + "version": "4.1.9", + "resolved": "https://registry.npmmirror.com/@vitest/ui/-/ui-4.1.9.tgz", + "integrity": "sha512-U/cRvtqfEPj27FI1n9cyUvi4vXXdcLhjJiI+InYKdk8hP4VrS6RXOjGL7rfFaeBc37iRKANsR6eEzIoC7lmgBQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.5", + "@vitest/utils": "4.1.9", "fflate": "^0.8.2", "flatted": "^3.4.2", "pathe": "^2.0.3", @@ -1878,17 +1861,17 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "4.1.5" + "vitest": "4.1.9" } }, "node_modules/@vitest/utils": { - "version": "4.1.5", - "resolved": "https://registry.npmmirror.com/@vitest/utils/-/utils-4.1.5.tgz", - "integrity": "sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==", + "version": "4.1.9", + "resolved": "https://registry.npmmirror.com/@vitest/utils/-/utils-4.1.9.tgz", + "integrity": "sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.5", + "@vitest/pretty-format": "4.1.9", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" }, @@ -1897,9 +1880,9 @@ } }, "node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "version": "8.17.0", + "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", "dev": true, "license": "MIT", "bin": { @@ -1919,6 +1902,18 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmmirror.com/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/ajv": { "version": "6.15.0", "resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.15.0.tgz", @@ -1963,14 +1958,14 @@ }, "node_modules/any-promise": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "resolved": "https://registry.npmmirror.com/any-promise/-/any-promise-1.3.0.tgz", "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", "dev": true, "license": "MIT" }, "node_modules/anymatch": { "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, "license": "ISC", @@ -1982,22 +1977,9 @@ "node": ">= 8" } }, - "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/arg": { "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "resolved": "https://registry.npmmirror.com/arg/-/arg-5.0.2.tgz", "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", "dev": true, "license": "MIT" @@ -2030,7 +2012,7 @@ }, "node_modules/autoprefixer": { "version": "10.5.0", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.0.tgz", + "resolved": "https://registry.npmmirror.com/autoprefixer/-/autoprefixer-10.5.0.tgz", "integrity": "sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==", "dev": true, "funding": [ @@ -2066,13 +2048,14 @@ } }, "node_modules/axios": { - "version": "1.15.2", - "resolved": "https://registry.npmmirror.com/axios/-/axios-1.15.2.tgz", - "integrity": "sha512-wLrXxPtcrPTsNlJmKjkPnNPK2Ihe0hn0wGSaTEiHRPxwjvJwT3hKmXF4dpqxmPO9SoNb2FsYXj/xEo0gHN+D5A==", + "version": "1.18.0", + "resolved": "https://registry.npmmirror.com/axios/-/axios-1.18.0.tgz", + "integrity": "sha512-E32NzpYKp++W7XRe52rHiXV2ehxmh3wbdgO7MHeFM+vqxLBYHzt0ElkiImtOBxtOmyp0yoC8C6uESVV84Y2/hw==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.11", + "follow-redirects": "^1.16.0", "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, @@ -2087,9 +2070,9 @@ } }, "node_modules/baseline-browser-mapping": { - "version": "2.10.24", - "resolved": "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.24.tgz", - "integrity": "sha512-I2NkZOOrj2XuguvWCK6OVh9GavsNjZjK908Rq3mIBK25+GD8vPX5w2WdxVqnQ7xx3SrZJiCiZFu+/Oz50oSYSA==", + "version": "2.10.38", + "resolved": "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.38.tgz", + "integrity": "sha512-31/02mVB4yuQU6adKk5SlY6m+mxDwUq5KZkyYgnLrrKl7TEm1+3PyDtDBz2kOv/wxZz41GHsvV1A/u6RmiyBvw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -2111,7 +2094,7 @@ }, "node_modules/binary-extensions": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "dev": true, "license": "MIT", @@ -2123,9 +2106,9 @@ } }, "node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "version": "5.0.6", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "dev": true, "license": "MIT", "dependencies": { @@ -2137,7 +2120,7 @@ }, "node_modules/braces": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "license": "MIT", @@ -2182,6 +2165,19 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer-image-size": { + "version": "0.6.4", + "resolved": "https://registry.npmmirror.com/buffer-image-size/-/buffer-image-size-0.6.4.tgz", + "integrity": "sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + }, + "engines": { + "node": ">=4.0" + } + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -2197,7 +2193,7 @@ }, "node_modules/camelcase-css": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "resolved": "https://registry.npmmirror.com/camelcase-css/-/camelcase-css-2.0.1.tgz", "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", "dev": true, "license": "MIT", @@ -2206,9 +2202,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001791", - "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz", - "integrity": "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==", + "version": "1.0.30001799", + "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", + "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==", "dev": true, "funding": [ { @@ -2238,7 +2234,7 @@ }, "node_modules/chokidar": { "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, "license": "MIT", @@ -2263,7 +2259,7 @@ }, "node_modules/chokidar/node_modules/glob-parent": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "license": "ISC", @@ -2297,7 +2293,7 @@ }, "node_modules/commander": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "resolved": "https://registry.npmmirror.com/commander/-/commander-4.1.1.tgz", "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", "dev": true, "license": "MIT", @@ -2363,7 +2359,7 @@ }, "node_modules/cssesc": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "resolved": "https://registry.npmmirror.com/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "dev": true, "license": "MIT", @@ -2530,7 +2526,6 @@ "version": "4.4.3", "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2595,14 +2590,14 @@ }, "node_modules/didyoumean": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "resolved": "https://registry.npmmirror.com/didyoumean/-/didyoumean-1.2.2.tgz", "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", "dev": true, "license": "Apache-2.0" }, "node_modules/dlv": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "resolved": "https://registry.npmmirror.com/dlv/-/dlv-1.1.3.tgz", "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "dev": true, "license": "MIT" @@ -2640,9 +2635,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.344", - "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.344.tgz", - "integrity": "sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg==", + "version": "1.5.376", + "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.376.tgz", + "integrity": "sha512-cUVA7/RvbFTEuw/i3obUwDTRIXojaxkResf+ibByPFxjc6XK3VNtcQXV0NSbAlJ0FMjcJGgftVVB4Qo184EXvA==", "dev": true, "license": "ISC" }, @@ -2685,9 +2680,9 @@ "license": "MIT" }, "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -2712,9 +2707,9 @@ } }, "node_modules/es-toolkit": { - "version": "1.46.0", - "resolved": "https://registry.npmmirror.com/es-toolkit/-/es-toolkit-1.46.0.tgz", - "integrity": "sha512-IToJ6ct9OLl5zz6WsC/1vZEwfSZ7Myil+ygl5Tf30Xjn9AEkzNB4kqp2G7VUJKF1DtTx/ra5M5KLlXvzOg51BA==", + "version": "1.48.1", + "resolved": "https://registry.npmmirror.com/es-toolkit/-/es-toolkit-1.48.1.tgz", + "integrity": "sha512-wfnXlwd5I75eXRtdD2vuEs50xHHESECDsGD7yiQnfFVNoa5522NwXEbmgo98LfiukSQHs+mBM7/YG3qKJB9/mQ==", "license": "MIT", "workspaces": [ "docs", @@ -2745,18 +2740,21 @@ } }, "node_modules/eslint": { - "version": "10.2.1", - "resolved": "https://registry.npmmirror.com/eslint/-/eslint-10.2.1.tgz", - "integrity": "sha512-wiyGaKsDgqXvF40P8mDwiUp/KQjE1FdrIEJsM8PZ3XCiniTMXS3OHWWUe5FI5agoCnr8x4xPrTDZuxsBlNHl+Q==", + "version": "10.5.0", + "resolved": "https://registry.npmmirror.com/eslint/-/eslint-10.5.0.tgz", + "integrity": "sha512-1y+7C+vi12bUK1IpZeaV3gsH9fHLBmPvYmPx42pvT/E9yG0IC8g3PUZZgp0+JLJl7ZDK0flc2gc+Aw9dpCvIsQ==", "dev": true, "license": "MIT", + "workspaces": [ + "packages/*" + ], "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", - "@eslint/config-helpers": "^0.5.5", + "@eslint/config-helpers": "^0.6.0", "@eslint/core": "^1.2.1", - "@eslint/plugin-kit": "^0.7.1", + "@eslint/plugin-kit": "^0.7.2", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", @@ -2821,9 +2819,9 @@ } }, "node_modules/eslint-plugin-react-refresh": { - "version": "0.5.2", - "resolved": "https://registry.npmmirror.com/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.2.tgz", - "integrity": "sha512-hmgTH57GfzoTFjVN0yBwTggnsVUF2tcqi7RJZHqi9lIezSs4eFyAMktA68YD4r5kNw1mxyY4dmkyoFDb3FIqrA==", + "version": "0.5.3", + "resolved": "https://registry.npmmirror.com/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.3.tgz", + "integrity": "sha512-5EMmLCV98Pi4o/f/3DP/v/tNqLHMIc9I8LKClNDWhZ9JTho89/kQcitCXQBMG7sAfVRK0Ie3T2EDOzp1YXYiVA==", "dev": true, "license": "MIT", "peerDependencies": { @@ -2961,7 +2959,7 @@ }, "node_modules/fast-glob": { "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.3.tgz", "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, "license": "MIT", @@ -2978,7 +2976,7 @@ }, "node_modules/fast-glob/node_modules/glob-parent": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "license": "ISC", @@ -3005,7 +3003,7 @@ }, "node_modules/fastq": { "version": "1.20.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.20.1.tgz", "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", "dev": true, "license": "ISC", @@ -3013,28 +3011,10 @@ "reusify": "^1.0.4" } }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, "node_modules/fflate": { - "version": "0.8.2", - "resolved": "https://registry.npmmirror.com/fflate/-/fflate-0.8.2.tgz", - "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "version": "0.8.3", + "resolved": "https://registry.npmmirror.com/fflate/-/fflate-0.8.3.tgz", + "integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==", "dev": true, "license": "MIT" }, @@ -3053,7 +3033,7 @@ }, "node_modules/fill-range": { "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "license": "MIT", @@ -3123,16 +3103,16 @@ } }, "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "version": "4.0.6", + "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" + "hasown": "^2.0.4", + "mime-types": "^2.1.35" }, "engines": { "node": ">= 6" @@ -3237,9 +3217,9 @@ } }, "node_modules/globals": { - "version": "17.5.0", - "resolved": "https://registry.npmmirror.com/globals/-/globals-17.5.0.tgz", - "integrity": "sha512-qoV+HK2yFl/366t2/Cb3+xxPUo5BuMynomoDmiaZBIdbs+0pYbjfZU+twLhGKp4uCZ/+NbtpVepH5bGCxRyy2g==", + "version": "17.6.0", + "resolved": "https://registry.npmmirror.com/globals/-/globals-17.6.0.tgz", + "integrity": "sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==", "dev": true, "license": "MIT", "engines": { @@ -3262,18 +3242,19 @@ } }, "node_modules/happy-dom": { - "version": "20.9.0", - "resolved": "https://registry.npmmirror.com/happy-dom/-/happy-dom-20.9.0.tgz", - "integrity": "sha512-GZZ9mKe8r646NUAf/zemnGbjYh4Bt8/MqASJY+pSm5ZDtc3YQox+4gsLI7yi1hba6o+eCsGxpHn5+iEVn31/FQ==", + "version": "20.10.6", + "resolved": "https://registry.npmmirror.com/happy-dom/-/happy-dom-20.10.6.tgz", + "integrity": "sha512-6QD0ilzDDt93tX44y8tbmZdAcdTRYDhUP+Asgi6pC8Pp5IA3cvaZGyoVN/EGtlq9ziT65iPuBBn3ASLr6hCgVw==", "dev": true, "license": "MIT", "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", + "buffer-image-size": "^0.6.4", "entities": "^7.0.1", "whatwg-mimetype": "^3.0.0", - "ws": "^8.18.3" + "ws": "^8.21.0" }, "engines": { "node": ">=20.0.0" @@ -3307,9 +3288,9 @@ } }, "node_modules/hasown": { - "version": "2.0.3", - "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.3.tgz", - "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -3348,6 +3329,19 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz", @@ -3399,7 +3393,7 @@ }, "node_modules/is-binary-path": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, "license": "MIT", @@ -3412,7 +3406,7 @@ }, "node_modules/is-core-module": { "version": "2.16.2", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.16.2.tgz", "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", "dev": true, "license": "MIT", @@ -3451,7 +3445,7 @@ }, "node_modules/is-number": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, "license": "MIT", @@ -3475,7 +3469,7 @@ }, "node_modules/jiti": { "version": "1.21.7", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "resolved": "https://registry.npmmirror.com/jiti/-/jiti-1.21.7.tgz", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "dev": true, "license": "MIT", @@ -3491,9 +3485,9 @@ "license": "MIT" }, "node_modules/jsdom": { - "version": "29.1.0", - "resolved": "https://registry.npmmirror.com/jsdom/-/jsdom-29.1.0.tgz", - "integrity": "sha512-YNUc7fB9QuvSSQWfrH0xF+TyABkxUwx8sswgIDaCrw4Hol8BghdZDkITtZheRJeMtzWlnTfsM3bBBusRvpO1wg==", + "version": "29.1.1", + "resolved": "https://registry.npmmirror.com/jsdom/-/jsdom-29.1.1.tgz", + "integrity": "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==", "dev": true, "license": "MIT", "dependencies": { @@ -3532,9 +3526,9 @@ } }, "node_modules/jsdom/node_modules/lru-cache": { - "version": "11.3.5", - "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-11.3.5.tgz", - "integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==", + "version": "11.5.1", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-11.5.1.tgz", + "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -3885,7 +3879,7 @@ }, "node_modules/lilconfig": { "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "resolved": "https://registry.npmmirror.com/lilconfig/-/lilconfig-3.1.3.tgz", "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", "dev": true, "license": "MIT", @@ -3898,7 +3892,7 @@ }, "node_modules/lines-and-columns": { "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "resolved": "https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true, "license": "MIT" @@ -3981,7 +3975,7 @@ }, "node_modules/merge2": { "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, "license": "MIT", @@ -3991,7 +3985,7 @@ }, "node_modules/micromatch": { "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "license": "MIT", @@ -4003,19 +3997,6 @@ "node": ">=8.6" } }, - "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", @@ -4088,12 +4069,11 @@ "version": "2.1.3", "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/mz": { "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "resolved": "https://registry.npmmirror.com/mz/-/mz-2.7.0.tgz", "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", "dev": true, "license": "MIT", @@ -4104,9 +4084,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.15", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.15.tgz", + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", "dev": true, "funding": [ { @@ -4130,15 +4110,18 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.38", - "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.38.tgz", - "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==", + "version": "2.0.48", + "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.48.tgz", + "integrity": "sha512-1uz8041X6LoI6ZSdZacM9lVY28vuzDlSKitnpbSNK0RfKoIJkX29NBPVEFXhnuSuEOA9Ww0xnPJ+ILWbGAv8DA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/normalize-path": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true, "license": "MIT", @@ -4148,7 +4131,7 @@ }, "node_modules/object-assign": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "dev": true, "license": "MIT", @@ -4158,7 +4141,7 @@ }, "node_modules/object-hash": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "resolved": "https://registry.npmmirror.com/object-hash/-/object-hash-3.0.0.tgz", "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", "dev": true, "license": "MIT", @@ -4167,15 +4150,18 @@ } }, "node_modules/obug": { - "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/obug/-/obug-2.1.1.tgz", - "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/obug/-/obug-2.1.3.tgz", + "integrity": "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==", "dev": true, "funding": [ "https://github.com/sponsors/sxzz", "https://opencollective.com/debug" ], - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } }, "node_modules/optionator": { "version": "0.9.4", @@ -4275,7 +4261,7 @@ }, "node_modules/path-parse": { "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true, "license": "MIT" @@ -4295,13 +4281,13 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "version": "2.3.2", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { - "node": ">=12" + "node": ">=8.6" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" @@ -4309,7 +4295,7 @@ }, "node_modules/pify": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "resolved": "https://registry.npmmirror.com/pify/-/pify-2.3.0.tgz", "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", "dev": true, "license": "MIT", @@ -4319,7 +4305,7 @@ }, "node_modules/pirates": { "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "resolved": "https://registry.npmmirror.com/pirates/-/pirates-4.0.7.tgz", "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", "dev": true, "license": "MIT", @@ -4328,9 +4314,9 @@ } }, "node_modules/postcss": { - "version": "8.5.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", - "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", + "version": "8.5.15", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", "dev": true, "funding": [ { @@ -4348,7 +4334,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -4358,7 +4344,7 @@ }, "node_modules/postcss-import": { "version": "15.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "resolved": "https://registry.npmmirror.com/postcss-import/-/postcss-import-15.1.0.tgz", "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", "dev": true, "license": "MIT", @@ -4376,7 +4362,7 @@ }, "node_modules/postcss-js": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "resolved": "https://registry.npmmirror.com/postcss-js/-/postcss-js-4.1.0.tgz", "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", "dev": true, "funding": [ @@ -4402,7 +4388,7 @@ }, "node_modules/postcss-load-config": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "resolved": "https://registry.npmmirror.com/postcss-load-config/-/postcss-load-config-6.0.1.tgz", "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", "dev": true, "funding": [ @@ -4445,7 +4431,7 @@ }, "node_modules/postcss-nested": { "version": "6.2.0", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "resolved": "https://registry.npmmirror.com/postcss-nested/-/postcss-nested-6.2.0.tgz", "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", "dev": true, "funding": [ @@ -4470,9 +4456,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", - "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "version": "6.1.4", + "resolved": "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", + "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4545,7 +4531,7 @@ }, "node_modules/queue-microtask": { "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true, "funding": [ @@ -4565,37 +4551,37 @@ "license": "MIT" }, "node_modules/react": { - "version": "19.2.5", - "resolved": "https://registry.npmmirror.com/react/-/react-19.2.5.tgz", - "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==", + "version": "19.2.7", + "resolved": "https://registry.npmmirror.com/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "19.2.5", - "resolved": "https://registry.npmmirror.com/react-dom/-/react-dom-19.2.5.tgz", - "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==", + "version": "19.2.7", + "resolved": "https://registry.npmmirror.com/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", "license": "MIT", "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.2.5" + "react": "^19.2.7" } }, "node_modules/react-is": { - "version": "19.2.5", - "resolved": "https://registry.npmmirror.com/react-is/-/react-is-19.2.5.tgz", - "integrity": "sha512-Dn0t8IQhCmeIT3wu+Apm1/YVsJXsGWi6k4sPdnBIdqMVtHtv0IGi6dcpNpNkNac0zB2uUAqNX3MHzN8c+z2rwQ==", + "version": "19.2.7", + "resolved": "https://registry.npmmirror.com/react-is/-/react-is-19.2.7.tgz", + "integrity": "sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A==", "license": "MIT", "peer": true }, "node_modules/react-redux": { - "version": "9.2.0", - "resolved": "https://registry.npmmirror.com/react-redux/-/react-redux-9.2.0.tgz", - "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "version": "9.3.0", + "resolved": "https://registry.npmmirror.com/react-redux/-/react-redux-9.3.0.tgz", + "integrity": "sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==", "license": "MIT", "dependencies": { "@types/use-sync-external-store": "^0.0.6", @@ -4616,9 +4602,9 @@ } }, "node_modules/react-router": { - "version": "7.14.2", - "resolved": "https://registry.npmmirror.com/react-router/-/react-router-7.14.2.tgz", - "integrity": "sha512-yCqNne6I8IB6rVCH7XUvlBK7/QKyqypBFGv+8dj4QBFJiiRX+FG7/nkdAvGElyvVZ/HQP5N19wzteuTARXi5Gw==", + "version": "7.18.0", + "resolved": "https://registry.npmmirror.com/react-router/-/react-router-7.18.0.tgz", + "integrity": "sha512-pTTGt8J+ji1NOmYnjzT+bAJy/1zD+Jp4ziO6cL7T3ZLvXKtusO7BpFqlRXitqpcPVqllsIXFHRMt+2/k3Xn6HQ==", "license": "MIT", "dependencies": { "cookie": "^1.0.1", @@ -4638,12 +4624,12 @@ } }, "node_modules/react-router-dom": { - "version": "7.14.2", - "resolved": "https://registry.npmmirror.com/react-router-dom/-/react-router-dom-7.14.2.tgz", - "integrity": "sha512-YZcM5ES8jJSM+KrJ9BdvHHqlnGTg5tH3sC5ChFRj4inosKctdyzBDhOyyHdGk597q2OT6NTrCA1OvB/YDwfekQ==", + "version": "7.18.0", + "resolved": "https://registry.npmmirror.com/react-router-dom/-/react-router-dom-7.18.0.tgz", + "integrity": "sha512-Fi0yY6kgtKae/Th2xibdWK0KSdYZ4B53Gyf6wRtomOKWgpNm7H7+DyfDhncdz9FKbpS+1jmDhg3F4WoGJ+yFOA==", "license": "MIT", "dependencies": { - "react-router": "7.14.2" + "react-router": "7.18.0" }, "engines": { "node": ">=20.0.0" @@ -4655,7 +4641,7 @@ }, "node_modules/read-cache": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "resolved": "https://registry.npmmirror.com/read-cache/-/read-cache-1.0.0.tgz", "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", "dev": true, "license": "MIT", @@ -4665,7 +4651,7 @@ }, "node_modules/readdirp": { "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, "license": "MIT", @@ -4676,19 +4662,6 @@ "node": ">=8.10.0" } }, - "node_modules/readdirp/node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/recharts": { "version": "3.8.1", "resolved": "https://registry.npmmirror.com/recharts/-/recharts-3.8.1.tgz", @@ -4766,7 +4739,7 @@ }, "node_modules/resolve": { "version": "1.22.12", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.12.tgz", "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", "dev": true, "license": "MIT", @@ -4788,7 +4761,7 @@ }, "node_modules/reusify": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "resolved": "https://registry.npmmirror.com/reusify/-/reusify-1.1.0.tgz", "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "dev": true, "license": "MIT", @@ -4798,14 +4771,14 @@ } }, "node_modules/rolldown": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmmirror.com/rolldown/-/rolldown-1.0.0-rc.17.tgz", - "integrity": "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==", + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/rolldown/-/rolldown-1.0.3.tgz", + "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.127.0", - "@rolldown/pluginutils": "1.0.0-rc.17" + "@oxc-project/types": "=0.133.0", + "@rolldown/pluginutils": "^1.0.0" }, "bin": { "rolldown": "bin/cli.mjs" @@ -4814,33 +4787,43 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.17", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.17", - "@rolldown/binding-darwin-x64": "1.0.0-rc.17", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.17", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.17", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.17", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.17", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.17", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.17", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.17", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.17" - } - }, - "node_modules/rolldown/node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.17.tgz", - "integrity": "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==", + "@rolldown/binding-android-arm64": "1.0.3", + "@rolldown/binding-darwin-arm64": "1.0.3", + "@rolldown/binding-darwin-x64": "1.0.3", + "@rolldown/binding-freebsd-x64": "1.0.3", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", + "@rolldown/binding-linux-arm64-gnu": "1.0.3", + "@rolldown/binding-linux-arm64-musl": "1.0.3", + "@rolldown/binding-linux-ppc64-gnu": "1.0.3", + "@rolldown/binding-linux-s390x-gnu": "1.0.3", + "@rolldown/binding-linux-x64-gnu": "1.0.3", + "@rolldown/binding-linux-x64-musl": "1.0.3", + "@rolldown/binding-openharmony-arm64": "1.0.3", + "@rolldown/binding-wasm32-wasi": "1.0.3", + "@rolldown/binding-win32-arm64-msvc": "1.0.3", + "@rolldown/binding-win32-x64-msvc": "1.0.3" + } + }, + "node_modules/rolldown/node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz", + "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, "node_modules/run-parallel": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "resolved": "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "dev": true, "funding": [ @@ -4987,7 +4970,7 @@ }, "node_modules/sucrase": { "version": "3.35.1", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "resolved": "https://registry.npmmirror.com/sucrase/-/sucrase-3.35.1.tgz", "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", "dev": true, "license": "MIT", @@ -5010,7 +4993,7 @@ }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "resolved": "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true, "license": "MIT", @@ -5030,7 +5013,7 @@ }, "node_modules/tailwindcss": { "version": "3.4.19", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "resolved": "https://registry.npmmirror.com/tailwindcss/-/tailwindcss-3.4.19.tgz", "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", "dev": true, "license": "MIT", @@ -5068,7 +5051,7 @@ }, "node_modules/thenify": { "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "resolved": "https://registry.npmmirror.com/thenify/-/thenify-3.3.1.tgz", "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", "dev": true, "license": "MIT", @@ -5078,7 +5061,7 @@ }, "node_modules/thenify-all": { "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "resolved": "https://registry.npmmirror.com/thenify-all/-/thenify-all-1.6.0.tgz", "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", "dev": true, "license": "MIT", @@ -5103,9 +5086,9 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/tinyexec/-/tinyexec-1.1.1.tgz", - "integrity": "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==", + "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/tinyexec/-/tinyexec-1.2.4.tgz", + "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", "dev": true, "license": "MIT", "engines": { @@ -5113,9 +5096,9 @@ } }, "node_modules/tinyglobby": { - "version": "0.2.16", - "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.16.tgz", - "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "version": "0.2.17", + "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, "license": "MIT", "dependencies": { @@ -5129,6 +5112,37 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/tinyrainbow": { "version": "3.1.0", "resolved": "https://registry.npmmirror.com/tinyrainbow/-/tinyrainbow-3.1.0.tgz", @@ -5140,28 +5154,28 @@ } }, "node_modules/tldts": { - "version": "7.0.29", - "resolved": "https://registry.npmmirror.com/tldts/-/tldts-7.0.29.tgz", - "integrity": "sha512-JIXCerhudr/N6OWLwLF1HVsTTUo7ry6qHa5eWZEkiMuxsIiAACL55tGLfqfHfoH7QaMQUW8fngD7u7TxWexYQg==", + "version": "7.4.3", + "resolved": "https://registry.npmmirror.com/tldts/-/tldts-7.4.3.tgz", + "integrity": "sha512-A3BDQBeeukYPzB4QdQ1DtdlUmp4x2OCH8n5UVhEWbyANxNep8GavottKzd1xYKFJKjUgMyPT7EzOfnBO55s8Sg==", "dev": true, "license": "MIT", "dependencies": { - "tldts-core": "^7.0.29" + "tldts-core": "^7.4.3" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "7.0.29", - "resolved": "https://registry.npmmirror.com/tldts-core/-/tldts-core-7.0.29.tgz", - "integrity": "sha512-W99NuU7b1DcG3uJ3v9k9VztCH3WialNbBkBft5wCs8V8mexu0XQqaZEYb9l9RNNzK8+3EJ9PKWB0/RUtTQ/o+Q==", + "version": "7.4.3", + "resolved": "https://registry.npmmirror.com/tldts-core/-/tldts-core-7.4.3.tgz", + "integrity": "sha512-27ep5H9PzdBrNd5OFM/j3WCU8F3kPwM9D0BOaOf7uYfxMJfyr0K5Tjj69Gri+sZlh2WXd5buIm47NuPF29CDiw==", "dev": true, "license": "MIT" }, "node_modules/to-regex-range": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "license": "MIT", @@ -5223,7 +5237,7 @@ }, "node_modules/ts-interface-checker": { "version": "0.1.13", - "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "resolved": "https://registry.npmmirror.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", "dev": true, "license": "Apache-2.0" @@ -5251,7 +5265,7 @@ }, "node_modules/typescript": { "version": "6.0.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-6.0.3.tgz", "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", @@ -5264,16 +5278,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.59.1", - "resolved": "https://registry.npmmirror.com/typescript-eslint/-/typescript-eslint-8.59.1.tgz", - "integrity": "sha512-xqDcFVBmlrltH64lklOVp1wYxgJr6LVdg3NamBgH2OOQDLFdTKfIZXF5PfghrnXQKXZGTQs8tr1vL7fJvq8CTQ==", + "version": "8.61.1", + "resolved": "https://registry.npmmirror.com/typescript-eslint/-/typescript-eslint-8.61.1.tgz", + "integrity": "sha512-V7PayAfJokV3pEHgN7/v03D1SpujhRfQtYLbLIiBfDDncdg4PAiRBfoS4cnCANK4jmAPncczi59QO3afiXUlNw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.59.1", - "@typescript-eslint/parser": "8.59.1", - "@typescript-eslint/typescript-estree": "8.59.1", - "@typescript-eslint/utils": "8.59.1" + "@typescript-eslint/eslint-plugin": "8.61.1", + "@typescript-eslint/parser": "8.61.1", + "@typescript-eslint/typescript-estree": "8.61.1", + "@typescript-eslint/utils": "8.61.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5288,9 +5302,9 @@ } }, "node_modules/undici": { - "version": "7.25.0", - "resolved": "https://registry.npmmirror.com/undici/-/undici-7.25.0.tgz", - "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==", + "version": "7.28.0", + "resolved": "https://registry.npmmirror.com/undici/-/undici-7.28.0.tgz", + "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==", "dev": true, "license": "MIT", "engines": { @@ -5298,9 +5312,9 @@ } }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "version": "7.18.2", + "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "dev": true, "license": "MIT" }, @@ -5356,7 +5370,7 @@ }, "node_modules/util-deprecate": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true, "license": "MIT" @@ -5384,17 +5398,17 @@ } }, "node_modules/vite": { - "version": "8.0.10", - "resolved": "https://registry.npmmirror.com/vite/-/vite-8.0.10.tgz", - "integrity": "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==", + "version": "8.0.16", + "resolved": "https://registry.npmmirror.com/vite/-/vite-8.0.16.tgz", + "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", "dev": true, "license": "MIT", "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", - "postcss": "^8.5.10", - "rolldown": "1.0.0-rc.17", - "tinyglobby": "^0.2.16" + "postcss": "^8.5.15", + "rolldown": "1.0.3", + "tinyglobby": "^0.2.17" }, "bin": { "vite": "bin/vite.js" @@ -5410,7 +5424,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.0", + "@vitejs/devtools": "^0.1.18", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", @@ -5461,20 +5475,33 @@ } } }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/vitest": { - "version": "4.1.5", - "resolved": "https://registry.npmmirror.com/vitest/-/vitest-4.1.5.tgz", - "integrity": "sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==", + "version": "4.1.9", + "resolved": "https://registry.npmmirror.com/vitest/-/vitest-4.1.9.tgz", + "integrity": "sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.1.5", - "@vitest/mocker": "4.1.5", - "@vitest/pretty-format": "4.1.5", - "@vitest/runner": "4.1.5", - "@vitest/snapshot": "4.1.5", - "@vitest/spy": "4.1.5", - "@vitest/utils": "4.1.5", + "@vitest/expect": "4.1.9", + "@vitest/mocker": "4.1.9", + "@vitest/pretty-format": "4.1.9", + "@vitest/runner": "4.1.9", + "@vitest/snapshot": "4.1.9", + "@vitest/spy": "4.1.9", + "@vitest/utils": "4.1.9", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", @@ -5502,12 +5529,12 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.5", - "@vitest/browser-preview": "4.1.5", - "@vitest/browser-webdriverio": "4.1.5", - "@vitest/coverage-istanbul": "4.1.5", - "@vitest/coverage-v8": "4.1.5", - "@vitest/ui": "4.1.5", + "@vitest/browser-playwright": "4.1.9", + "@vitest/browser-preview": "4.1.9", + "@vitest/browser-webdriverio": "4.1.9", + "@vitest/coverage-istanbul": "4.1.9", + "@vitest/coverage-v8": "4.1.9", + "@vitest/ui": "4.1.9", "happy-dom": "*", "jsdom": "*", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -5551,6 +5578,19 @@ } } }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmmirror.com/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", @@ -5643,9 +5683,9 @@ } }, "node_modules/ws": { - "version": "8.20.0", - "resolved": "https://registry.npmmirror.com/ws/-/ws-8.20.0.tgz", - "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "version": "8.21.0", + "resolved": "https://registry.npmmirror.com/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", "dev": true, "license": "MIT", "engines": { @@ -5702,9 +5742,9 @@ } }, "node_modules/zod": { - "version": "4.3.6", - "resolved": "https://registry.npmmirror.com/zod/-/zod-4.3.6.tgz", - "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", "dev": true, "license": "MIT", "funding": { @@ -5725,9 +5765,9 @@ } }, "node_modules/zustand": { - "version": "5.0.12", - "resolved": "https://registry.npmmirror.com/zustand/-/zustand-5.0.12.tgz", - "integrity": "sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g==", + "version": "5.0.14", + "resolved": "https://registry.npmmirror.com/zustand/-/zustand-5.0.14.tgz", + "integrity": "sha512-/8tAspM5LMPr28b3fwLYrtdj77ECpfZviaP75CMTnwO8ISyaE4GDIG/9rDDYq/cH9D2Xw2A2RXglLInmVBQB/g==", "license": "MIT", "engines": { "node": ">=12.20.0" diff --git a/frontend/src/components/common/Card.tsx b/frontend/src/components/common/Card.tsx index 5d36980..b070be3 100644 --- a/frontend/src/components/common/Card.tsx +++ b/frontend/src/components/common/Card.tsx @@ -5,6 +5,7 @@ interface CardProps { title?: string; className?: string; padding?: 'none' | 'sm' | 'md' | 'lg'; + onClick?: () => void; } export const Card: React.FC = ({ @@ -12,6 +13,7 @@ export const Card: React.FC = ({ title, className = '', padding = 'md', + onClick, }) => { const paddingStyles = { none: '', @@ -20,8 +22,24 @@ export const Card: React.FC = ({ lg: 'p-8', }; + const isClickable = typeof onClick === 'function'; + + const handleKeyDown = (event: React.KeyboardEvent) => { + if (!isClickable) return; + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault(); + onClick(); + } + }; + return ( -
+
{title && (

{title}

diff --git a/frontend/src/hooks/useDevice.ts b/frontend/src/hooks/useDevice.ts index f4f7436..4ed11b4 100644 --- a/frontend/src/hooks/useDevice.ts +++ b/frontend/src/hooks/useDevice.ts @@ -1,102 +1,233 @@ -import { useState, useCallback, useRef } from 'react'; +import { useCallback, useEffect, useRef } from 'react'; import { checkDevice } from '../services/device'; +import { useDeviceStore } from '../store/deviceStore'; + +const getPermissionState = (granted: boolean): PermissionState => ( + granted ? 'granted' : 'denied' +); export const useDevice = () => { - const [hasPermission, setHasPermission] = useState(false); - const [hasMicrophone, setHasMicrophone] = useState(false); - const [hasCamera, setHasCamera] = useState(false); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); + const { + microphonePermission, + cameraPermission, + microphoneDeviceId, + cameraDeviceId, + microphoneStream, + cameraStream, + isMicrophoneTested, + isCameraTested, + audioLevel, + setMicrophonePermission, + setCameraPermission, + setMicrophoneDeviceId, + setCameraDeviceId, + setMicrophoneStream, + setCameraStream, + setMicrophoneTested, + setCameraTested, + setAudioLevel, + stopAllStreams, + } = useDeviceStore(); + + const analyserRef = useRef(null); + const audioContextRef = useRef(null); + const animationFrameRef = useRef(0); + + const stopAudioLevelMonitor = useCallback(() => { + if (animationFrameRef.current) { + cancelAnimationFrame(animationFrameRef.current); + animationFrameRef.current = 0; + } + analyserRef.current = null; + + if (audioContextRef.current) { + audioContextRef.current.close().catch(() => {}); + audioContextRef.current = null; + } + + setAudioLevel(0); + }, [setAudioLevel]); + + const startAudioLevelMonitor = useCallback((stream: MediaStream) => { + stopAudioLevelMonitor(); + + const audioContext = new AudioContext(); + const analyser = audioContext.createAnalyser(); + analyser.fftSize = 256; + + const source = audioContext.createMediaStreamSource(stream); + source.connect(analyser); + + audioContextRef.current = audioContext; + analyserRef.current = analyser; + + const data = new Uint8Array(analyser.frequencyBinCount); + const tick = () => { + if (!analyserRef.current) { + return; + } + + analyserRef.current.getByteFrequencyData(data); + const avg = data.reduce((sum, value) => sum + value, 0) / data.length; + setAudioLevel(avg / 255); + animationFrameRef.current = requestAnimationFrame(tick); + }; + + animationFrameRef.current = requestAnimationFrame(tick); + }, [setAudioLevel, stopAudioLevelMonitor]); + + const applyMediaStream = useCallback((stream: MediaStream) => { + const audioTracks = stream.getAudioTracks(); + const videoTracks = stream.getVideoTracks(); + + const nextMicrophoneStream = audioTracks.length > 0 ? new MediaStream(audioTracks) : null; + const nextCameraStream = videoTracks.length > 0 ? new MediaStream(videoTracks) : null; + + setMicrophoneStream(nextMicrophoneStream); + setCameraStream(nextCameraStream); + + if (audioTracks[0]) { + const settings = audioTracks[0].getSettings(); + setMicrophoneDeviceId(settings.deviceId ?? ''); + setMicrophonePermission('granted'); + startAudioLevelMonitor(nextMicrophoneStream!); + } else { + setMicrophoneDeviceId(''); + setMicrophonePermission('denied'); + stopAudioLevelMonitor(); + } + + if (videoTracks[0]) { + const settings = videoTracks[0].getSettings(); + setCameraDeviceId(settings.deviceId ?? ''); + setCameraPermission('granted'); + } else { + setCameraDeviceId(''); + setCameraPermission('denied'); + } + }, [ + setCameraDeviceId, + setCameraPermission, + setCameraStream, + setMicrophoneDeviceId, + setMicrophonePermission, + setMicrophoneStream, + startAudioLevelMonitor, + stopAudioLevelMonitor, + ]); + + const requestPermissions = useCallback(async (): Promise => { + stopAllStreams(); + stopAudioLevelMonitor(); + + const stream = await navigator.mediaDevices.getUserMedia({ + audio: { + echoCancellation: true, + noiseSuppression: true, + autoGainControl: true, + }, + video: true, + }); - const mediaStreamRef = useRef(null); + applyMediaStream(stream); + }, [applyMediaStream, stopAllStreams, stopAudioLevelMonitor]); - // 请求麦克风权限 const requestMicrophonePermission = useCallback(async (): Promise => { - setLoading(true); - setError(null); try { + if (microphoneStream) { + return true; + } + const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); - mediaStreamRef.current = stream; - setHasMicrophone(true); - setHasPermission(true); - return true; - } catch (err: any) { - setError('麦克风权限获取失败'); - setHasMicrophone(false); + const audioTracks = stream.getAudioTracks(); + const nextMicrophoneStream = audioTracks.length > 0 ? new MediaStream(audioTracks) : null; + setMicrophoneStream(nextMicrophoneStream); + setMicrophonePermission(getPermissionState(Boolean(nextMicrophoneStream))); + + if (audioTracks[0]) { + const settings = audioTracks[0].getSettings(); + setMicrophoneDeviceId(settings.deviceId ?? ''); + startAudioLevelMonitor(nextMicrophoneStream!); + } + + return Boolean(nextMicrophoneStream); + } catch { + setMicrophonePermission('denied'); + stopAudioLevelMonitor(); return false; - } finally { - setLoading(false); } - }, []); + }, [ + microphoneStream, + setMicrophoneDeviceId, + setMicrophonePermission, + setMicrophoneStream, + startAudioLevelMonitor, + stopAudioLevelMonitor, + ]); - // 请求摄像头权限 const requestCameraPermission = useCallback(async (): Promise => { - setLoading(true); - setError(null); try { + if (cameraStream) { + return true; + } + const stream = await navigator.mediaDevices.getUserMedia({ video: true }); - if (mediaStreamRef.current) { - stream.getTracks().forEach(track => mediaStreamRef.current?.addTrack(track)); - } else { - mediaStreamRef.current = stream; + const videoTracks = stream.getVideoTracks(); + const nextCameraStream = videoTracks.length > 0 ? new MediaStream(videoTracks) : null; + setCameraStream(nextCameraStream); + setCameraPermission(getPermissionState(Boolean(nextCameraStream))); + + if (videoTracks[0]) { + const settings = videoTracks[0].getSettings(); + setCameraDeviceId(settings.deviceId ?? ''); } - setHasCamera(true); - setHasPermission(true); - return true; - } catch (err: any) { - setError('摄像头权限获取失败'); - setHasCamera(false); + + return Boolean(nextCameraStream); + } catch { + setCameraPermission('denied'); return false; - } finally { - setLoading(false); } - }, []); + }, [cameraStream, setCameraDeviceId, setCameraPermission, setCameraStream]); - // 提交设备检测结果到服务端 const submitDeviceCheck = useCallback(async (): Promise => { - setLoading(true); - setError(null); - try { - const browser = navigator.userAgent; - const os = navigator.platform; - - await checkDevice({ - has_microphone: hasMicrophone, - has_camera: hasCamera, - browser, - os, - }); - - return true; - } catch (err: any) { - setError('设备检测提交失败'); - return false; - } finally { - setLoading(false); - } - }, [hasMicrophone, hasCamera]); + await checkDevice({ + has_microphone: microphonePermission === 'granted', + has_camera: cameraPermission === 'granted', + browser: navigator.userAgent, + os: navigator.platform, + }); - // 停止所有媒体流 - const stopMediaStream = useCallback(() => { - if (mediaStreamRef.current) { - mediaStreamRef.current.getTracks().forEach(track => track.stop()); - mediaStreamRef.current = null; - } - setHasMicrophone(false); - setHasCamera(false); - setHasPermission(false); - }, []); + return true; + }, [cameraPermission, microphonePermission]); + + useEffect(() => { + return () => { + stopAudioLevelMonitor(); + }; + }, [stopAudioLevelMonitor]); return { - hasPermission, - hasMicrophone, - hasCamera, - loading, - error, - mediaStream: mediaStreamRef.current, + hasPermission: microphonePermission === 'granted' || cameraPermission === 'granted', + hasMicrophone: microphonePermission === 'granted', + hasCamera: cameraPermission === 'granted', + loading: false, + error: null, + mediaStream: microphoneStream ?? cameraStream, + microphonePermission, + cameraPermission, + microphoneDeviceId, + cameraDeviceId, + microphoneStream, + cameraStream, + isMicrophoneTested, + isCameraTested, + audioLevel, + requestPermissions, requestMicrophonePermission, requestCameraPermission, submitDeviceCheck, - stopMediaStream, + setMicrophoneTested, + setCameraTested, + stopMediaStream: stopAllStreams, }; }; diff --git a/frontend/src/hooks/useInterview.ts b/frontend/src/hooks/useInterview.ts index e1e31f3..1ec1cda 100644 --- a/frontend/src/hooks/useInterview.ts +++ b/frontend/src/hooks/useInterview.ts @@ -110,6 +110,7 @@ export const useInterview = () => { stage, loading, error, + setInterviewId, setStage, handleConfigInterview, handleCreateInterview, diff --git a/frontend/src/pages/Config/DirectionSelect.tsx b/frontend/src/pages/Config/DirectionSelect.tsx index 9844954..30f755a 100644 --- a/frontend/src/pages/Config/DirectionSelect.tsx +++ b/frontend/src/pages/Config/DirectionSelect.tsx @@ -12,7 +12,7 @@ import type { Direction } from '../../types/interview'; export const DirectionSelect = () => { const navigate = useNavigate(); const { user } = useAuthStore(); - const { position, setDirection } = useInterviewStore(); + const { position, setDirection, setInterviewId } = useInterviewStore(); const [selected, setSelected] = useState(null); const [loading, setLoading] = useState(false); @@ -26,11 +26,13 @@ export const DirectionSelect = () => { setLoading(true); try { - await configInterview({ + const response = await configInterview({ user_id: user.user_id, position, direction: selected, }); + + setInterviewId(response.interview_id); navigate('/prepare'); } catch (error) { console.error('配置失败', error); @@ -41,22 +43,22 @@ export const DirectionSelect = () => { return ( -
-

选择面试方向

-

请选择您的技术方向

+
+

选择面试方向

+

请选择您的技术方向

-
+
{DIRECTIONS.map((dir) => ( handleSelect(dir.value)} > -

+

{dir.label}

{dir.description}

diff --git a/frontend/src/pages/Interview/InterviewRoom.tsx b/frontend/src/pages/Interview/InterviewRoom.tsx index 0ff145d..d2afc0d 100644 --- a/frontend/src/pages/Interview/InterviewRoom.tsx +++ b/frontend/src/pages/Interview/InterviewRoom.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState, useCallback, useRef } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { Container } from '../../components/layout/Container'; import { Button } from '../../components/common/Button'; @@ -11,29 +11,44 @@ import { useAudio } from '../../hooks/useAudio'; import { usePCMPlayer } from '../../hooks/usePCMPlayer'; import { useInterviewStore } from '../../store/interviewStore'; import { useAuthStore } from '../../store/authStore'; -import type { WSStageChangePayload, WSASRFinalPayload, WSLLMTokenPayload, WSErrorPayload } from '../../types/interview'; +import type { + WSErrorPayload, + WSASRFinalPayload, + WSLLMTokenPayload, + WSStageChangePayload, +} from '../../types/interview'; export const InterviewRoom = () => { const navigate = useNavigate(); - const { handleCreateInterview, handleFinishInterview } = useInterview(); - const { interviewId, stage, turns, setInterviewId, setStage, addTurn, updateLastTurn, setConnected } = - useInterviewStore(); + const { + handleCreateInterview, + handleFinishInterview, + } = useInterview(); + const { + interviewId, + stage, + turns, + setStage, + addTurn, + updateLastTurn, + setConnected, + } = useInterviewStore(); const token = useAuthStore((s) => s.token) ?? ''; const [wsError, setWsError] = useState(null); + const [createError, setCreateError] = useState(null); + const [isCreatingInterview, setIsCreatingInterview] = useState(false); - // ── PCM 播放器(TTS 流式播放)───────────────────────────────────────────── const { enqueuePCM, flush: flushAudio } = usePCMPlayer(); - // ── WS 事件处理 ─────────────────────────────────────────────────────────── const handleStageChange = useCallback( (payload: WSStageChangePayload) => { setStage(payload.stage === 'end' ? 'finished' : payload.stage); if (payload.stage === 'end') { - navigate('/report/generating'); + navigate('/report'); } }, - [setStage, navigate], + [navigate, setStage], ); const handleASRFinal = useCallback( @@ -46,15 +61,14 @@ export const InterviewRoom = () => { asr_raw: payload.text, }); }, - [stage, addTurn], + [addTurn, stage], ); - // AI 回复文字流:追加到最后一个 AI turn(question 字段) const llmTurnIdRef = useRef(''); + const createdInterviewRef = useRef(''); const handleLLMToken = useCallback( (payload: WSLLMTokenPayload) => { if (llmTurnIdRef.current !== payload.turn_id) { - // 新的 AI 回复轮次 llmTurnIdRef.current = payload.turn_id; addTurn({ turn_id: `ai_${payload.turn_id}`, @@ -62,22 +76,23 @@ export const InterviewRoom = () => { question: payload.token, user_answer: '', }); - } else { - updateLastTurn({ question: turns.at(-1)?.question + payload.token }); + return; } + + const currentQuestion = turns.at(-1)?.question ?? ''; + updateLastTurn({ question: currentQuestion + payload.token }); }, - [stage, addTurn, updateLastTurn, turns], + [addTurn, stage, turns, updateLastTurn], ); const handleWSError = useCallback((payload: WSErrorPayload) => { - setWsError(`错误 ${payload.code}:${payload.message}`); + setWsError(`错误 ${payload.code}: ${payload.message}`); }, []); const handleReportReady = useCallback(() => { navigate('/report'); }, [navigate]); - // ── WebSocket ───────────────────────────────────────────────────────────── const effectiveInterviewId = interviewId ?? ''; const { isConnected, @@ -85,7 +100,6 @@ export const InterviewRoom = () => { disconnect, sendAudioChunk, sendControl, - sendCodeSubmit: _sendCodeSubmit, } = useWebSocket(effectiveInterviewId, token, { onTTSAudio: enqueuePCM, onStageChange: handleStageChange, @@ -97,63 +111,70 @@ export const InterviewRoom = () => { onClose: () => setConnected(false), }); - // ── 音频采集(AudioWorklet)──────────────────────────────────────────────── const { isRecording, audioLevel, startRecording, stopRecording } = useAudio( effectiveInterviewId, { onPCMChunk: sendAudioChunk }, ); - // ── 初始化:创建面试 → 建立 WS ──────────────────────────────────────────── useEffect(() => { - if (interviewId) { - connect(); + if (!interviewId) { + setCreateError('缺少 interview_id,请返回上一页重新选择面试方向。'); + createdInterviewRef.current = ''; return; } - // 还没有 interviewId,先创建 - handleCreateInterview({ user_id: '' }).then((id) => { - if (id) { - setInterviewId(id); - // interviewId 更新后 connect 会在下一轮 effect 触发 - } - }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [interviewId]); - // interviewId 设定后连接 WS - useEffect(() => { - if (interviewId && !isConnected) { - connect(); + if (createdInterviewRef.current === interviewId || isCreatingInterview || isConnected) { + return; } + + createdInterviewRef.current = interviewId; + setIsCreatingInterview(true); + setCreateError(null); + + handleCreateInterview({ interview_id: interviewId }) + .then((createdInterviewId) => { + if (!createdInterviewId) { + createdInterviewRef.current = ''; + setCreateError('创建面试失败,请返回上一页重新开始。'); + return; + } + + connect(); + }) + .finally(() => { + setIsCreatingInterview(false); + }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [interviewId]); - // ── 录音开关 ────────────────────────────────────────────────────────────── const handleToggleRecording = useCallback(async () => { if (isRecording) { await stopRecording(); sendControl('pause'); - flushAudio(); // 停止说话时打断 AI 播放(可根据产品策略调整) - } else { - const ok = await startRecording(); - if (ok) sendControl('start'); + flushAudio(); + return; + } + + const ok = await startRecording(); + if (ok) { + sendControl('start'); } - }, [isRecording, startRecording, stopRecording, sendControl, flushAudio]); + }, [flushAudio, isRecording, sendControl, startRecording, stopRecording]); - // ── 结束面试 ────────────────────────────────────────────────────────────── const handleFinish = useCallback(async () => { if (!interviewId) return; + await stopRecording(); sendControl('stop'); await handleFinishInterview(interviewId); disconnect(); navigate('/questionnaire'); - }, [interviewId, stopRecording, sendControl, handleFinishInterview, disconnect, navigate]); + }, [disconnect, handleFinishInterview, interviewId, navigate, sendControl, stopRecording]); return ( - {/* 耳机提示横幅 */} -
- +
+ { d="M15.536 8.464a5 5 0 010 7.072M12 6a7 7 0 017 7v1a2 2 0 01-2 2h-1a2 2 0 01-2-2v-2a2 2 0 012-2h.93A7.001 7.001 0 005 13v1a2 2 0 01-2 2h-1a2 2 0 01-2-2v-2a2 2 0 012-2H3a7 7 0 017-7z" /> - 建议佩戴耳机,以避免 AI 声音回声影响识别效果 + 建议佩戴耳机,以避免 AI 语音回声影响识别效果
-
- {/* WS 错误提示 */} +
+ {createError && ( +
+ {createError} +
+ )} + {wsError && ( -
+
{wsError}
-
-
+
+
-
-

音频控制

+
+

音频控制

- {/* 连接状态 */} -
+
- {isConnected ? '已连接' : '连接中…'} + {isConnected ? '已连接' : (isCreatingInterview ? '创建中...' : '连接中...')}
@@ -205,11 +230,11 @@ export const InterviewRoom = () => { onClick={handleToggleRecording} disabled={!isConnected} > - {isRecording ? '⏸ 暂停录音' : '🎤 开始说话'} + {isRecording ? '暂停录音' : '开始说话'} -

- {isRecording ? '正在录音,AI 实时转写中…' : '点击按钮开始回答'} +

+ {isRecording ? '正在录音,AI 实时转写中...' : '点击按钮开始回答'}

diff --git a/frontend/src/pages/Resume/PDFUpload.tsx b/frontend/src/pages/Resume/PDFUpload.tsx index ea301b1..319b800 100644 --- a/frontend/src/pages/Resume/PDFUpload.tsx +++ b/frontend/src/pages/Resume/PDFUpload.tsx @@ -5,6 +5,20 @@ import { parseResume } from '../../services/resume'; import { useResumeStore } from '../../store/resumeStore'; import { FILE_UPLOAD } from '../../utils/constants'; +const getErrorMessage = (error: unknown): string => { + if (error && typeof error === 'object') { + const apiError = error as { error?: { message?: string }; message?: string }; + if (apiError.error?.message) { + return apiError.error.message; + } + if (apiError.message) { + return apiError.message; + } + } + + return '简历解析失败,请手动填写'; +}; + export const PDFUpload = () => { const { setResume, setParsing, parseError, setParseError } = useResumeStore(); const [uploading, setUploading] = useState(false); @@ -17,8 +31,8 @@ export const PDFUpload = () => { try { const result = await parseResume(file); setResume(result); - } catch (error: any) { - setParseError(error.response?.data?.message || '简历解析失败,请手动填写'); + } catch (error) { + setParseError(getErrorMessage(error)); } finally { setUploading(false); setParsing(false); @@ -27,9 +41,9 @@ export const PDFUpload = () => { return (
-

上传简历(可选)

+

上传简历(可选)

-

+

上传 PDF 格式简历,系统将自动解析并填充表单。如果解析失败,您也可以跳过此步骤手动填写。

@@ -37,20 +51,20 @@ export const PDFUpload = () => { ) : ( )} {parseError && ( -
+
{parseError}
)}
- 提示:您也可以跳过上传,直接点击"下一步"手动填写简历信息 + 提示:您也可以跳过上传,直接点击“下一步”手动填写简历信息。
); diff --git a/frontend/src/services/interview.ts b/frontend/src/services/interview.ts index 454ca1c..f878d89 100644 --- a/frontend/src/services/interview.ts +++ b/frontend/src/services/interview.ts @@ -14,34 +14,51 @@ import type { FinishInterviewResponse, } from '../types/interview'; +interface RawCreateInterviewResponse { + interview_id?: string; + stage?: CreateInterviewResponse['stage']; + created_at?: string; + InterviewID?: string; + Stage?: CreateInterviewResponse['stage']; + CreatedAt?: string; +} + +const normalizeCreateInterviewResponse = ( + payload: RawCreateInterviewResponse, +): CreateInterviewResponse => ({ + interview_id: payload.interview_id ?? payload.InterviewID ?? '', + stage: payload.stage ?? payload.Stage ?? 'intro', + created_at: payload.created_at ?? payload.CreatedAt ?? '', +}); + // 配置面试岗位和方向 export const configInterview = async ( - data: InterviewConfigRequest + data: InterviewConfigRequest, ): Promise => { const response = await apiClient.post>( '/interview/config', - data + data, ); return response.data.data; }; // 创建面试 export const createInterview = async ( - data: CreateInterviewRequest + data: CreateInterviewRequest, ): Promise => { - const response = await apiClient.post>( + const response = await apiClient.post>( '/interview/create', - data + data, ); - return response.data.data; + return normalizeCreateInterviewResponse(response.data.data); }; // 查询面试状态 export const getInterviewState = async ( - interviewId: string + interviewId: string, ): Promise => { const response = await apiClient.get>( - `/interview/state?interview_id=${interviewId}` + `/interview/state?interview_id=${interviewId}`, ); return response.data.data; }; @@ -49,7 +66,7 @@ export const getInterviewState = async ( // 发送音频流 export const submitAudio = async ( audioData: Blob, - headers: AudioSubmitHeaders + headers: AudioSubmitHeaders, ): Promise => { const response = await apiClient.post>( '/interview/audio', @@ -60,7 +77,7 @@ export const submitAudio = async ( 'X-Interview-Id': headers['X-Interview-Id'], 'X-Turn-Id': headers['X-Turn-Id'], }, - } + }, ); return response.data.data; }; @@ -69,19 +86,18 @@ export const submitAudio = async ( export const submitCode = async (data: CodeSubmitRequest): Promise => { const response = await apiClient.post>( '/interview/code/submit', - data + data, ); return response.data.data; }; // 结束面试 export const finishInterview = async ( - data: FinishInterviewRequest + data: FinishInterviewRequest, ): Promise => { const response = await apiClient.post>( '/interview/finish', - data + data, ); return response.data.data; }; - diff --git a/frontend/src/services/resume.ts b/frontend/src/services/resume.ts index 6b1be5b..67c4a88 100644 --- a/frontend/src/services/resume.ts +++ b/frontend/src/services/resume.ts @@ -4,20 +4,40 @@ import type { ResumeSubmitRequest, ResumeParseResponse, ResumeSubmitResponse, + ResumeUploadURLResponse, } from '../types/resume'; -// 解析简历 PDF +const DEFAULT_PDF_CONTENT_TYPE = 'application/pdf'; + +// 解析简历 PDF:先拿预签名地址,再直传对象存储,最后用 object_key 触发解析 export const parseResume = async (file: File): Promise => { - const formData = new FormData(); - formData.append('file', file); + const uploadURLResponse = await apiClient.get>( + '/resume/upload-url', + { + params: { + filename: file.name, + }, + } + ); + + const { upload_url, object_key } = uploadURLResponse.data.data; + + const uploadResponse = await fetch(upload_url, { + method: 'PUT', + headers: { + 'Content-Type': file.type || DEFAULT_PDF_CONTENT_TYPE, + }, + body: file, + }); + + if (!uploadResponse.ok) { + throw new Error(`简历上传失败 (${uploadResponse.status})`); + } const response = await apiClient.post>( '/resume/parse', - formData, { - headers: { - 'Content-Type': 'multipart/form-data', - }, + object_key, } ); diff --git a/frontend/src/types/interview.ts b/frontend/src/types/interview.ts index 579b0bd..9839f69 100644 --- a/frontend/src/types/interview.ts +++ b/frontend/src/types/interview.ts @@ -19,13 +19,12 @@ export interface InterviewConfigRequest { // 面试配置响应 export interface InterviewConfigResponse { - config_id: string; - message: string; + interview_id: string; } // 创建面试请求 export interface CreateInterviewRequest { - user_id: string; + interview_id: string; } // 创建面试响应 diff --git a/frontend/src/types/resume.ts b/frontend/src/types/resume.ts index 504b49a..c2b6666 100644 --- a/frontend/src/types/resume.ts +++ b/frontend/src/types/resume.ts @@ -47,15 +47,16 @@ export interface ResumeSubmitRequest { education: ResumeEducation; } -// PDF 上传请求 -export interface ResumePDFUploadRequest { - user_id: string; - file: File; +// 简历提交响应 +export interface ResumeSubmitResponse { + resume_id: string; } -// 简历解析响应 -export interface ResumeParseResponse { - success: boolean; - resume?: StructuredResume; - error?: string; +// 预签名上传地址响应 +export interface ResumeUploadURLResponse { + upload_url: string; + object_key: string; } + +// 简历解析响应 +export type ResumeParseResponse = StructuredResume; diff --git a/gateway/agent/client.go b/gateway/agent/client.go new file mode 100644 index 0000000..b4d4765 --- /dev/null +++ b/gateway/agent/client.go @@ -0,0 +1,66 @@ +package agent + +import "context" + +// CreateInterviewReq CreateInterview 入参。 +type CreateInterviewReq struct { + CandidateID string + Position string + Direction string +} + +// CreateInterviewResp CreateInterview 出参。 +type CreateInterviewResp struct { + InterviewID string + Stage string + CreatedAt string +} + +// SubmitTurnReq SubmitTurn 入参。 +type SubmitTurnReq struct { + InterviewID string + CandidateID string + Text string +} + +// SubmitTurnResp SubmitTurn 出参。 +type SubmitTurnResp struct { + Reply string + Stage string + IsFinished bool +} + +// FinishInterviewReq FinishInterview 入参。 +type FinishInterviewReq struct { + InterviewID string +} + +// FinishInterviewResp FinishInterview 出参。 +type FinishInterviewResp struct { + FinishedAt string + DurationSeconds int64 +} + +// Client 封装对面试服务的 Kitex RPC 调用。 +type Client struct { + // TODO: 注入 Kitex 生成的 InterviewService client +} + +func NewClient() *Client { + return &Client{} +} + +func (c *Client) CreateInterview(ctx context.Context, req *CreateInterviewReq) (*CreateInterviewResp, error) { + // TODO: 调用 Kitex client.CreateInterview + return &CreateInterviewResp{}, nil +} + +func (c *Client) SubmitTurn(ctx context.Context, req *SubmitTurnReq) (*SubmitTurnResp, error) { + // TODO: 调用 Kitex client.SubmitTurn + return &SubmitTurnResp{}, nil +} + +func (c *Client) FinishInterview(ctx context.Context, req *FinishInterviewReq) (*FinishInterviewResp, error) { + // TODO: 调用 Kitex client.FinishInterview + return &FinishInterviewResp{}, nil +} diff --git a/gateway/channel/feishu/adapter.go b/gateway/channel/feishu/adapter.go new file mode 100644 index 0000000..a96c9ac --- /dev/null +++ b/gateway/channel/feishu/adapter.go @@ -0,0 +1,31 @@ +package feishu + +import ( + "context" + "fmt" + + "gateway/channel" + "gateway/inbound" +) + +// Connector 飞书渠道连接器骨架。 +// 飞书官方走 WebSocket 长连接(larksuite/openclaw-lark),此处为占位实现, +// 待后续按 WSClient 模型补全。 +type Connector struct{} + +func New() *Connector { return &Connector{} } + +func (c *Connector) Name() string { return "feishu" } + +// Start 尚未实现。飞书需建立 WebSocket 长连接订阅 im.message.receive_v1 等事件。 +func (c *Connector) Start(_ context.Context, _ chan<- *inbound.InboundEvent) error { + return fmt.Errorf("feishu: connector not implemented") +} + +func (c *Connector) Send(_ context.Context, _ string, _ string, _ channel.SendOpts) error { + return fmt.Errorf("feishu: send not implemented") +} + +func (c *Connector) Status() channel.ConnStatus { + return channel.ConnStatus{Connected: false, Detail: "not implemented"} +} diff --git a/gateway/channel/iface.go b/gateway/channel/iface.go new file mode 100644 index 0000000..9402424 --- /dev/null +++ b/gateway/channel/iface.go @@ -0,0 +1,43 @@ +package channel + +import ( + "context" + + "gateway/inbound" +) + +// ChannelConnector 渠道连接器。 +// 每个渠道账号对应一个常驻连接(长轮询 / WebSocket / webhook 由各实现自行管理), +// 负责维持入站连接、归一消息为 InboundEvent,并提供出站发送能力。 +// +// 三个目标渠道(微信 iLink 长轮询、飞书 WebSocket、QQ WebSocket/webhook)的接入模式 +// 都收敛到「主动维持一条长连接 + 自管重连 + 取消即停」的模型,故抽象为此接口, +// 取代早期按 webhook 被动推送设计的 ChannelAdapter。 +type ChannelConnector interface { + // Name 返回渠道标识,如 "wechat" / "feishu" / "qqbot"。 + Name() string + + // Start 启动该渠道的入站连接,把收到的消息归一为 InboundEvent 投入 out。 + // 阻塞直到 ctx 取消;内部自行管理重连、心跳、退避。 + Start(ctx context.Context, out chan<- *inbound.InboundEvent) error + + // Send 向平台用户发送消息。 + Send(ctx context.Context, peerID, content string, opts SendOpts) error + + // Status 返回当前连接状态,供健康检查使用。 + Status() ConnStatus +} + +// SendOpts 出站发送的附加选项。 +type SendOpts struct { + // ContextToken 会话上下文令牌。微信回复时需回传;其他渠道可空。 + ContextToken string + // AccountID 多账号场景下指定发送账号;单账号可空。 + AccountID string +} + +// ConnStatus 渠道连接状态。 +type ConnStatus struct { + Connected bool // 是否已建立连接 + Detail string // 状态描述(如 "reconnecting"、"not implemented") +} diff --git a/gateway/channel/qqbot/adapter.go b/gateway/channel/qqbot/adapter.go new file mode 100644 index 0000000..4062e73 --- /dev/null +++ b/gateway/channel/qqbot/adapter.go @@ -0,0 +1,31 @@ +package qqbot + +import ( + "context" + "fmt" + + "gateway/channel" + "gateway/inbound" +) + +// Connector QQ Bot 渠道连接器骨架。 +// QQ 官方支持 WebSocket(默认)/ Webhook 双 transport(tencent-connect/openclaw-qqbot), +// 此处为占位实现,待后续补全。 +type Connector struct{} + +func New() *Connector { return &Connector{} } + +func (c *Connector) Name() string { return "qqbot" } + +// Start 尚未实现。QQ 默认建立 WebSocket 长连接接收事件。 +func (c *Connector) Start(_ context.Context, _ chan<- *inbound.InboundEvent) error { + return fmt.Errorf("qqbot: connector not implemented") +} + +func (c *Connector) Send(_ context.Context, _ string, _ string, _ channel.SendOpts) error { + return fmt.Errorf("qqbot: send not implemented") +} + +func (c *Connector) Status() channel.ConnStatus { + return channel.ConnStatus{Connected: false, Detail: "not implemented"} +} diff --git a/gateway/cmd/main.go b/gateway/cmd/main.go new file mode 100644 index 0000000..2792d56 --- /dev/null +++ b/gateway/cmd/main.go @@ -0,0 +1,46 @@ +package main + +import ( + "github.com/cloudwego/hertz/pkg/app/server" + + "gateway/agent" + "gateway/channel/feishu" + "gateway/channel/qqbot" + "gateway/config" + "gateway/handler" + "gateway/outbound" + "gateway/session" + "gateway/statemachine" +) + +func main() { + cfg := config.Load() + + // 渠道连接器:每个渠道一个常驻长连接(拉取/WS),自管重连。 + feishuConn := feishu.New() + qqbotConn := qqbot.New() + + // 内部层 + sessionMgr := session.NewManager() + _ = statemachine.NewFSM() + _ = agent.NewClient() + _ = outbound.NewDispatcher(feishuConn, qqbotConn) + + // TODO: 启动各 connector 的 Start(ctx, eventCh) goroutine, + // 由统一消费循环把 InboundEvent 喂入 session -> fsm -> agent。 + + // Handler + sessionH := handler.NewSessionHandler(sessionMgr) + manageH := handler.NewManageHandler(sessionMgr) + + // HTTP 服务 + h := server.Default(server.WithHostPorts(cfg.HTTPAddr)) + + v1 := h.Group("/v1/gateway") + v1.GET("/session/:session_id", sessionH.Get) + v1.GET("/sessions", sessionH.List) + v1.POST("/session/:session_id/handoff", manageH.Handoff) + v1.POST("/session/:session_id/terminate", manageH.Terminate) + + h.Spin() +} diff --git a/gateway/config/config.go b/gateway/config/config.go new file mode 100644 index 0000000..72b3915 --- /dev/null +++ b/gateway/config/config.go @@ -0,0 +1,25 @@ +package config + +import "os" + +// Config 网关服务配置,全部从环境变量读取。 +type Config struct { + HTTPAddr string // 监听地址,默认 :8081 + EtcdAddr string // etcd 地址,默认 localhost:2379 + InterviewServiceName string // 面试服务在 etcd 中的服务名 +} + +func Load() *Config { + return &Config{ + HTTPAddr: getEnv("GATEWAY_HTTP_ADDR", ":8081"), + EtcdAddr: getEnv("GATEWAY_ETCD_ADDR", "localhost:2379"), + InterviewServiceName: getEnv("GATEWAY_INTERVIEW_SERVICE", "ai_interview"), + } +} + +func getEnv(key, fallback string) string { + if v := os.Getenv(key); v != "" { + return v + } + return fallback +} diff --git a/gateway/domain/session.go b/gateway/domain/session.go new file mode 100644 index 0000000..fe56487 --- /dev/null +++ b/gateway/domain/session.go @@ -0,0 +1,32 @@ +package domain + +import "time" + +type SessionStatus string + +const ( + StatusNew SessionStatus = "new" + StatusVerifying SessionStatus = "verifying" + StatusReady SessionStatus = "ready" + StatusInterviewing SessionStatus = "interviewing" + StatusWaitingUser SessionStatus = "waiting_user" + StatusPaused SessionStatus = "paused" + StatusScoring SessionStatus = "scoring" + StatusFinished SessionStatus = "finished" + StatusHandoff SessionStatus = "handoff" + StatusExpired SessionStatus = "expired" +) + +// GatewaySession 网关层面试会话,记录渠道身份与状态机节点。 +// 不存面试内容,面试内容在面试服务侧通过 InterviewID 关联查询。 +type GatewaySession struct { + SessionID string + CandidateID string + Channel string // wechat / feishu / qqbot + PeerID string // 用户在平台的唯一 ID + Status SessionStatus + InterviewID string // 关联面试服务的 interview_id + CreatedAt time.Time + UpdatedAt time.Time + LastActiveAt time.Time +} diff --git a/gateway/go.mod b/gateway/go.mod new file mode 100644 index 0000000..a2407ae --- /dev/null +++ b/gateway/go.mod @@ -0,0 +1,26 @@ +module gateway + +go 1.23.0 + +require ( + github.com/cloudwego/hertz v0.10.4 + github.com/google/uuid v1.6.0 +) + +require ( + github.com/bytedance/gopkg v0.1.3 // indirect + github.com/bytedance/sonic v1.15.0 // indirect + github.com/bytedance/sonic/loader v0.5.0 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect + github.com/cloudwego/gopkg v0.1.4 // indirect + github.com/cloudwego/netpoll v0.7.2 // indirect + github.com/fsnotify/fsnotify v1.5.4 // indirect + github.com/klauspost/cpuid/v2 v2.2.9 // indirect + github.com/tidwall/gjson v1.17.3 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + golang.org/x/arch v0.14.0 // indirect + golang.org/x/sys v0.30.0 // indirect + google.golang.org/protobuf v1.34.1 // indirect +) diff --git a/gateway/go.sum b/gateway/go.sum new file mode 100644 index 0000000..605b05b --- /dev/null +++ b/gateway/go.sum @@ -0,0 +1,109 @@ +github.com/bytedance/gopkg v0.1.1/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= +github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= +github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= +github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE= +github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k= +github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE= +github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +github.com/cloudwego/gopkg v0.1.4 h1:EoQiCG4sTonTPHxOGE0VlQs+sQR+Hsi2uN0qqwu8O50= +github.com/cloudwego/gopkg v0.1.4/go.mod h1:FQuXsRWRsSqJLsMVd5SYzp8/Z1y5gXKnVvRrWUOsCMI= +github.com/cloudwego/hertz v0.10.4 h1:xJxomApZYR67cROevam6SrtUBDvhcI4ZZhx/WgvpHwU= +github.com/cloudwego/hertz v0.10.4/go.mod h1:tZXEi/4o7R0Ho9yw5V2C+k/wVx3S8+wuuiJGDMopnpg= +github.com/cloudwego/netpoll v0.7.2 h1:4qDBGQ6CG2SvEXhZSDxMdtqt/NLDxjAVk0PC/biKiJo= +github.com/cloudwego/netpoll v0.7.2/go.mod h1:PI+YrmyS7cIr0+SD4seJz3Eo3ckkXdu2ZVKBLhURLNU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= +github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tidwall/gjson v1.17.3 h1:bwWLZU7icoKRG+C+0PNwIKC6FCJO/Q3p2pZvuP0jN94= +github.com/tidwall/gjson v1.17.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/arch v0.14.0 h1:z9JUEZWr8x4rR0OU6c4/4t6E6jOZ8/QBS2bBYBm4tx4= +golang.org/x/arch v0.14.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/gateway/handler/manage.go b/gateway/handler/manage.go new file mode 100644 index 0000000..33d0c96 --- /dev/null +++ b/gateway/handler/manage.go @@ -0,0 +1,78 @@ +package handler + +import ( + "context" + "net/http" + + "github.com/cloudwego/hertz/pkg/app" + + "gateway/domain" + "gateway/session" +) + +// ManageHandler 处理管理操作接口。 +type ManageHandler struct { + mgr *session.Manager +} + +func NewManageHandler(mgr *session.Manager) *ManageHandler { + return &ManageHandler{mgr: mgr} +} + +type handoffReq struct { + Reason string `json:"reason"` + OperatorID string `json:"operator_id"` +} + +// Handoff POST /v1/gateway/session/:session_id/handoff +func (h *ManageHandler) Handoff(ctx context.Context, c *app.RequestContext) { + sessionID := c.Param("session_id") + + var req handoffReq + if err := c.BindJSON(&req); err != nil { + status, resp := fail(http.StatusBadRequest, 400, "invalid request body") + c.JSON(status, resp) + return + } + + if err := h.mgr.UpdateStatus(ctx, sessionID, domain.StatusHandoff); err != nil { + status, resp := fail(http.StatusInternalServerError, 500, "handoff failed") + c.JSON(status, resp) + return + } + + status, resp := ok(map[string]any{ + "session_id": sessionID, + "status": domain.StatusHandoff, + }) + c.JSON(status, resp) +} + +type terminateReq struct { + Reason string `json:"reason"` + OperatorID string `json:"operator_id"` +} + +// Terminate POST /v1/gateway/session/:session_id/terminate +func (h *ManageHandler) Terminate(ctx context.Context, c *app.RequestContext) { + sessionID := c.Param("session_id") + + var req terminateReq + if err := c.BindJSON(&req); err != nil { + status, resp := fail(http.StatusBadRequest, 400, "invalid request body") + c.JSON(status, resp) + return + } + + if err := h.mgr.UpdateStatus(ctx, sessionID, domain.StatusFinished); err != nil { + status, resp := fail(http.StatusInternalServerError, 500, "terminate failed") + c.JSON(status, resp) + return + } + + status, resp := ok(map[string]any{ + "session_id": sessionID, + "status": domain.StatusFinished, + }) + c.JSON(status, resp) +} diff --git a/gateway/handler/response.go b/gateway/handler/response.go new file mode 100644 index 0000000..99d7d68 --- /dev/null +++ b/gateway/handler/response.go @@ -0,0 +1,28 @@ +package handler + +import "net/http" + +// Result 统一响应格式,与主服务保持一致。 +type Result struct { + Success bool `json:"success"` + Data any `json:"data"` + Error *Error `json:"error"` +} + +// Error 错误详情。 +type Error struct { + Code int `json:"code"` + Message string `json:"message"` +} + +func ok(data any) (int, *Result) { + return http.StatusOK, &Result{Success: true, Data: data} +} + +func fail(status, code int, msg string) (int, *Result) { + return status, &Result{ + Success: false, + Data: nil, + Error: &Error{Code: code, Message: msg}, + } +} diff --git a/gateway/handler/session.go b/gateway/handler/session.go new file mode 100644 index 0000000..2eda002 --- /dev/null +++ b/gateway/handler/session.go @@ -0,0 +1,69 @@ +package handler + +import ( + "context" + "net/http" + "strconv" + + "github.com/cloudwego/hertz/pkg/app" + + "gateway/session" +) + +// SessionHandler 处理会话查询接口。 +type SessionHandler struct { + mgr *session.Manager +} + +func NewSessionHandler(mgr *session.Manager) *SessionHandler { + return &SessionHandler{mgr: mgr} +} + +// Get GET /v1/gateway/session/:session_id +func (h *SessionHandler) Get(ctx context.Context, c *app.RequestContext) { + sessionID := c.Param("session_id") + if sessionID == "" { + status, resp := fail(http.StatusBadRequest, 400, "session_id is required") + c.JSON(status, resp) + return + } + + s, err := h.mgr.Get(ctx, sessionID) + if err != nil { + status, resp := fail(http.StatusNotFound, 404, "session not found") + c.JSON(status, resp) + return + } + + status, resp := ok(s) + c.JSON(status, resp) +} + +// List GET /v1/gateway/sessions?candidate_id=&page=&page_size= +func (h *SessionHandler) List(ctx context.Context, c *app.RequestContext) { + candidateID := string(c.Query("candidate_id")) + page, _ := strconv.Atoi(string(c.DefaultQuery("page", "1"))) + pageSize, _ := strconv.Atoi(string(c.DefaultQuery("page_size", "20"))) + + if page < 1 { + page = 1 + } + if pageSize < 1 || pageSize > 100 { + pageSize = 20 + } + + sessions, total, err := h.mgr.List(ctx, candidateID, page, pageSize) + if err != nil { + status, resp := fail(http.StatusInternalServerError, 500, "list sessions failed") + c.JSON(status, resp) + return + } + + status, resp := ok(map[string]any{ + "sessions": sessions, + "total": total, + "page": page, + "page_size": pageSize, + }) + c.JSON(status, resp) +} diff --git a/gateway/idl/interview.thrift b/gateway/idl/interview.thrift new file mode 100644 index 0000000..7a6a2bd --- /dev/null +++ b/gateway/idl/interview.thrift @@ -0,0 +1,40 @@ +namespace go interview + +struct CreateInterviewReq { + 1: required string candidate_id + 2: required string position + 3: required string direction +} + +struct CreateInterviewResp { + 1: required string interview_id + 2: required string stage + 3: required string created_at +} + +struct SubmitTurnReq { + 1: required string interview_id + 2: required string candidate_id + 3: required string text +} + +struct SubmitTurnResp { + 1: required string reply + 2: required string stage + 3: required bool is_finished +} + +struct FinishInterviewReq { + 1: required string interview_id +} + +struct FinishInterviewResp { + 1: required string finished_at + 2: required i64 duration_seconds +} + +service InterviewService { + CreateInterviewResp CreateInterview(1: CreateInterviewReq req) + SubmitTurnResp SubmitTurn(1: SubmitTurnReq req) + FinishInterviewResp FinishInterview(1: FinishInterviewReq req) +} diff --git a/gateway/inbound/event.go b/gateway/inbound/event.go new file mode 100644 index 0000000..43dcbcc --- /dev/null +++ b/gateway/inbound/event.go @@ -0,0 +1,22 @@ +package inbound + +import "time" + +// InboundEvent 统一入站事件,所有渠道消息转换为此结构后进入网关处理流程。 +// 平台特有字段只放 Raw,不进主协议。 +type InboundEvent struct { + Channel string // wechat / feishu / qqbot + AccountID string // 我们的平台账号 ID(区分多账号) + PeerID string // 用户在平台的唯一 ID + PeerType string // user / group + MessageID string // 平台消息 ID(去重用) + Payload EventPayload + Raw []byte // 原始平台数据 + ReceivedAt time.Time +} + +// EventPayload 统一消息内容,支持 text / image / audio。 +type EventPayload struct { + Type string // text / image / audio + Content string // text 时为文本内容,其他类型为资源 URL 或 base64 +} diff --git a/gateway/outbound/dispatcher.go b/gateway/outbound/dispatcher.go new file mode 100644 index 0000000..53cafc6 --- /dev/null +++ b/gateway/outbound/dispatcher.go @@ -0,0 +1,30 @@ +package outbound + +import ( + "context" + "fmt" + + "gateway/channel" +) + +// Dispatcher 出站分发器,按 channel 把网关动作回写到对应平台。 +type Dispatcher struct { + connectors map[string]channel.ChannelConnector +} + +func NewDispatcher(connectors ...channel.ChannelConnector) *Dispatcher { + m := make(map[string]channel.ChannelConnector, len(connectors)) + for _, c := range connectors { + m[c.Name()] = c + } + return &Dispatcher{connectors: m} +} + +// Send 向指定渠道的用户发送文本消息。 +func (d *Dispatcher) Send(ctx context.Context, ch, peerID, content string, opts channel.SendOpts) error { + conn, ok := d.connectors[ch] + if !ok { + return fmt.Errorf("unknown channel: %s", ch) + } + return conn.Send(ctx, peerID, content, opts) +} diff --git a/gateway/session/manager.go b/gateway/session/manager.go new file mode 100644 index 0000000..3efaa0f --- /dev/null +++ b/gateway/session/manager.go @@ -0,0 +1,63 @@ +package session + +import ( + "context" + "fmt" + "time" + + "github.com/google/uuid" + + "gateway/domain" +) + +// Manager 负责 GatewaySession 的路由、创建和查询。 +// 不承载面试业务判断,只管会话归属和状态存储。 +type Manager struct { + // TODO: 注入 Redis client 用于持久化 GatewaySession +} + +func NewManager() *Manager { + return &Manager{} +} + +// Route 按 channel + peerID 查找活跃会话。 +// 未命中返回 nil, false,由上层决定是否进入 onboarding。 +func (m *Manager) Route(ctx context.Context, channel, peerID string) (*domain.GatewaySession, bool, error) { + // TODO: 从 Redis 按 key = "gw:session:{channel}:{peerID}" 查活跃 session + return nil, false, nil +} + +// Create 创建新的 GatewaySession。 +func (m *Manager) Create(ctx context.Context, channel, peerID, candidateID string) (*domain.GatewaySession, error) { + now := time.Now() + s := &domain.GatewaySession{ + SessionID: uuid.New().String(), + CandidateID: candidateID, + Channel: channel, + PeerID: peerID, + Status: domain.StatusNew, + CreatedAt: now, + UpdatedAt: now, + LastActiveAt: now, + } + // TODO: 持久化到 Redis + return s, nil +} + +// Get 按 session_id 查询会话。 +func (m *Manager) Get(ctx context.Context, sessionID string) (*domain.GatewaySession, error) { + // TODO: 从 Redis 按 key = "gw:session:id:{sessionID}" 查询 + return nil, fmt.Errorf("not implemented") +} + +// List 按 candidate_id 分页查询会话列表。 +func (m *Manager) List(ctx context.Context, candidateID string, page, pageSize int) ([]*domain.GatewaySession, int64, error) { + // TODO: 从存储查询 + return nil, 0, nil +} + +// UpdateStatus 更新会话状态。 +func (m *Manager) UpdateStatus(ctx context.Context, sessionID string, status domain.SessionStatus) error { + // TODO: 更新 Redis + 持久化存储 + return nil +} diff --git a/gateway/statemachine/fsm.go b/gateway/statemachine/fsm.go new file mode 100644 index 0000000..df2783b --- /dev/null +++ b/gateway/statemachine/fsm.go @@ -0,0 +1,68 @@ +package statemachine + +import ( + "context" + "fmt" + + "gateway/domain" +) + +// Action 状态机决定网关下一步做什么。 +type Action string + +const ( + ActionTriggerAgent Action = "trigger_agent" // 调用 Agent 处理本轮消息 + ActionSendMessage Action = "send_message" // 直接回复固定文本(onboarding 引导等) + ActionPause Action = "pause" // 暂停面试 + ActionHandoff Action = "handoff" // 转人工 + ActionEnd Action = "end" // 结束面试 + ActionReject Action = "reject" // 拒绝本条消息(状态不允许) + ActionNoop Action = "noop" // 无操作 +) + +// Decision 状态机决策结果。 +type Decision struct { + Action Action + Message string // ActionSendMessage 时填充回复文本 +} + +// FSM 面试网关状态机。 +// 决定每条消息进来后网关如何推进,最终执行权始终属于网关而非 Agent。 +type FSM struct{} + +func NewFSM() *FSM { return &FSM{} } + +// Decide 根据当前会话状态和入站事件类型,返回网关应执行的动作。 +func (f *FSM) Decide(ctx context.Context, session *domain.GatewaySession, eventType string) (*Decision, error) { + switch session.Status { + case domain.StatusNew, domain.StatusVerifying: + return &Decision{Action: ActionSendMessage, Message: "请先完成身份绑定"}, nil + + case domain.StatusReady: + return &Decision{Action: ActionTriggerAgent}, nil + + case domain.StatusInterviewing: + return &Decision{Action: ActionTriggerAgent}, nil + + case domain.StatusWaitingUser: + return &Decision{Action: ActionTriggerAgent}, nil + + case domain.StatusPaused: + return &Decision{Action: ActionReject, Message: "面试已暂停,请稍后再试"}, nil + + case domain.StatusFinished, domain.StatusExpired: + return &Decision{Action: ActionReject, Message: "面试已结束"}, nil + + case domain.StatusHandoff: + return &Decision{Action: ActionNoop}, nil + + default: + return nil, fmt.Errorf("unknown session status: %s", session.Status) + } +} + +// Transition 执行状态迁移,校验迁移是否合法。 +func (f *FSM) Transition(ctx context.Context, session *domain.GatewaySession, target domain.SessionStatus) error { + // TODO: 定义合法迁移表并校验 + return nil +} diff --git a/start-rag.sh b/start-rag.sh index 4825ca0..ec2df8a 100644 --- a/start-rag.sh +++ b/start-rag.sh @@ -1,5 +1,6 @@ #!/usr/bin/env bash # 全量启动:启用向量/关键词检索,拉起全套基础设施(含 etcd + Milvus + Elasticsearch)。 +# 需要 8G+ 内存,首次启动会拉取较大镜像。 set -euo pipefail cd "$(dirname "$0")" @@ -9,9 +10,9 @@ if [ ! -f .env ]; then fi echo "[start-rag] 启动全套基础设施(含 milvus/es/etcd,较重,首次拉镜像耗时较长)..." -docker compose up -d --wait postgres redis minio minio-init rabbitmq etcd milvus elasticsearch +docker compose --profile rag up -d --wait postgres redis minio rabbitmq etcd milvus elasticsearch +docker compose up -d minio-init -# 强制开启 RAG(export 优先于 .env) export RAG_ENABLED=true echo "[start-rag] RAG_ENABLED=true,启动应用(启用 Milvus + ES 检索)..." diff --git a/start-wiki.sh b/start-wiki.sh index 94b7b1c..2306b0d 100644 --- a/start-wiki.sh +++ b/start-wiki.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash # 轻量启动:出题走 LLM wiki(Skill middleware 读本地 SKILL.md),不启动 Milvus/ES。 # 只拉起必需基础设施:Postgres + Redis + MinIO + RabbitMQ。 +# 推荐在 2C2G 及以上机器使用。 set -euo pipefail cd "$(dirname "$0")" @@ -10,9 +11,9 @@ if [ ! -f .env ]; then fi echo "[start-wiki] 启动基础设施(postgres redis minio rabbitmq)..." -docker compose up -d --wait postgres redis minio minio-init rabbitmq +docker compose up -d --wait postgres redis minio rabbitmq +docker compose up -d minio-init -# 强制关闭 RAG(export 优先于 .env,godotenv 不覆盖已存在的环境变量) export RAG_ENABLED=false echo "[start-wiki] RAG_ENABLED=false,启动应用(出题走 wiki)..."