diff --git a/.agents/plugins/marketplace.json b/.agents/plugins/marketplace.json
deleted file mode 100644
index 98a0146..0000000
--- a/.agents/plugins/marketplace.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
- "name": "infinite-canvas-local",
- "interface": {
- "displayName": "Infinite Canvas"
- },
- "plugins": [
- {
- "name": "infinite-canvas",
- "source": {
- "source": "local",
- "path": "./plugins/infinite-canvas"
- },
- "policy": {
- "installation": "AVAILABLE",
- "authentication": "ON_INSTALL"
- },
- "category": "Productivity"
- }
- ]
-}
diff --git a/.github/workflows/publish-plugins.yml b/.github/workflows/publish-plugins.yml
deleted file mode 100644
index 3779769..0000000
--- a/.github/workflows/publish-plugins.yml
+++ /dev/null
@@ -1,47 +0,0 @@
-name: Publish Official Plugins
-
-# 构建官方插件并把产物(bundle + 清单)强推到孤儿分支 plugins-dist。
-# 与 github-pages.yml 一致,在打版本 tag 时触发 —— 发版时 web 与官方插件一起发布。
-# 前端经 jsDelivr 从该分支远程拉取;构建产物不进 main、不进 git。
-on:
- push:
- tags: ["v*"]
- workflow_dispatch:
-
-permissions:
- contents: write
-
-concurrency:
- group: publish-plugins
- cancel-in-progress: false
-
-jobs:
- publish:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
-
- - uses: actions/setup-node@v4
- with:
- node-version: 22
-
- - name: Install build dependencies
- working-directory: plugins/canvas/registry
- run: npm install --no-audit --no-fund
-
- - name: Build official plugins
- working-directory: plugins/canvas/registry
- run: npm run build
-
- - name: Publish to plugins-dist branch
- working-directory: plugins/canvas/registry/dist
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: |
- git init -q
- git config user.name "github-actions[bot]"
- git config user.email "github-actions[bot]@users.noreply.github.com"
- git checkout -q -b plugins-dist
- git add -A
- git commit -q -m "publish official plugins @ ${GITHUB_SHA::7}"
- git push -f "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" plugins-dist
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ad5a143..5d7b2ce 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,15 @@
## Unreleased
+## v0.17.0
+
++ [新增] 拉取 TDesign React 后台模板至 `admin/`,通过统一的 3000 端口提供前台 `/` 和后台 `/admin/`,并补充一体化开发与构建脚本。
++ [调整] 移除右上角 GitHub 入口及其前端组件代码。
++ [调整] 项目品牌统一为 MiragenFlow / 元境幻生,同步更新前端标题、配置与导出标识、WebDAV 默认目录、本地存储命名空间和内部插件 SDK scope。
++ [调整] 收缩产品范围,移除独立图片/视频工作台、全部视频能力、远程提示词、Local Agent/Codex/MCP/Skill、Codex App 与远程插件市场,保留首页、项目库、无限画布、资产库、图文音生成、图片工具、WebDAV 和内部插件 SDK。
++ [优化] 同步收缩后的文档导航、插件 SDK 说明和前端依赖,移除已下线功能的死链与构建依赖。
++ [修复] 统一开发启动时强制重建后台 Vite 依赖缓存,避免后台入口因过期预构建依赖返回 504 进入白屏。
+
## v0.16.0 - 2026-08-18
+ [新增] 提示词来源新增 Freestylefly GPT Image 2 内置来源。
diff --git a/Dockerfile b/Dockerfile
index 38d7005..62b1159 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -9,10 +9,20 @@ COPY CHANGELOG.md /app/CHANGELOG.md
COPY web ./
RUN bun run build
+# 构建后台管理端。它最终与前台一起由同一个 Nginx 端口提供服务。
+FROM oven/bun:1.3.13 AS admin-build
+
+WORKDIR /app/admin
+COPY admin/package.json admin/package-lock.json ./
+RUN npm ci --ignore-scripts
+COPY admin ./
+RUN npm run build
+
# 运行镜像:只启动静态前端,AI 请求由浏览器前台直连用户自己的接口。
FROM nginx:1.27-alpine
COPY --from=web-build /app/web/dist /usr/share/nginx/html
+COPY --from=admin-build /app/admin/dist /usr/share/nginx/html/admin
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY web/docker-entrypoint.sh /docker-entrypoint.d/40-runtime-config.sh
RUN chmod +x /docker-entrypoint.d/40-runtime-config.sh
diff --git a/README.md b/README.md
index 13c3dc7..c95667b 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,8 @@
-
+
-无限画布 (infinite-canvas)
+元境幻生 (MiragenFlow)
@@ -19,10 +19,10 @@
- 快速开始 · 功能介绍 · Render 部署 · Docker 部署 · 画布节点操作手册 · 画布快捷键 · 漏洞提交 · 待办事项 · 本地 Canvas Agent · Codex app 插件
+ 快速开始 · 功能介绍 · Render 部署 · Docker 部署 · 画布节点操作手册 · 画布快捷键 · 漏洞提交 · 待办事项
-无限画布是一款面向图片创作的开源工作台。它把画布编排、AI 图片生成、参考图编辑、对话助手、提示词库和素材沉淀放在同一个界面里,适合用来探索视觉方案并连续迭代图片结果。
+元境幻生是一款面向图片、文本和音频创作的开源工作台。它把无限画布编排、AI 生成、图片编辑和资产沉淀放在同一个界面里,适合连续探索视觉方案并管理生成结果。
> [!CAUTION]
> 项目目前处于开发阶段,不保证历史数据兼容。各种本地存储格式都可能直接调整,欢迎关注后续更新。
@@ -34,10 +34,10 @@
-
+
- Atlas Cloud is a full-modal AI inference platform that gives developers a single AI API to access video generation, image generation, and LLM APIs. Instead of managing multiple vendor integrations, you connect once and get unified access to 300+ curated models across all modalities. Check out Atlas Cloud's new coding plan promotion for more budget-friendly API access.
+ Atlas Cloud provides a unified AI API for image, text, and other model services. Instead of managing multiple vendor integrations, you connect once to access a broad model catalog. Check out Atlas Cloud's coding plan for budget-friendly API access.
@@ -45,7 +45,7 @@
- MiniMax H3 视频生成 API|秘塔科技 秘塔科技提供高性价比的 MiniMax H3 视频生成服务:768P 仅 0.09 元/秒,2K 仅 0.15 元/秒 。支持原生 2K、音画同步,API 兼容 OpenAI 协议 ,同时支持 ComfyUI ,无需自行部署 GPU。 🎁 通过 无限画布专属链接注册 ,即可领取赠送额度及专属优惠。
+ AI API 服务|秘塔科技 提供兼容 OpenAI 协议 的模型 API 服务,适合接入图片、文本和音频创作流程。通过 专属链接 注册可领取优惠。
@@ -53,21 +53,20 @@
- 无限画布 × Infistar.ai 无限星河|内置原生画布 · 全能多模态 API 💡 原生集成,即点即用: Infistar.ai 已原生上架无限画布!同时提供低至官方 1 折的稳定 API 中转服务,模型倍率与调用明细全程透明。 🎨 多模态生图/生视频: 完美适配 Seedance、FLUX、Midjourney、Sora、Runway、Luma、可灵(Kling)等顶级图片与视频大模型。 🧠 全系语言模型: 覆盖 OpenAI、Claude、Gemini、Grok、DeepSeek、Qwen、GLM 等国内外主流模型,兼容 OpenAI 标准接口。 ⚡ 动态调度: 多路供应保障高可用,拒绝断连。 🎁 专属福利: 通过 专属链接 注册,立享赠送额度/专属折扣/首充权益!
+ 元境幻生 × Infistar.ai 无限星河|多模态 API 💡 提供兼容 OpenAI 标准接口的图片、文本和音频模型服务,支持透明的模型倍率与调用明细。通过 专属链接 注册可享优惠。
## 核心功能
-- 无限画布:多画布项目、节点拖拽缩放、连线、小地图、撤销重做、导入导出。
-- AI 创作:浏览器前台直连你配置的 OpenAI 兼容接口,支持文生图、图生图、参考图编辑、文本问答、音频和视频生成。
-- 画布助手:围绕选中节点和上游节点对话、生图,并把结果插回画布。
-- 本地 Agent:通过本机 Canvas Agent 连接 Codex / Claude Code,让 Agent 通过 MCP 操作当前画布;
-- Codex App 插件:提供 Codex app 插件,安装后会自动注册 MCP 并尝试拉起本地 Agent。
-- 插件系统:支持通过 URL 动态安装 / 启用 / 更新 / 卸载远程节点插件,并提供 TypeScript SDK 自行开发画布节点插件。
-- 自定义接口调用:可自定义生图 / 视频接口的调用方式,灵活适配各类中转站与自建服务。
-- 提示词库:浏览器前端直连多个 GitHub 开源项目,并缓存到 IndexedDB。
+- 首页与项目库:保留网站首页、多画布项目管理、导入导出和项目删除。
+- 无限画布:节点拖拽缩放、连线、分组、小地图、撤销重做和多种画布背景。
+- AI 创作:浏览器直连用户配置的接口,支持图片、文本和音频生成以及参考图编辑。
+- 图片工具:反推提示词、多角度生成、裁剪、遮罩编辑、切分和本地放大。
+- 资产与同步:本地资产库支持文本和图片;WebDAV 可同步画布、资产及图片/音频文件。
+- 模型配置:支持多渠道、默认模型、配置导入导出,以及图片、文本和音频自定义调用脚本。
+- 内部插件能力:保留画布节点注册、运行时和 TypeScript SDK,供项目内部构建扩展节点。
完整功能说明见 [功能介绍](docs/content/docs/overview/features.mdx)。
@@ -75,18 +74,21 @@
## 快速开始
-AI API Key、Base URL、画布、素材和生成记录默认保存在浏览器本地。
+AI API Key、Base URL、画布和资产默认保存在浏览器本地,也可以使用 WebDAV 保存画布资产文件。
### 本地开发
```bash
git clone git@github.com:basketikun/infinite-canvas.git
cd infinite-canvas
+npm --prefix admin install --ignore-scripts
cd web
bun install
-bun run dev
+bun run dev:all
```
+统一入口为 `http://localhost:3000`:前台访问 `/`,后台管理台访问 `/admin/`。
+
### Docker 运行
```bash
@@ -95,11 +97,11 @@ cd infinite-canvas
docker compose up -d
```
-运行后默认端口3000,可访问 `http://localhost:3000`。
+运行后默认只使用 3000 端口,可访问 `http://localhost:3000`;后台管理台位于 `http://localhost:3000/admin/`。
首次打开后进入右上角配置,填入自己的 OpenAI 兼容 `Base URL` 和 `API Key`。
-如果默认的OpenAI接口调用方式与您的API不同,可自定义生图/视频脚本调用。
+如果默认的 OpenAI 接口调用方式与您的 API 不同,可自定义图片、文本或音频脚本调用。
## 效果展示
diff --git a/SECURITY.md b/SECURITY.md
index d65c860..d31c30e 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -2,7 +2,7 @@
## Supported Versions
-infinite-canvas is in active development. Security fixes are accepted for the
+MiragenFlow is in active development. Security fixes are accepted for the
`main` branch and the latest tagged release. Older versions may be handled on a
best-effort basis.
@@ -16,7 +16,7 @@ Preferred reporting channels:
1. Use GitHub private vulnerability reporting or a GitHub Security Advisory for
this repository, if available.
2. If a private GitHub report is not available, email `1844025705@qq.com` with
- the subject `[infinite-canvas security]`.
+ the subject `[MiragenFlow security]`.
3. If neither private channel is available, open a public issue that asks for a
private contact channel and does not include technical exploit details.
@@ -77,4 +77,3 @@ this community project.
Please allow time for investigation and remediation before publishing details.
Credit will be given on request unless you prefer to remain anonymous.
-
diff --git a/VERSION b/VERSION
index 49dd460..6b60281 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-v0.16.0
\ No newline at end of file
+v0.17.0
diff --git a/admin/.editorconfig b/admin/.editorconfig
new file mode 100644
index 0000000..3638cec
--- /dev/null
+++ b/admin/.editorconfig
@@ -0,0 +1,13 @@
+# indicate this is the root of the project
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.md]
+trim_trailing_whitespace = false
diff --git a/admin/.env.example b/admin/.env.example
new file mode 100644
index 0000000..865b48a
--- /dev/null
+++ b/admin/.env.example
@@ -0,0 +1,2 @@
+VITE_BASE_URL=/admin/
+NODE_ENV=development
diff --git a/admin/.eslintignore b/admin/.eslintignore
new file mode 100644
index 0000000..98aae96
--- /dev/null
+++ b/admin/.eslintignore
@@ -0,0 +1,5 @@
+build
+mock
+config
+public
+scripts
diff --git a/admin/.eslintrc.js b/admin/.eslintrc.js
new file mode 100644
index 0000000..53cc55c
--- /dev/null
+++ b/admin/.eslintrc.js
@@ -0,0 +1,60 @@
+module.exports = {
+ root: true,
+ extends: [
+ 'airbnb-base',
+ 'prettier',
+ 'plugin:react/recommended',
+ 'plugin:import/typescript',
+ 'plugin:@typescript-eslint/recommended',
+ ],
+ plugins: ['eslint-plugin-prettier'],
+ parser: '@typescript-eslint/parser',
+ env: {
+ jest: true,
+ },
+ settings: {
+ react: {
+ version: 'detect',
+ },
+ },
+ rules: {
+ 'no-shadow': 'off',
+ 'no-param-reassign': ['error', { props: false }],
+ 'no-console': 'off',
+ 'no-plusplus': [
+ 'error',
+ {
+ allowForLoopAfterthoughts: true,
+ },
+ ],
+ 'react/display-name': 'off',
+ // jsx 单引号
+ 'jsx-quotes': [2, 'prefer-single'],
+ 'import/no-cycle': 'off', // TODO: remove
+ 'import/extensions': 'off',
+ 'import/no-unresolved': 'off',
+ 'import/order': 'off',
+ 'import/prefer-default-export': 'off',
+ 'import/no-extraneous-dependencies': ['error', { devDependencies: true }],
+ // 关闭variable必须全部大写规则
+ '@typescript-eslint/naming-convention': [
+ 'error',
+ {
+ selector: 'variable',
+ modifiers: ['const'],
+ format: null,
+ },
+ ],
+ '@typescript-eslint/no-empty-function': 'off',
+ '@typescript-eslint/no-explicit-any': 'off',
+ '@typescript-eslint/ban-types': 'off',
+ // 统一eslint prettier配置
+ 'prettier/prettier': [
+ 'warn',
+ {},
+ {
+ usePrettierrc: true,
+ },
+ ],
+ },
+};
diff --git a/admin/.github/ISSUE_TEMPLATE/bug-report.zh-CN.yml b/admin/.github/ISSUE_TEMPLATE/bug-report.zh-CN.yml
new file mode 100644
index 0000000..c3bc775
--- /dev/null
+++ b/admin/.github/ISSUE_TEMPLATE/bug-report.zh-CN.yml
@@ -0,0 +1,79 @@
+name: 反馈 Bug
+description: 通过 github 模板进行 Bug 反馈。
+title: "[组件名称] 描述问题的标题"
+body:
+ - type: markdown
+ attributes:
+ value: |
+ # 欢迎你的参与
+ tdesign-react-starter 的 Issue 列表接受 bug 报告或是新功能请求。也可加入官方社区:
+
+ 在发布一个 Issue 前,请确保:
+ - 在 [常见问题](https://tdesign.tencent.com/about/faq)、[更新日志](https://tdesign.tencent.com/about/release) 和 [旧Issue列表](https://github.com/Tencent/tdesign-react-starter/issues?q=is%3Aissue) 中搜索过你的问题。(你的问题可能已有人提出,也可能已在最新版本中被修正)
+ - 如果你发现一个已经关闭的旧 Issue 在最新版本中仍然存在,不要在旧 Issue 下面留言,请建一个新的 issue。
+
+ - type: input
+ id: version
+ attributes:
+ label: tdesign-react-starter 版本
+ description: 请检查在最新项目版本中能否重现此 issue。
+ placeholder: 请填写
+ validations:
+ required: true
+
+ - type: input
+ id: reproduce
+ attributes:
+ label: 重现链接
+ description: 请提供尽可能精简的 CodePen、CodeSandbox 或 GitHub 仓库的链接。请不要填无关链接,否则你的 Issue 将被关闭。
+ placeholder: 请填写
+
+ - type: textarea
+ id: reproduceSteps
+ attributes:
+ label: 重现步骤
+ description: 请清晰的描述重现该 Issue 的步骤,这能帮助我们快速定位问题。没有清晰重现步骤将不会被修复,标有 'need reproduction' 的 Issue 在 7 天内不提供相关步骤,将被关闭。
+ placeholder: 请填写
+
+ - type: textarea
+ id: expect
+ attributes:
+ label: 期望结果
+ placeholder: 请填写
+
+ - type: textarea
+ id: actual
+ attributes:
+ label: 实际结果
+ placeholder: 请填写
+
+ - type: input
+ id: frameworkVersion
+ attributes:
+ label: 框架版本
+ placeholder: Vue(3.2.0)
+
+ - type: input
+ id: browsersVersion
+ attributes:
+ label: 浏览器版本
+ placeholder: Chrome(8.213.231.123)
+
+ - type: input
+ id: systemVersion
+ attributes:
+ label: 系统版本
+ placeholder: MacOS(11.2.3)
+
+ - type: input
+ id: nodeVersion
+ attributes:
+ label: Node版本
+ placeholder: 请填写
+
+ - type: textarea
+ id: remarks
+ attributes:
+ label: 补充说明
+ description: 可以是遇到这个 bug 的业务场景、上下文等信息。
+ placeholder: 请填写
diff --git a/admin/.github/ISSUE_TEMPLATE/config.yml b/admin/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000..cec7613
--- /dev/null
+++ b/admin/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,5 @@
+blank_issues_enabled: true
+contact_links:
+ - name: 使用 issue-helper 新建
+ url: https://Tencent.github.io/tdesign/issue-helper/?lang=zh-CN&repo=Tencent/tdesign
+ about: 使用 https://Tencent.github.io/tdesign/issue-helper/ 创建 issue,其中包含 bug 和 feature,表单提交更加严格。
diff --git a/admin/.github/ISSUE_TEMPLATE/feature-report.zh-CN.yml b/admin/.github/ISSUE_TEMPLATE/feature-report.zh-CN.yml
new file mode 100644
index 0000000..0668509
--- /dev/null
+++ b/admin/.github/ISSUE_TEMPLATE/feature-report.zh-CN.yml
@@ -0,0 +1,30 @@
+name: 反馈新功能
+description: 通过 github 模板进行新功能反馈。
+title: "[组件名称] 描述问题的标题"
+body:
+ - type: markdown
+ attributes:
+ value: |
+ # 欢迎你的参与
+ tdesign-react-starter 的 Issue 列表接受 bug 报告或是新功能请求。也可加入官方社区:
+
+ 在发布一个 Issue 前,请确保:
+ - 在 [常见问题](https://tdesign.tencent.com/about/faq)、[更新日志](https://tdesign.tencent.com/about/release) 和 [旧Issue列表](https://github.com/Tencent/tdesign-react-starter/issues?q=is%3Aissue) 中搜索过你的问题。(你的问题可能已有人提出,也可能已在最新版本中被修正)
+ - 如果你发现一个已经关闭的旧 Issue 在最新版本中仍然存在,不要在旧 Issue 下面留言,请建一个新的 issue。
+
+ - type: textarea
+ id: functionContent
+ attributes:
+ label: 这个功能解决了什么问题
+ description: 请详尽说明这个需求的用例和场景。最重要的是:解释清楚是怎样的用户体验需求催生了这个功能上的需求。我们将考虑添加在现有 API 无法轻松实现的功能。新功能的用例也应当足够常见。
+ placeholder: 请填写
+ validations:
+ required: true
+
+ - type: textarea
+ id: functionalExpectations
+ attributes:
+ label: 你建议的方案是什么
+ placeholder: 请填写
+ validations:
+ required: true
diff --git a/admin/.github/PULL_REQUEST_TEMPLATE.md b/admin/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..a97d15e
--- /dev/null
+++ b/admin/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,52 @@
+
+
+### 🤔 这个 PR 的性质是?
+
+- [ ] 日常 bug 修复
+- [ ] 新特性提交
+- [ ] 文档改进
+- [ ] 演示代码改进
+- [ ] 组件样式/交互改进
+- [ ] CI/CD 改进
+- [ ] 重构
+- [ ] 代码风格优化
+- [ ] 测试用例
+- [ ] 分支合并
+- [ ] 其他
+
+### 🔗 相关 Issue
+
+
+
+### 💡 需求背景和解决方案
+
+
+
+### 📝 更新日志
+
+
+
+- fix(组件名称): 处理问题或特性描述 ...
+
+- [ ] 本条 PR 不需要纳入 Changelog
+
+### ☑️ 请求合并前的自查清单
+
+⚠️ 请自检并全部**勾选全部选项**。⚠️
+
+- [ ] 文档已补充或无须补充
+- [ ] 代码演示已提供或无须提供
+- [ ] TypeScript 定义已补充或无须补充
+- [ ] Changelog 已提供或无须提供
diff --git a/admin/.github/configs/typos_config.toml b/admin/.github/configs/typos_config.toml
new file mode 100644
index 0000000..a935ba0
--- /dev/null
+++ b/admin/.github/configs/typos_config.toml
@@ -0,0 +1,4 @@
+files.extend-exclude = [
+ "package-lock.json",
+ "src/configs/color.ts"
+]
\ No newline at end of file
diff --git a/admin/.github/dependabot.yml b/admin/.github/dependabot.yml
new file mode 100644
index 0000000..c37a937
--- /dev/null
+++ b/admin/.github/dependabot.yml
@@ -0,0 +1,20 @@
+# Basic dependabot.yml file with
+# minimum configuration for two package managers
+
+version: 2
+updates:
+ # Enable version updates for npm
+ - package-ecosystem: "npm"
+ # Look for `package.json` and `lock` files in the `root` directory
+ directory: "/"
+ # Check the npm registry for updates every day (weekdays)
+ schedule:
+ interval: "monthly"
+
+ # Enable version updates for Docker
+ - package-ecosystem: "docker"
+ # Look for a `Dockerfile` in the `root` directory
+ directory: "/"
+ # Check for updates once a week
+ schedule:
+ interval: "monthly"
diff --git a/admin/.github/workflows/issue-assignees.temp.yml b/admin/.github/workflows/issue-assignees.temp.yml
new file mode 100644
index 0000000..f060efb
--- /dev/null
+++ b/admin/.github/workflows/issue-assignees.temp.yml
@@ -0,0 +1,52 @@
+# force copy from tencent/tdesign
+name: Issue Add Assigness
+
+on:
+ issues:
+ types: [opened, edited]
+
+jobs:
+ mark-duplicate:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: wow-actions/auto-comment@v1
+ with:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ issuesOpened: |
+ 👋 @{{ author }},感谢给 TDesign 提出了 issue。
+ 请根据 issue 模版确保背景信息的完善,我们将调查并尽快回复你。
+
+ # https://docs.github.com/cn/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#issues
+ - uses: 94dreamer/issue-assignees@main
+ id: assignees
+ with:
+ project_name: ${{github.event.repository.name}}
+ issue_title: ${{github.event.issue.title}}
+
+ - run: echo ${{ steps.assignees.outputs.contributors }}
+ - name: Add assigness
+ if: steps.assignees.outputs.contributors != ''
+ uses: actions-cool/issues-helper@v3
+ with:
+ actions: 'add-assignees'
+ token: ${{ secrets.GITHUB_TOKEN }}
+ issue-number: ${{ github.event.issue.number }}
+ assignees: ${{ steps.assignees.outputs.contributors }}
+
+ - run: |
+ contributors=${{ steps.assignees.outputs.contributors }}
+ contributorstring=${contributors//,/ @}
+ echo "::set-output name=string::@$contributorstring"
+ id: contributors
+
+ - name: 通知贡献者
+ if: steps.assignees.outputs.contributors != ''
+ uses: TDesignOteam/workflows/actions/maintain-one-comment@main
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ body: |
+ ♥️ 有劳 ${{ steps.contributors.outputs.string }} 尽快确认问题。
+ 确认有效后将下一步计划和可能需要的时间回复给 @${{ github.event.issue.user.login }} 。
+
+ number: ${{ github.event.issue.number }}
+ body-include: ""
diff --git a/admin/.github/workflows/issue-help-wanted.temp.yml b/admin/.github/workflows/issue-help-wanted.temp.yml
new file mode 100644
index 0000000..94e9bbd
--- /dev/null
+++ b/admin/.github/workflows/issue-help-wanted.temp.yml
@@ -0,0 +1,22 @@
+# force copy from tencent/tdesign
+name: Issue Help wanted
+on:
+ issues:
+ types:
+ - labeled
+jobs:
+ add-comment:
+ if: github.event.label.name == 'help wanted'
+ runs-on: ubuntu-latest
+ permissions:
+ issues: write
+ steps:
+ - name: Add comment
+ uses: peter-evans/create-or-update-comment@v1
+ with:
+ issue-number: ${{ github.event.issue.number }}
+ body: |
+ 任何人都可以处理此问题。
+ **请务必在您的 `pull request` 中引用此问题。** :sparkles:
+ 感谢你的贡献! :sparkles:
+ reactions: heart
\ No newline at end of file
diff --git a/admin/.github/workflows/issue-mark-duplicate.temp.yml b/admin/.github/workflows/issue-mark-duplicate.temp.yml
new file mode 100644
index 0000000..3017723
--- /dev/null
+++ b/admin/.github/workflows/issue-mark-duplicate.temp.yml
@@ -0,0 +1,19 @@
+# force copy from tencent/tdesign
+# 当在 issue 的 comment 回复类似 `Duplicate of #111` 这样的话,issue 将被自动打上 重复提交标签 并且 cloese
+name: Issue Mark Duplicate
+
+on:
+ issue_comment:
+ types: [created, edited]
+
+jobs:
+ mark-duplicate:
+ runs-on: ubuntu-latest
+ steps:
+ - name: mark-duplicate
+ uses: actions-cool/issues-helper@v2
+ with:
+ actions: "mark-duplicate"
+ token: ${{ secrets.GITHUB_TOKEN }}
+ duplicate-labels: "duplicate"
+ close-issue: true
diff --git a/admin/.github/workflows/issue-reply.temp.yml b/admin/.github/workflows/issue-reply.temp.yml
new file mode 100644
index 0000000..271a94f
--- /dev/null
+++ b/admin/.github/workflows/issue-reply.temp.yml
@@ -0,0 +1,21 @@
+# force copy from tencent/tdesign
+# 当被打上 Need Reproduce 标签时候,自动提示需要重现实例
+
+name: ISSUE_REPLY
+
+on:
+ issues:
+ types: [labeled]
+
+jobs:
+ issue-reply:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Need Reproduce
+ if: github.event.label.name == 'Need Reproduce'
+ uses: actions-cool/issues-helper@v2
+ with:
+ actions: 'create-comment'
+ issue-number: ${{ github.event.issue.number }}
+ body: |
+ 你好 @${{ github.event.issue.user.login }}, 我们需要你提供一个在线的重现实例以便于我们帮你排查问题。你可以通过点击 [此处](https://codesandbox.io/) 创建一个 codesandbox 或者提供一个最小化的 GitHub 仓库。请确保选择准确的版本。
diff --git a/admin/.github/workflows/pr-spelling.temp.yml b/admin/.github/workflows/pr-spelling.temp.yml
new file mode 100644
index 0000000..48cd8cf
--- /dev/null
+++ b/admin/.github/workflows/pr-spelling.temp.yml
@@ -0,0 +1,14 @@
+# force copy from tencent/tdesign
+name: pr-spell-check
+on: [pull_request]
+
+jobs:
+ run:
+ name: Spell Check with Typos
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Check spelling
+ uses: crate-ci/typos@master
+ with:
+ config: .github/configs/typos_config.toml
diff --git a/admin/.github/workflows/preview-publish.yml b/admin/.github/workflows/preview-publish.yml
new file mode 100644
index 0000000..278ec79
--- /dev/null
+++ b/admin/.github/workflows/preview-publish.yml
@@ -0,0 +1,15 @@
+# 文件名建议统一为 preview-publish
+# 应用 preview.yml 的 demo
+name: PREVIEW_PUBLISH
+
+on:
+ workflow_run:
+ workflows: ["MAIN_PULL_REQUEST"]
+ types:
+ - completed
+
+jobs:
+ call-preview:
+ uses: TDesignOteam/workflows/.github/workflows/reusable-pr-preview.yml@main
+ secrets:
+ TDESIGN_SURGE_TOKEN: ${{ secrets.TDESIGN_SURGE_TOKEN }}
diff --git a/admin/.github/workflows/pull-request.yml b/admin/.github/workflows/pull-request.yml
new file mode 100644
index 0000000..93798f0
--- /dev/null
+++ b/admin/.github/workflows/pull-request.yml
@@ -0,0 +1,14 @@
+# 文件名建议统一为 pull-request.yml
+# 应用 test-build.yml 的 demo
+# 要求 package.json 提供 lint test site:preview
+
+name: MAIN_PULL_REQUEST
+
+on:
+ pull_request:
+ branches: [develop, main, site]
+ types: [opened, synchronize, reopened]
+
+jobs:
+ call-test-build:
+ uses: TDesignOteam/workflows/.github/workflows/reusable-unit-test.yml@main
diff --git a/admin/.gitignore b/admin/.gitignore
new file mode 100644
index 0000000..f4293fa
--- /dev/null
+++ b/admin/.gitignore
@@ -0,0 +1,25 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/coverage
+
+# production
+/dist
+
+# misc
+.DS_Store
+.idea
+.VSCodeCounter
+
+# log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# lock 文件 请根据自身项目或团队需求选择具体的包管理工具 并移除具体的ignore的lock文件
+yarn.lock
+pnpm-lock.yaml
+
+# code editor setting
+/.vscode
diff --git a/admin/.husky/pre-commit b/admin/.husky/pre-commit
new file mode 100755
index 0000000..52b5d1a
--- /dev/null
+++ b/admin/.husky/pre-commit
@@ -0,0 +1,8 @@
+#!/bin/sh
+. "$(dirname "$0")/_/husky.sh"
+
+if [[ `uname` == 'Darwin' ]]; then
+ PATH="/usr/local/bin:$PATH"
+fi
+
+npm run lint:fix
diff --git a/admin/.prettierrc.js b/admin/.prettierrc.js
new file mode 100644
index 0000000..8ae8226
--- /dev/null
+++ b/admin/.prettierrc.js
@@ -0,0 +1,22 @@
+module.exports = {
+ printWidth: 120,
+ tabWidth: 2,
+ useTabs: false,
+ semi: true,
+ singleQuote: true,
+ quoteProps: 'as-needed',
+ jsxSingleQuote: true,
+ trailingComma: 'all',
+ bracketSpacing: true,
+ jsxBracketSameLine: false,
+ arrowParens: 'always',
+ rangeStart: 0,
+ rangeEnd: null,
+ requirePragma: false,
+ insertPragma: false,
+ proseWrap: 'preserve',
+ htmlWhitespaceSensitivity: 'css',
+ vueIndentScriptAndStyle: false,
+ endOfLine: 'lf',
+ embeddedLanguageFormatting: 'auto',
+};
diff --git a/admin/CHANGELOG.md b/admin/CHANGELOG.md
new file mode 100644
index 0000000..b629a7f
--- /dev/null
+++ b/admin/CHANGELOG.md
@@ -0,0 +1,106 @@
+
+# Changelog
+
+## 0.3.1
+### 🚀 Features
+
+- 移除所有内置主题色相关代码,全部通过 tvision-color 计算获取 by @uyarn #144
+
+- 卡片统一默认样式为无边框 by @uyarn #144
+
+**Full Changelog**: https://github.com/Tencent/tdesign-react-starter/compare/0.3.0...0.3.1
+
+## 0.3.0
+❗ Breaking Changes
+
+- 统一使用`.env`文件区分环境 生产环境改为`release` by @uyarn in https://github.com/Tencent/tdesign-react-starter/pull/136
+
+🚀 Features
+
+- 默认路由模式改为`history`路由 by @uyarn in https://github.com/Tencent/tdesign-react-starter/pull/136
+
+**Full Changelog**: https://github.com/Tencent/tdesign-react-starter/compare/0.2.0...0.3.0
+
+## 0.2.0
+### ❗ Breaking Changes
+
+- 升级组件库依赖至 0.43+ 更新主题色配色方案 by @uyarn in https://github.com/Tencent/tdesign-react-starter/pull/129
+
+### 🚀 Features
+
+- 新增自定义颜色面板选择 by @uyarn in https://github.com/Tencent/tdesign-react-starter/pull/129
+
+### 🐞 Bug Fixes
+
+- 修复卡片面板的标题丢失的问题 by @uyarn https://github.com/Tencent/tdesign-react-starter/pull/129
+
+**Full Changelog**: https://github.com/Tencent/tdesign-react-starter/compare/0.1.6...0.2.0
+
+## 0.1.6
+### Features
+
+- 升级组件库依赖至0.42+ 版本
+
+### Bug Fixes
+
+- 修复部分页面样式展示的缺陷
+
+- 优化顶部菜单布局
+
+## 0.1.5
+## Features
+
+- 新增卡片列表页 by @xucz in https://github.com/Tencent/tdesign-react-starter/pull/91
+
+- 菜单路由配置`hidden`和`single`功能 by @xucz in https://github.com/Tencent/tdesign-react-starter/pull/95
+
+## Bug Fixes
+
+- 同步DatePicker组件升级的改动 by @uyarn in https://github.com/Tencent/tdesign-react-starter/pull/87
+
+**Full Changelog**: https://github.com/Tencent/tdesign-react-starter/compare/0.1.4...0.1.5
+
+## 0.1.4
+## Features
+
+- 增加面包屑导航
+
+- 搜索框样式优化
+
+- Layout组件命名语义化
+
+- 图表主题功能优化
+
+## 0.1.3
+1. 升级tdesign-react
+
+2. 替换为tdesign-react Card 组件
+
+3. table样式优化
+
+## 0.1.2
+- 升级axios、tdesign @xucz
+
+- fix: loss css token @uyarn
+
+- 修复图表文字重叠 @xucz
+
+## 0.1.1
+## Features
+
+- 新增登录页 #42 by @uyarn
+
+- Logo点击跳转首页 #45 by @aouos
+
+## 0.1.0
+## Bug Fixes
+
+- 修复页面主题色 by @xucz
+
+- 修复页面布局
+
+- 修复版本图表渲染问题
+
+## Features
+
+- 增加数据Mock by @xucz
diff --git a/admin/LICENSE b/admin/LICENSE
new file mode 100644
index 0000000..be586af
--- /dev/null
+++ b/admin/LICENSE
@@ -0,0 +1,9 @@
+MIT License
+
+Copyright (c) 2021-present TDesign
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/admin/README-zh_CN.md b/admin/README-zh_CN.md
new file mode 100644
index 0000000..909c3e1
--- /dev/null
+++ b/admin/README-zh_CN.md
@@ -0,0 +1,97 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+简体中文 | [English](./README.md)
+
+### 项目简介
+
+TDesign React Starter 是一个基于 tdesign-react,使用 `React`、`Vite2`开发,可进行个性化主题配置,旨在提供项目开箱即用的、配置式的中后台项目。
+
+
+
+### 特性
+
+- 内置多种常用的中后台页面
+- 完善的目录结构
+- 完善的代码规范配置
+- 支持暗黑模式
+- 自定义主题颜色
+- 多种空间布局
+- 内置 Mock 数据方案
+
+### 开发
+
+```bash
+## 安装依赖
+npm install
+
+## 启动项目
+npm run dev
+
+## mock方式启动项目
+npm run dev:mock
+```
+
+### 构建
+
+```bash
+## 构建正式环境
+npm run build
+
+## 构建测试环境
+npm run build:test
+```
+
+### 其他
+
+```bash
+## 预览构建产物
+npm run preview
+
+## 代码格式检查
+npm run lint
+
+## 代码格式检查与自动修复
+npm run lint:fix
+
+```
+
+### 如何贡献
+
+非常欢迎您的贡献!提交您的 [Issue](https://github.com/tencent/tdesign-react-starter/issues/new/choose) 或者提交 [Pull Request](https://github.com/Tencent/tdesign-react-starter/pulls)。
+
+#### Pull Request
+
+1. Fork 代码!
+2. 创建自己的分支: `git checkout -b feat/xxxx`
+3. 提交你的修改: `git commit -a 'feat(project): describe'`
+4. 推送您的分支: `git push origin feat/xxxx`
+5. 提交 `pull request`
+
+#### 贡献提交规范
+
+- [Angular Convention](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular)
+
+### 兼容性
+
+| [ ](http://godban.github.io/browsers-support-badges/) IE / Edge | [ ](http://godban.github.io/browsers-support-badges/)Firefox | [ ](http://godban.github.io/browsers-support-badges/)Chrome | [ ](http://godban.github.io/browsers-support-badges/)Safari |
+| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| Edge >=84 | Firefox >=83 | Chrome >=84 | Safari >=14.1 |
+
+### 开源协议
+
+TDesign 遵循 [MIT 协议](https://github.com/Tencent/tdesign-react-starter/LICENSE)。
+
diff --git a/admin/README.md b/admin/README.md
new file mode 100644
index 0000000..9cba5a3
--- /dev/null
+++ b/admin/README.md
@@ -0,0 +1,97 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+English | [简体中文](./README-zh_CN.md)
+### Introduction
+
+TDesign React Starter is a TDesign-based project developed with `React`, `Vite`. It can be customized theme configuration, and aims to provide project out-of-the-box, configuration-style middle and background projects.
+
+
+ Live Preview
+ ·
+ Documentation
+
+
+
+
+### Features
+
+- Various provided pages for develop
+- Complete directory structure for develop
+- Code specification configuration
+- Support dark mode
+- Custom theme colors
+- Various space layouts
+- Mock data scheme
+
+### Usage
+
+> Initialize project with our CLI tool `tdesign-starter-cli`
+
+```bash
+## install tdesign-starter-cli
+npm i tdesign-starter-cli@latest -g
+
+## create project
+td-starter init
+```
+
+### Develop
+
+```bash
+## install dependencies
+npm install
+
+## set up
+npm run dev
+```
+
+### Build
+
+```bash
+## build
+npm run build
+
+## build for test
+npm run build:test
+```
+
+
+### Contributing Guide
+
+We welcome contributions to our project. Create your [Issue](https://github.com/tencent/tdesign-react-starter/issues/new/choose) or Submit your [Pull Request](https://github.com/Tencent/tdesign-react-starter/pulls).
+
+#### Pull Request
+
+1. Fork it!
+2. Create your branch: `git checkout -b feat/xxxx`
+3. Commit: `git commit -a 'feat(project): describe'`
+4. Push: `git push origin feat/xxxx`
+5. New Pull Request `pull request`
+
+#### Commit Specification
+
+- [Angular Convention](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular)
+
+### Compatibility
+
+| [ ](http://godban.github.io/browsers-support-badges/) IE / Edge | [ ](http://godban.github.io/browsers-support-badges/)Firefox | [ ](http://godban.github.io/browsers-support-badges/)Chrome | [ ](http://godban.github.io/browsers-support-badges/)Safari |
+| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| Edge >=84 | Firefox >=83 | Chrome >=84 | Safari >=14.1 |
+
+### License
+
+The MIT License. Please see [the license file](LICENSE) for more information.
diff --git a/admin/docs/docs-starter.png b/admin/docs/docs-starter.png
new file mode 100644
index 0000000..6c8d7e7
Binary files /dev/null and b/admin/docs/docs-starter.png differ
diff --git a/admin/index.html b/admin/index.html
new file mode 100644
index 0000000..68016fd
--- /dev/null
+++ b/admin/index.html
@@ -0,0 +1,38 @@
+
+
+
+
+
+ 元境幻生管理台
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/admin/mock/api/getList.js b/admin/mock/api/getList.js
new file mode 100644
index 0000000..0605035
--- /dev/null
+++ b/admin/mock/api/getList.js
@@ -0,0 +1,28 @@
+const Mock = require('mockjs');
+export default {
+ url: '/api/get-list',
+ method: 'get',
+ response: () => {
+ return {
+ code: 0,
+ msg: 'ok',
+ data: {
+ ...Mock.mock({
+ 'list|100': [
+ {
+ 'index|+1': 1,
+ 'status|1': '@natural(0, 4)',
+ no: 'BH00@natural(01, 100)',
+ name: '@city()办公用品采购项目',
+ 'paymentType|1': '@natural(0, 1)',
+ 'contractType|1': '@natural(0, 2)',
+ updateTime: '2020-05-30 @date("HH:mm:ss")',
+ amount: '@natural(10, 500),000,000',
+ adminName: '@cname()',
+ },
+ ],
+ }),
+ },
+ };
+ },
+};
diff --git a/admin/mock/index.js b/admin/mock/index.js
new file mode 100644
index 0000000..08b2a08
--- /dev/null
+++ b/admin/mock/index.js
@@ -0,0 +1,3 @@
+import getList from './api/getList';
+
+export default [getList];
diff --git a/admin/package-lock.json b/admin/package-lock.json
new file mode 100644
index 0000000..04f93a3
--- /dev/null
+++ b/admin/package-lock.json
@@ -0,0 +1,8208 @@
+{
+ "name": "@tencent/tdesign-react-starter",
+ "version": "0.3.1",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "@tencent/tdesign-react-starter",
+ "version": "0.3.1",
+ "dependencies": {
+ "@reduxjs/toolkit": "^1.8.5",
+ "axios": "^1.2.0",
+ "axios-jsonp": "^1.0.4",
+ "classnames": "^2.3.1",
+ "dayjs": "^1.10.7",
+ "echarts": "^5.3.0",
+ "echarts-for-react": "^3.0.2",
+ "lodash": "^4.17.21",
+ "mockjs": "^1.1.0",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-redux": "^7.2.4",
+ "react-router-dom": "^6.3.0",
+ "tdesign-icons-react": "^0.6.0",
+ "tdesign-react": "^1.15.0",
+ "tvision-color": "^1.5.0"
+ },
+ "devDependencies": {
+ "@honkhonk/vite-plugin-svgr": "^1.1.0",
+ "@types/echarts": "^4.9.13",
+ "@types/lodash": "^4.14.178",
+ "@types/mockjs": "^1.0.6",
+ "@types/node": "^20.2.5",
+ "@types/react": "^18.2.0",
+ "@types/react-dom": "^18.2.0",
+ "@types/react-router-dom": "^5.3.3",
+ "@vitejs/plugin-react": "^1.3.2",
+ "eslint": "^8.3.0",
+ "eslint-config-airbnb-base": "^15.0.0",
+ "eslint-config-prettier": "^8.4.0",
+ "eslint-config-react-app": "^7.0.0",
+ "eslint-plugin-import": "^2.25.4",
+ "eslint-plugin-prettier": "^4.0.0",
+ "eslint-plugin-react": "^7.28.0",
+ "husky": "^8.0.1",
+ "less": "^4.1.3",
+ "prettier": "^2.5.1",
+ "typescript": "^4.8.4",
+ "vite": "^2.9.15",
+ "vite-plugin-mock": "^2.9.6"
+ }
+ },
+ "node_modules/@ampproject/remapping": {
+ "version": "2.3.0",
+ "resolved": "https://mirrors.tencent.com/npm/@ampproject/remapping/-/remapping-2.3.0.tgz",
+ "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.26.2",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/code-frame/-/code-frame-7.26.2.tgz",
+ "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.25.9",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.26.5",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/compat-data/-/compat-data-7.26.5.tgz",
+ "integrity": "sha512-XvcZi1KWf88RVbF9wn8MN6tYFloU5qX8KjuF3E1PVBmJ9eypXfs4GRiJwLuTZL0iSnJUKn1BFPa5BPZZJyFzPg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.26.0",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/core/-/core-7.26.0.tgz",
+ "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==",
+ "dev": true,
+ "dependencies": {
+ "@ampproject/remapping": "^2.2.0",
+ "@babel/code-frame": "^7.26.0",
+ "@babel/generator": "^7.26.0",
+ "@babel/helper-compilation-targets": "^7.25.9",
+ "@babel/helper-module-transforms": "^7.26.0",
+ "@babel/helpers": "^7.26.0",
+ "@babel/parser": "^7.26.0",
+ "@babel/template": "^7.25.9",
+ "@babel/traverse": "^7.25.9",
+ "@babel/types": "^7.26.0",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/eslint-parser": {
+ "version": "7.26.5",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/eslint-parser/-/eslint-parser-7.26.5.tgz",
+ "integrity": "sha512-Kkm8C8uxI842AwQADxl0GbcG1rupELYLShazYEZO/2DYjhyWXJIOUVOE3tBYm6JXzUCNJOZEzqc4rCW/jsEQYQ==",
+ "dev": true,
+ "dependencies": {
+ "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1",
+ "eslint-visitor-keys": "^2.1.0",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": "^10.13.0 || ^12.13.0 || >=14.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.11.0",
+ "eslint": "^7.5.0 || ^8.0.0 || ^9.0.0"
+ }
+ },
+ "node_modules/@babel/eslint-parser/node_modules/eslint-visitor-keys": {
+ "version": "2.1.0",
+ "resolved": "https://mirrors.tencent.com/npm/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
+ "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.26.5",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/generator/-/generator-7.26.5.tgz",
+ "integrity": "sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/parser": "^7.26.5",
+ "@babel/types": "^7.26.5",
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-annotate-as-pure": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz",
+ "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.26.5",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz",
+ "integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/compat-data": "^7.26.5",
+ "@babel/helper-validator-option": "^7.25.9",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-create-class-features-plugin": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.9.tgz",
+ "integrity": "sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.25.9",
+ "@babel/helper-member-expression-to-functions": "^7.25.9",
+ "@babel/helper-optimise-call-expression": "^7.25.9",
+ "@babel/helper-replace-supers": "^7.25.9",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9",
+ "@babel/traverse": "^7.25.9",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-create-regexp-features-plugin": {
+ "version": "7.26.3",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.26.3.tgz",
+ "integrity": "sha512-G7ZRb40uUgdKOQqPLjfD12ZmGA54PzqDFUv2BKImnC9QIfGhIHKvVML0oN8IUiDq4iRqpq74ABpvOaerfWdong==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.25.9",
+ "regexpu-core": "^6.2.0",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-define-polyfill-provider": {
+ "version": "0.6.3",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.3.tgz",
+ "integrity": "sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-compilation-targets": "^7.22.6",
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "debug": "^4.1.1",
+ "lodash.debounce": "^4.0.8",
+ "resolve": "^1.14.2"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
+ }
+ },
+ "node_modules/@babel/helper-member-expression-to-functions": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz",
+ "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/traverse": "^7.25.9",
+ "@babel/types": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz",
+ "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/traverse": "^7.25.9",
+ "@babel/types": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.26.0",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz",
+ "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.25.9",
+ "@babel/helper-validator-identifier": "^7.25.9",
+ "@babel/traverse": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-optimise-call-expression": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz",
+ "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.26.5",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz",
+ "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-remap-async-to-generator": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz",
+ "integrity": "sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.25.9",
+ "@babel/helper-wrap-function": "^7.25.9",
+ "@babel/traverse": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-replace-supers": {
+ "version": "7.26.5",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz",
+ "integrity": "sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-member-expression-to-functions": "^7.25.9",
+ "@babel/helper-optimise-call-expression": "^7.25.9",
+ "@babel/traverse": "^7.26.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-skip-transparent-expression-wrappers": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz",
+ "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/traverse": "^7.25.9",
+ "@babel/types": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
+ "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
+ "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz",
+ "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-wrap-function": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz",
+ "integrity": "sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==",
+ "dev": true,
+ "dependencies": {
+ "@babel/template": "^7.25.9",
+ "@babel/traverse": "^7.25.9",
+ "@babel/types": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.26.0",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/helpers/-/helpers-7.26.0.tgz",
+ "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/template": "^7.25.9",
+ "@babel/types": "^7.26.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.26.5",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/parser/-/parser-7.26.5.tgz",
+ "integrity": "sha512-SRJ4jYmXRqV1/Xc+TIVG84WjHBXKlxO9sHQnA2Pf12QQEAp1LOh6kDzNHXcUnbH1QI0FDoPPVOt+vyUDucxpaw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.26.5"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz",
+ "integrity": "sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9",
+ "@babel/traverse": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz",
+ "integrity": "sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz",
+ "integrity": "sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.cloud.tencent.com/npm/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz",
+ "integrity": "sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9",
+ "@babel/plugin-transform-optional-chaining": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.13.0"
+ }
+ },
+ "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz",
+ "integrity": "sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9",
+ "@babel/traverse": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-proposal-class-properties": {
+ "version": "7.18.6",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz",
+ "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==",
+ "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-create-class-features-plugin": "^7.18.6",
+ "@babel/helper-plugin-utils": "^7.18.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-proposal-decorators": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.25.9.tgz",
+ "integrity": "sha512-smkNLL/O1ezy9Nhy4CNosc4Va+1wo5w4gzSZeLe6y6dM4mmHfYOCPolXQPHQxonZCF+ZyebxN9vqOolkYrSn5g==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-create-class-features-plugin": "^7.25.9",
+ "@babel/helper-plugin-utils": "^7.25.9",
+ "@babel/plugin-syntax-decorators": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": {
+ "version": "7.18.6",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz",
+ "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==",
+ "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead.",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.18.6",
+ "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-proposal-numeric-separator": {
+ "version": "7.18.6",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz",
+ "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==",
+ "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead.",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.18.6",
+ "@babel/plugin-syntax-numeric-separator": "^7.10.4"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-proposal-optional-chaining": {
+ "version": "7.21.0",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz",
+ "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==",
+ "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead.",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.20.2",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0",
+ "@babel/plugin-syntax-optional-chaining": "^7.8.3"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-proposal-private-methods": {
+ "version": "7.18.6",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz",
+ "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==",
+ "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead.",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-create-class-features-plugin": "^7.18.6",
+ "@babel/helper-plugin-utils": "^7.18.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-proposal-private-property-in-object": {
+ "version": "7.21.0-placeholder-for-preset-env.2",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz",
+ "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-decorators": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.25.9.tgz",
+ "integrity": "sha512-ryzI0McXUPJnRCvMo4lumIKZUzhYUO/ScI+Mz4YVaTLt04DHNSjEUjKVvbzQjZFLuod/cYEc07mJWhzl6v4DPg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-flow": {
+ "version": "7.26.0",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.26.0.tgz",
+ "integrity": "sha512-B+O2DnPc0iG+YXFqOxv2WNuNU97ToWjOomUQ78DouOENWUaM5sVrmet9mcomUGQFwpJd//gvUagXBSdzO1fRKg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-import-assertions": {
+ "version": "7.26.0",
+ "resolved": "https://mirrors.cloud.tencent.com/npm/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz",
+ "integrity": "sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-import-attributes": {
+ "version": "7.26.0",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz",
+ "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-jsx": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz",
+ "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": {
+ "version": "7.8.3",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz",
+ "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-numeric-separator": {
+ "version": "7.10.4",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz",
+ "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-optional-chaining": {
+ "version": "7.8.3",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz",
+ "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-typescript": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz",
+ "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-unicode-sets-regex": {
+ "version": "7.18.6",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz",
+ "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-create-regexp-features-plugin": "^7.18.6",
+ "@babel/helper-plugin-utils": "^7.18.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-arrow-functions": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz",
+ "integrity": "sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-async-generator-functions": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.cloud.tencent.com/npm/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.9.tgz",
+ "integrity": "sha512-RXV6QAzTBbhDMO9fWwOmwwTuYaiPbggWQ9INdZqAYeSHyG7FzQ+nOZaUUjNwKv9pV3aE4WFqFm1Hnbci5tBCAw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9",
+ "@babel/helper-remap-async-to-generator": "^7.25.9",
+ "@babel/traverse": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-async-to-generator": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz",
+ "integrity": "sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.25.9",
+ "@babel/helper-plugin-utils": "^7.25.9",
+ "@babel/helper-remap-async-to-generator": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-block-scoped-functions": {
+ "version": "7.26.5",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.26.5.tgz",
+ "integrity": "sha512-chuTSY+hq09+/f5lMj8ZSYgCFpppV2CbYrhNFJ1BFoXpiWPnnAb7R0MqrafCpN8E1+YRrtM1MXZHJdIx8B6rMQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.26.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-block-scoping": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.9.tgz",
+ "integrity": "sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-class-properties": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz",
+ "integrity": "sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-create-class-features-plugin": "^7.25.9",
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-class-static-block": {
+ "version": "7.26.0",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz",
+ "integrity": "sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-create-class-features-plugin": "^7.25.9",
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.12.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-classes": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz",
+ "integrity": "sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.25.9",
+ "@babel/helper-compilation-targets": "^7.25.9",
+ "@babel/helper-plugin-utils": "^7.25.9",
+ "@babel/helper-replace-supers": "^7.25.9",
+ "@babel/traverse": "^7.25.9",
+ "globals": "^11.1.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-computed-properties": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz",
+ "integrity": "sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9",
+ "@babel/template": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-destructuring": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz",
+ "integrity": "sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-dotall-regex": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz",
+ "integrity": "sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-create-regexp-features-plugin": "^7.25.9",
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-duplicate-keys": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz",
+ "integrity": "sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz",
+ "integrity": "sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-create-regexp-features-plugin": "^7.25.9",
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-dynamic-import": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.cloud.tencent.com/npm/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz",
+ "integrity": "sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-exponentiation-operator": {
+ "version": "7.26.3",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz",
+ "integrity": "sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-export-namespace-from": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz",
+ "integrity": "sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-flow-strip-types": {
+ "version": "7.26.5",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.26.5.tgz",
+ "integrity": "sha512-eGK26RsbIkYUns3Y8qKl362juDDYK+wEdPGHGrhzUl6CewZFo55VZ7hg+CyMFU4dd5QQakBN86nBMpRsFpRvbQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.26.5",
+ "@babel/plugin-syntax-flow": "^7.26.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-for-of": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.25.9.tgz",
+ "integrity": "sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-function-name": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz",
+ "integrity": "sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-compilation-targets": "^7.25.9",
+ "@babel/helper-plugin-utils": "^7.25.9",
+ "@babel/traverse": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-json-strings": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.cloud.tencent.com/npm/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz",
+ "integrity": "sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-literals": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz",
+ "integrity": "sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-logical-assignment-operators": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz",
+ "integrity": "sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-member-expression-literals": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz",
+ "integrity": "sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-modules-amd": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz",
+ "integrity": "sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-module-transforms": "^7.25.9",
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-modules-commonjs": {
+ "version": "7.26.3",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz",
+ "integrity": "sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-module-transforms": "^7.26.0",
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-modules-systemjs": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz",
+ "integrity": "sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-module-transforms": "^7.25.9",
+ "@babel/helper-plugin-utils": "^7.25.9",
+ "@babel/helper-validator-identifier": "^7.25.9",
+ "@babel/traverse": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-modules-umd": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz",
+ "integrity": "sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-module-transforms": "^7.25.9",
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-named-capturing-groups-regex": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz",
+ "integrity": "sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-create-regexp-features-plugin": "^7.25.9",
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-new-target": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz",
+ "integrity": "sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-nullish-coalescing-operator": {
+ "version": "7.26.6",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.26.6.tgz",
+ "integrity": "sha512-CKW8Vu+uUZneQCPtXmSBUC6NCAUdya26hWCElAWh5mVSlSRsmiCPUUDKb3Z0szng1hiAJa098Hkhg9o4SE35Qw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.26.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-numeric-separator": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.cloud.tencent.com/npm/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz",
+ "integrity": "sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-object-rest-spread": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz",
+ "integrity": "sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-compilation-targets": "^7.25.9",
+ "@babel/helper-plugin-utils": "^7.25.9",
+ "@babel/plugin-transform-parameters": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-object-super": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.cloud.tencent.com/npm/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz",
+ "integrity": "sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9",
+ "@babel/helper-replace-supers": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-optional-catch-binding": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.cloud.tencent.com/npm/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz",
+ "integrity": "sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-optional-chaining": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz",
+ "integrity": "sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-parameters": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz",
+ "integrity": "sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-private-methods": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.cloud.tencent.com/npm/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz",
+ "integrity": "sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-create-class-features-plugin": "^7.25.9",
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-private-property-in-object": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz",
+ "integrity": "sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.25.9",
+ "@babel/helper-create-class-features-plugin": "^7.25.9",
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-property-literals": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz",
+ "integrity": "sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-display-name": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.25.9.tgz",
+ "integrity": "sha512-KJfMlYIUxQB1CJfO3e0+h0ZHWOTLCPP115Awhaz8U0Zpq36Gl/cXlpoyMRnUWlhNUBAzldnCiAZNvCDj7CrKxQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.9.tgz",
+ "integrity": "sha512-s5XwpQYCqGerXl+Pu6VDL3x0j2d82eiV77UJ8a2mDHAW7j9SWRqQ2y1fNo1Z74CdcYipl5Z41zvjj4Nfzq36rw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.25.9",
+ "@babel/helper-module-imports": "^7.25.9",
+ "@babel/helper-plugin-utils": "^7.25.9",
+ "@babel/plugin-syntax-jsx": "^7.25.9",
+ "@babel/types": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-development": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.25.9.tgz",
+ "integrity": "sha512-9mj6rm7XVYs4mdLIpbZnHOYdpW42uoiBCTVowg7sP1thUOiANgMb4UtpRivR0pp5iL+ocvUv7X4mZgFRpJEzGw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/plugin-transform-react-jsx": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.9.tgz",
+ "integrity": "sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.9.tgz",
+ "integrity": "sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-pure-annotations": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.25.9.tgz",
+ "integrity": "sha512-KQ/Takk3T8Qzj5TppkS1be588lkbTp5uj7w6a0LeQaTMSckU/wK0oJ/pih+T690tkgI5jfmg2TqDJvd41Sj1Cg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.25.9",
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-regenerator": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz",
+ "integrity": "sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9",
+ "regenerator-transform": "^0.15.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-regexp-modifiers": {
+ "version": "7.26.0",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.26.0.tgz",
+ "integrity": "sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-create-regexp-features-plugin": "^7.25.9",
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-reserved-words": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz",
+ "integrity": "sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-runtime": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.25.9.tgz",
+ "integrity": "sha512-nZp7GlEl+yULJrClz0SwHPqir3lc0zsPrDHQUcxGspSL7AKrexNSEfTbfqnDNJUO13bgKyfuOLMF8Xqtu8j3YQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.25.9",
+ "@babel/helper-plugin-utils": "^7.25.9",
+ "babel-plugin-polyfill-corejs2": "^0.4.10",
+ "babel-plugin-polyfill-corejs3": "^0.10.6",
+ "babel-plugin-polyfill-regenerator": "^0.6.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-shorthand-properties": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz",
+ "integrity": "sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-spread": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz",
+ "integrity": "sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-sticky-regex": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz",
+ "integrity": "sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-template-literals": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.25.9.tgz",
+ "integrity": "sha512-o97AE4syN71M/lxrCtQByzphAdlYluKPDBzDVzMmfCobUjjhAryZV0AIpRPrxN0eAkxXO6ZLEScmt+PNhj2OTw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-typeof-symbol": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.25.9.tgz",
+ "integrity": "sha512-v61XqUMiueJROUv66BVIOi0Fv/CUuZuZMl5NkRoCVxLAnMexZ0A3kMe7vvZ0nulxMuMp0Mk6S5hNh48yki08ZA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-typescript": {
+ "version": "7.26.5",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.26.5.tgz",
+ "integrity": "sha512-GJhPO0y8SD5EYVCy2Zr+9dSZcEgaSmq5BLR0Oc25TOEhC+ba49vUAGZFjy8v79z9E1mdldq4x9d1xgh4L1d5dQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.25.9",
+ "@babel/helper-create-class-features-plugin": "^7.25.9",
+ "@babel/helper-plugin-utils": "^7.26.5",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9",
+ "@babel/plugin-syntax-typescript": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-unicode-escapes": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz",
+ "integrity": "sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-unicode-property-regex": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz",
+ "integrity": "sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-create-regexp-features-plugin": "^7.25.9",
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-unicode-regex": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz",
+ "integrity": "sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-create-regexp-features-plugin": "^7.25.9",
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-unicode-sets-regex": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz",
+ "integrity": "sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-create-regexp-features-plugin": "^7.25.9",
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/preset-env": {
+ "version": "7.26.0",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/preset-env/-/preset-env-7.26.0.tgz",
+ "integrity": "sha512-H84Fxq0CQJNdPFT2DrfnylZ3cf5K43rGfWK4LJGPpjKHiZlk0/RzwEus3PDDZZg+/Er7lCA03MVacueUuXdzfw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/compat-data": "^7.26.0",
+ "@babel/helper-compilation-targets": "^7.25.9",
+ "@babel/helper-plugin-utils": "^7.25.9",
+ "@babel/helper-validator-option": "^7.25.9",
+ "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.9",
+ "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.9",
+ "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.9",
+ "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.25.9",
+ "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.9",
+ "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2",
+ "@babel/plugin-syntax-import-assertions": "^7.26.0",
+ "@babel/plugin-syntax-import-attributes": "^7.26.0",
+ "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6",
+ "@babel/plugin-transform-arrow-functions": "^7.25.9",
+ "@babel/plugin-transform-async-generator-functions": "^7.25.9",
+ "@babel/plugin-transform-async-to-generator": "^7.25.9",
+ "@babel/plugin-transform-block-scoped-functions": "^7.25.9",
+ "@babel/plugin-transform-block-scoping": "^7.25.9",
+ "@babel/plugin-transform-class-properties": "^7.25.9",
+ "@babel/plugin-transform-class-static-block": "^7.26.0",
+ "@babel/plugin-transform-classes": "^7.25.9",
+ "@babel/plugin-transform-computed-properties": "^7.25.9",
+ "@babel/plugin-transform-destructuring": "^7.25.9",
+ "@babel/plugin-transform-dotall-regex": "^7.25.9",
+ "@babel/plugin-transform-duplicate-keys": "^7.25.9",
+ "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.9",
+ "@babel/plugin-transform-dynamic-import": "^7.25.9",
+ "@babel/plugin-transform-exponentiation-operator": "^7.25.9",
+ "@babel/plugin-transform-export-namespace-from": "^7.25.9",
+ "@babel/plugin-transform-for-of": "^7.25.9",
+ "@babel/plugin-transform-function-name": "^7.25.9",
+ "@babel/plugin-transform-json-strings": "^7.25.9",
+ "@babel/plugin-transform-literals": "^7.25.9",
+ "@babel/plugin-transform-logical-assignment-operators": "^7.25.9",
+ "@babel/plugin-transform-member-expression-literals": "^7.25.9",
+ "@babel/plugin-transform-modules-amd": "^7.25.9",
+ "@babel/plugin-transform-modules-commonjs": "^7.25.9",
+ "@babel/plugin-transform-modules-systemjs": "^7.25.9",
+ "@babel/plugin-transform-modules-umd": "^7.25.9",
+ "@babel/plugin-transform-named-capturing-groups-regex": "^7.25.9",
+ "@babel/plugin-transform-new-target": "^7.25.9",
+ "@babel/plugin-transform-nullish-coalescing-operator": "^7.25.9",
+ "@babel/plugin-transform-numeric-separator": "^7.25.9",
+ "@babel/plugin-transform-object-rest-spread": "^7.25.9",
+ "@babel/plugin-transform-object-super": "^7.25.9",
+ "@babel/plugin-transform-optional-catch-binding": "^7.25.9",
+ "@babel/plugin-transform-optional-chaining": "^7.25.9",
+ "@babel/plugin-transform-parameters": "^7.25.9",
+ "@babel/plugin-transform-private-methods": "^7.25.9",
+ "@babel/plugin-transform-private-property-in-object": "^7.25.9",
+ "@babel/plugin-transform-property-literals": "^7.25.9",
+ "@babel/plugin-transform-regenerator": "^7.25.9",
+ "@babel/plugin-transform-regexp-modifiers": "^7.26.0",
+ "@babel/plugin-transform-reserved-words": "^7.25.9",
+ "@babel/plugin-transform-shorthand-properties": "^7.25.9",
+ "@babel/plugin-transform-spread": "^7.25.9",
+ "@babel/plugin-transform-sticky-regex": "^7.25.9",
+ "@babel/plugin-transform-template-literals": "^7.25.9",
+ "@babel/plugin-transform-typeof-symbol": "^7.25.9",
+ "@babel/plugin-transform-unicode-escapes": "^7.25.9",
+ "@babel/plugin-transform-unicode-property-regex": "^7.25.9",
+ "@babel/plugin-transform-unicode-regex": "^7.25.9",
+ "@babel/plugin-transform-unicode-sets-regex": "^7.25.9",
+ "@babel/preset-modules": "0.1.6-no-external-plugins",
+ "babel-plugin-polyfill-corejs2": "^0.4.10",
+ "babel-plugin-polyfill-corejs3": "^0.10.6",
+ "babel-plugin-polyfill-regenerator": "^0.6.1",
+ "core-js-compat": "^3.38.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/preset-modules": {
+ "version": "0.1.6-no-external-plugins",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz",
+ "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.0.0",
+ "@babel/types": "^7.4.4",
+ "esutils": "^2.0.2"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0"
+ }
+ },
+ "node_modules/@babel/preset-react": {
+ "version": "7.26.3",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/preset-react/-/preset-react-7.26.3.tgz",
+ "integrity": "sha512-Nl03d6T9ky516DGK2YMxrTqvnpUW63TnJMOMonj+Zae0JiPC5BC9xPMSL6L8fiSpA5vP88qfygavVQvnLp+6Cw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9",
+ "@babel/helper-validator-option": "^7.25.9",
+ "@babel/plugin-transform-react-display-name": "^7.25.9",
+ "@babel/plugin-transform-react-jsx": "^7.25.9",
+ "@babel/plugin-transform-react-jsx-development": "^7.25.9",
+ "@babel/plugin-transform-react-pure-annotations": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/preset-typescript": {
+ "version": "7.26.0",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/preset-typescript/-/preset-typescript-7.26.0.tgz",
+ "integrity": "sha512-NMk1IGZ5I/oHhoXEElcm+xUnL/szL6xflkFZmoEU9xj1qSJXpiS7rsspYo92B4DRCDvZn2erT5LdsCeXAKNCkg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9",
+ "@babel/helper-validator-option": "^7.25.9",
+ "@babel/plugin-syntax-jsx": "^7.25.9",
+ "@babel/plugin-transform-modules-commonjs": "^7.25.9",
+ "@babel/plugin-transform-typescript": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/runtime": {
+ "version": "7.26.10",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz",
+ "integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==",
+ "dependencies": {
+ "regenerator-runtime": "^0.14.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/runtime-corejs3": {
+ "version": "7.18.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/runtime-corejs3/-/runtime-corejs3-7.18.9.tgz",
+ "integrity": "sha512-qZEWeccZCrHA2Au4/X05QW5CMdm4VjUDCrGq5gf1ZDcM4hRqreKrtwAn7yci9zfgAS9apvnsFXiGBHBAxZdK9A==",
+ "dependencies": {
+ "core-js-pure": "^3.20.2",
+ "regenerator-runtime": "^0.13.4"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/runtime-corejs3/node_modules/regenerator-runtime": {
+ "version": "0.13.11",
+ "resolved": "https://mirrors.tencent.com/npm/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
+ "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
+ },
+ "node_modules/@babel/template": {
+ "version": "7.25.9",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/template/-/template-7.25.9.tgz",
+ "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.25.9",
+ "@babel/parser": "^7.25.9",
+ "@babel/types": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.26.5",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/traverse/-/traverse-7.26.5.tgz",
+ "integrity": "sha512-rkOSPOw+AXbgtwUga3U4u8RpoK9FEFWBNAlTpcnkLFjL5CT+oyHNuUUC/xx6XefEJ16r38r8Bc/lfp6rYuHeJQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.26.2",
+ "@babel/generator": "^7.26.5",
+ "@babel/parser": "^7.26.5",
+ "@babel/template": "^7.25.9",
+ "@babel/types": "^7.26.5",
+ "debug": "^4.3.1",
+ "globals": "^11.1.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.26.5",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/types/-/types-7.26.5.tgz",
+ "integrity": "sha512-L6mZmwFDK6Cjh1nRCLXpa6no13ZIioJDz7mdkzHv399pThrTa/k0nUlNaenOeh2kWu/iaOQYElEpKPUswUa9Vg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.25.9",
+ "@babel/helper-validator-identifier": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.14.54",
+ "resolved": "https://mirrors.tencent.com/npm/@esbuild/linux-loong64/-/linux-loong64-0.14.54.tgz",
+ "integrity": "sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.4.1",
+ "resolved": "https://mirrors.tencent.com/npm/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz",
+ "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==",
+ "dev": true,
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.1",
+ "resolved": "https://mirrors.tencent.com/npm/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
+ "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
+ "dev": true,
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "2.1.4",
+ "resolved": "https://mirrors.tencent.com/npm/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
+ "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
+ "dev": true,
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^9.6.0",
+ "globals": "^13.19.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/globals": {
+ "version": "13.24.0",
+ "resolved": "https://mirrors.tencent.com/npm/globals/-/globals-13.24.0.tgz",
+ "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
+ "dev": true,
+ "dependencies": {
+ "type-fest": "^0.20.2"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "8.57.1",
+ "resolved": "https://mirrors.tencent.com/npm/@eslint/js/-/js-8.57.1.tgz",
+ "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==",
+ "dev": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@honkhonk/vite-plugin-svgr": {
+ "version": "1.1.0",
+ "resolved": "https://mirrors.tencent.com/npm/@honkhonk/vite-plugin-svgr/-/vite-plugin-svgr-1.1.0.tgz",
+ "integrity": "sha512-Z/KR54UolyaNRlbRWnrXb3C+2Xtl6ilHvD3ceoqTVV69OswtjSGZDefHHNK+SQUSBbYQo0lJO2jLgip0QBxL1A==",
+ "dev": true,
+ "dependencies": {
+ "@svgr/core": "^5.5.0"
+ },
+ "peerDependencies": {
+ "vite": "^2.5.0"
+ }
+ },
+ "node_modules/@humanwhocodes/config-array": {
+ "version": "0.13.0",
+ "resolved": "https://mirrors.tencent.com/npm/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
+ "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==",
+ "deprecated": "Use @eslint/config-array instead",
+ "dev": true,
+ "dependencies": {
+ "@humanwhocodes/object-schema": "^2.0.3",
+ "debug": "^4.3.1",
+ "minimatch": "^3.0.5"
+ },
+ "engines": {
+ "node": ">=10.10.0"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://mirrors.tencent.com/npm/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/object-schema": {
+ "version": "2.0.3",
+ "resolved": "https://mirrors.tencent.com/npm/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
+ "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
+ "dev": true
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.8",
+ "resolved": "https://mirrors.tencent.com/npm/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
+ "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/set-array": "^1.2.1",
+ "@jridgewell/sourcemap-codec": "^1.4.10",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://mirrors.tencent.com/npm/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/set-array": {
+ "version": "1.2.1",
+ "resolved": "https://mirrors.tencent.com/npm/@jridgewell/set-array/-/set-array-1.2.1.tgz",
+ "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.0",
+ "resolved": "https://mirrors.tencent.com/npm/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
+ "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
+ "dev": true
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.25",
+ "resolved": "https://mirrors.tencent.com/npm/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
+ "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@material/material-color-utilities": {
+ "version": "0.1.2",
+ "resolved": "https://mirrors.tencent.com/npm/@material/material-color-utilities/-/material-color-utilities-0.1.2.tgz",
+ "integrity": "sha512-uKkkx7Xo1AZJR0eMjH902rAb0vEjDbb3tgXt0cC3SHbpDGkPNhkuDKtxnw147axOM6uKuI7vywHRQlDrjDGhCA=="
+ },
+ "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": {
+ "version": "5.1.1-v1",
+ "resolved": "https://mirrors.tencent.com/npm/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz",
+ "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==",
+ "dev": true,
+ "dependencies": {
+ "eslint-scope": "5.1.1"
+ }
+ },
+ "node_modules/@nicolo-ribaudo/eslint-scope-5-internals/node_modules/eslint-scope": {
+ "version": "5.1.1",
+ "resolved": "https://mirrors.tencent.com/npm/eslint-scope/-/eslint-scope-5.1.1.tgz",
+ "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
+ "dev": true,
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^4.1.1"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/@nicolo-ribaudo/eslint-scope-5-internals/node_modules/estraverse": {
+ "version": "4.3.0",
+ "resolved": "https://mirrors.tencent.com/npm/estraverse/-/estraverse-4.3.0.tgz",
+ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+ "dev": true,
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://mirrors.tencent.com/npm/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://mirrors.tencent.com/npm/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://mirrors.tencent.com/npm/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@popperjs/core": {
+ "version": "2.11.8",
+ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
+ "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/popperjs"
+ }
+ },
+ "node_modules/@reduxjs/toolkit": {
+ "version": "1.9.7",
+ "resolved": "https://mirrors.tencent.com/npm/@reduxjs/toolkit/-/toolkit-1.9.7.tgz",
+ "integrity": "sha512-t7v8ZPxhhKgOKtU+uyJT13lu4vL7az5aFi4IdoDs/eS548edn2M8Ik9h8fxgvMjGoAUVFSt6ZC1P5cWmQ014QQ==",
+ "dependencies": {
+ "immer": "^9.0.21",
+ "redux": "^4.2.1",
+ "redux-thunk": "^2.4.2",
+ "reselect": "^4.1.8"
+ },
+ "peerDependencies": {
+ "react": "^16.9.0 || ^17.0.0 || ^18",
+ "react-redux": "^7.2.1 || ^8.0.2"
+ },
+ "peerDependenciesMeta": {
+ "react": {
+ "optional": true
+ },
+ "react-redux": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@remix-run/router": {
+ "version": "1.21.0",
+ "resolved": "https://mirrors.tencent.com/npm/@remix-run/router/-/router-1.21.0.tgz",
+ "integrity": "sha512-xfSkCAchbdG5PnbrKqFWwia4Bi61nH+wm8wLEqfHDyp7Y3dZzgqS2itV8i4gAq9pC2HsTpwyBC6Ds8VHZ96JlA==",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@rollup/pluginutils": {
+ "version": "4.2.1",
+ "resolved": "https://mirrors.tencent.com/npm/@rollup/pluginutils/-/pluginutils-4.2.1.tgz",
+ "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==",
+ "dev": true,
+ "dependencies": {
+ "estree-walker": "^2.0.1",
+ "picomatch": "^2.2.2"
+ },
+ "engines": {
+ "node": ">= 8.0.0"
+ }
+ },
+ "node_modules/@rtsao/scc": {
+ "version": "1.1.0",
+ "resolved": "https://mirrors.tencent.com/npm/@rtsao/scc/-/scc-1.1.0.tgz",
+ "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==",
+ "dev": true
+ },
+ "node_modules/@rushstack/eslint-patch": {
+ "version": "1.10.5",
+ "resolved": "https://mirrors.tencent.com/npm/@rushstack/eslint-patch/-/eslint-patch-1.10.5.tgz",
+ "integrity": "sha512-kkKUDVlII2DQiKy7UstOR1ErJP8kUKAQ4oa+SQtM0K+lPdmmjj0YnnxBgtTVYH7mUKtbsxeFC9y0AmK7Yb78/A==",
+ "dev": true
+ },
+ "node_modules/@svgr/babel-plugin-add-jsx-attribute": {
+ "version": "5.4.0",
+ "resolved": "https://mirrors.tencent.com/npm/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz",
+ "integrity": "sha512-ZFf2gs/8/6B8PnSofI0inYXr2SDNTDScPXhN7k5EqD4aZ3gi6u+rbmZHVB8IM3wDyx8ntKACZbtXSm7oZGRqVg==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ }
+ },
+ "node_modules/@svgr/babel-plugin-remove-jsx-attribute": {
+ "version": "5.4.0",
+ "resolved": "https://mirrors.tencent.com/npm/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-5.4.0.tgz",
+ "integrity": "sha512-yaS4o2PgUtwLFGTKbsiAy6D0o3ugcUhWK0Z45umJ66EPWunAz9fuFw2gJuje6wqQvQWOTJvIahUwndOXb7QCPg==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ }
+ },
+ "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": {
+ "version": "5.0.1",
+ "resolved": "https://mirrors.tencent.com/npm/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-5.0.1.tgz",
+ "integrity": "sha512-LA72+88A11ND/yFIMzyuLRSMJ+tRKeYKeQ+mR3DcAZ5I4h5CPWN9AHyUzJbWSYp/u2u0xhmgOe0+E41+GjEueA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ }
+ },
+ "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": {
+ "version": "5.0.1",
+ "resolved": "https://mirrors.tencent.com/npm/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-5.0.1.tgz",
+ "integrity": "sha512-PoiE6ZD2Eiy5mK+fjHqwGOS+IXX0wq/YDtNyIgOrc6ejFnxN4b13pRpiIPbtPwHEc+NT2KCjteAcq33/F1Y9KQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ }
+ },
+ "node_modules/@svgr/babel-plugin-svg-dynamic-title": {
+ "version": "5.4.0",
+ "resolved": "https://mirrors.tencent.com/npm/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-5.4.0.tgz",
+ "integrity": "sha512-zSOZH8PdZOpuG1ZVx/cLVePB2ibo3WPpqo7gFIjLV9a0QsuQAzJiwwqmuEdTaW2pegyBE17Uu15mOgOcgabQZg==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ }
+ },
+ "node_modules/@svgr/babel-plugin-svg-em-dimensions": {
+ "version": "5.4.0",
+ "resolved": "https://mirrors.tencent.com/npm/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-5.4.0.tgz",
+ "integrity": "sha512-cPzDbDA5oT/sPXDCUYoVXEmm3VIoAWAPT6mSPTJNbQaBNUuEKVKyGH93oDY4e42PYHRW67N5alJx/eEol20abw==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ }
+ },
+ "node_modules/@svgr/babel-plugin-transform-react-native-svg": {
+ "version": "5.4.0",
+ "resolved": "https://mirrors.tencent.com/npm/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-5.4.0.tgz",
+ "integrity": "sha512-3eYP/SaopZ41GHwXma7Rmxcv9uRslRDTY1estspeB1w1ueZWd/tPlMfEOoccYpEMZU3jD4OU7YitnXcF5hLW2Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ }
+ },
+ "node_modules/@svgr/babel-plugin-transform-svg-component": {
+ "version": "5.5.0",
+ "resolved": "https://mirrors.tencent.com/npm/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-5.5.0.tgz",
+ "integrity": "sha512-q4jSH1UUvbrsOtlo/tKcgSeiCHRSBdXoIoqX1pgcKK/aU3JD27wmMKwGtpB8qRYUYoyXvfGxUVKchLuR5pB3rQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ }
+ },
+ "node_modules/@svgr/babel-preset": {
+ "version": "5.5.0",
+ "resolved": "https://mirrors.tencent.com/npm/@svgr/babel-preset/-/babel-preset-5.5.0.tgz",
+ "integrity": "sha512-4FiXBjvQ+z2j7yASeGPEi8VD/5rrGQk4Xrq3EdJmoZgz/tpqChpo5hgXDvmEauwtvOc52q8ghhZK4Oy7qph4ig==",
+ "dev": true,
+ "dependencies": {
+ "@svgr/babel-plugin-add-jsx-attribute": "^5.4.0",
+ "@svgr/babel-plugin-remove-jsx-attribute": "^5.4.0",
+ "@svgr/babel-plugin-remove-jsx-empty-expression": "^5.0.1",
+ "@svgr/babel-plugin-replace-jsx-attribute-value": "^5.0.1",
+ "@svgr/babel-plugin-svg-dynamic-title": "^5.4.0",
+ "@svgr/babel-plugin-svg-em-dimensions": "^5.4.0",
+ "@svgr/babel-plugin-transform-react-native-svg": "^5.4.0",
+ "@svgr/babel-plugin-transform-svg-component": "^5.5.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ }
+ },
+ "node_modules/@svgr/core": {
+ "version": "5.5.0",
+ "resolved": "https://mirrors.tencent.com/npm/@svgr/core/-/core-5.5.0.tgz",
+ "integrity": "sha512-q52VOcsJPvV3jO1wkPtzTuKlvX7Y3xIcWRpCMtBF3MrteZJtBfQw/+u0B1BHy5ColpQc1/YVTrPEtSYIMNZlrQ==",
+ "dev": true,
+ "dependencies": {
+ "@svgr/plugin-jsx": "^5.5.0",
+ "camelcase": "^6.2.0",
+ "cosmiconfig": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ }
+ },
+ "node_modules/@svgr/hast-util-to-babel-ast": {
+ "version": "5.5.0",
+ "resolved": "https://mirrors.tencent.com/npm/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-5.5.0.tgz",
+ "integrity": "sha512-cAaR/CAiZRB8GP32N+1jocovUtvlj0+e65TB50/6Lcime+EA49m/8l+P2ko+XPJ4dw3xaPS3jOL4F2X4KWxoeQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.12.6"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ }
+ },
+ "node_modules/@svgr/plugin-jsx": {
+ "version": "5.5.0",
+ "resolved": "https://mirrors.tencent.com/npm/@svgr/plugin-jsx/-/plugin-jsx-5.5.0.tgz",
+ "integrity": "sha512-V/wVh33j12hGh05IDg8GpIUXbjAPnTdPTKuP4VNLggnwaHMPNQNae2pRnyTAILWCQdz5GyMqtO488g7CKM8CBA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/core": "^7.12.3",
+ "@svgr/babel-preset": "^5.5.0",
+ "@svgr/hast-util-to-babel-ast": "^5.5.0",
+ "svg-parser": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ }
+ },
+ "node_modules/@types/echarts": {
+ "version": "4.9.22",
+ "resolved": "https://mirrors.tencent.com/npm/@types/echarts/-/echarts-4.9.22.tgz",
+ "integrity": "sha512-7Fo6XdWpoi8jxkwP7BARUOM7riq8bMhmsCtSG8gzUcJmFhLo387tihoBYS/y5j7jl3PENT5RxeWZdN9RiwO7HQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/zrender": "*"
+ }
+ },
+ "node_modules/@types/history": {
+ "version": "4.7.11",
+ "resolved": "https://mirrors.tencent.com/npm/@types/history/-/history-4.7.11.tgz",
+ "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==",
+ "dev": true
+ },
+ "node_modules/@types/hoist-non-react-statics": {
+ "version": "3.3.6",
+ "resolved": "https://mirrors.tencent.com/npm/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.6.tgz",
+ "integrity": "sha512-lPByRJUer/iN/xa4qpyL0qmL11DqNW81iU/IG1S3uvRUq4oKagz8VCxZjiWkumgt66YT3vOdDgZ0o32sGKtCEw==",
+ "dependencies": {
+ "@types/react": "*",
+ "hoist-non-react-statics": "^3.3.0"
+ }
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://mirrors.tencent.com/npm/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true
+ },
+ "node_modules/@types/json5": {
+ "version": "0.0.29",
+ "resolved": "https://mirrors.tencent.com/npm/@types/json5/-/json5-0.0.29.tgz",
+ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
+ "dev": true
+ },
+ "node_modules/@types/lodash": {
+ "version": "4.17.14",
+ "resolved": "https://mirrors.tencent.com/npm/@types/lodash/-/lodash-4.17.14.tgz",
+ "integrity": "sha512-jsxagdikDiDBeIRaPYtArcT8my4tN1og7MtMRquFT3XNA6axxyHDRUemqDz/taRDdOUn0GnGHRCuff4q48sW9A==",
+ "dev": true
+ },
+ "node_modules/@types/mockjs": {
+ "version": "1.0.10",
+ "resolved": "https://mirrors.tencent.com/npm/@types/mockjs/-/mockjs-1.0.10.tgz",
+ "integrity": "sha512-SXgrhajHG7boLv6oU93CcmdDm0HYRiceuz6b+7z+/2lCJPTWDv0V5YiwFHT2ejE4bQqgSXQiVPQYPWv7LGsK1g==",
+ "dev": true
+ },
+ "node_modules/@types/node": {
+ "version": "20.17.12",
+ "resolved": "https://mirrors.tencent.com/npm/@types/node/-/node-20.17.12.tgz",
+ "integrity": "sha512-vo/wmBgMIiEA23A/knMfn/cf37VnuF52nZh5ZoW0GWt4e4sxNquibrMRJ7UQsA06+MBx9r/H1jsI9grYjQCQlw==",
+ "dev": true,
+ "dependencies": {
+ "undici-types": "~6.19.2"
+ }
+ },
+ "node_modules/@types/parse-json": {
+ "version": "4.0.2",
+ "resolved": "https://mirrors.tencent.com/npm/@types/parse-json/-/parse-json-4.0.2.tgz",
+ "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==",
+ "dev": true
+ },
+ "node_modules/@types/prop-types": {
+ "version": "15.7.14",
+ "resolved": "https://mirrors.tencent.com/npm/@types/prop-types/-/prop-types-15.7.14.tgz",
+ "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ=="
+ },
+ "node_modules/@types/react": {
+ "version": "18.3.18",
+ "resolved": "https://mirrors.tencent.com/npm/@types/react/-/react-18.3.18.tgz",
+ "integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==",
+ "dependencies": {
+ "@types/prop-types": "*",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "18.3.5",
+ "resolved": "https://mirrors.tencent.com/npm/@types/react-dom/-/react-dom-18.3.5.tgz",
+ "integrity": "sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==",
+ "dev": true,
+ "peerDependencies": {
+ "@types/react": "^18.0.0"
+ }
+ },
+ "node_modules/@types/react-redux": {
+ "version": "7.1.34",
+ "resolved": "https://mirrors.tencent.com/npm/@types/react-redux/-/react-redux-7.1.34.tgz",
+ "integrity": "sha512-GdFaVjEbYv4Fthm2ZLvj1VSCedV7TqE5y1kNwnjSdBOTXuRSgowux6J8TAct15T3CKBr63UMk+2CO7ilRhyrAQ==",
+ "dependencies": {
+ "@types/hoist-non-react-statics": "^3.3.0",
+ "@types/react": "*",
+ "hoist-non-react-statics": "^3.3.0",
+ "redux": "^4.0.0"
+ }
+ },
+ "node_modules/@types/react-router": {
+ "version": "5.1.20",
+ "resolved": "https://mirrors.tencent.com/npm/@types/react-router/-/react-router-5.1.20.tgz",
+ "integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==",
+ "dev": true,
+ "dependencies": {
+ "@types/history": "^4.7.11",
+ "@types/react": "*"
+ }
+ },
+ "node_modules/@types/react-router-dom": {
+ "version": "5.3.3",
+ "resolved": "https://mirrors.tencent.com/npm/@types/react-router-dom/-/react-router-dom-5.3.3.tgz",
+ "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==",
+ "dev": true,
+ "dependencies": {
+ "@types/history": "^4.7.11",
+ "@types/react": "*",
+ "@types/react-router": "*"
+ }
+ },
+ "node_modules/@types/semver": {
+ "version": "7.5.8",
+ "resolved": "https://mirrors.tencent.com/npm/@types/semver/-/semver-7.5.8.tgz",
+ "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==",
+ "dev": true
+ },
+ "node_modules/@types/sortablejs": {
+ "version": "1.15.8",
+ "resolved": "https://mirrors.tencent.com/npm/@types/sortablejs/-/sortablejs-1.15.8.tgz",
+ "integrity": "sha512-b79830lW+RZfwaztgs1aVPgbasJ8e7AXtZYHTELNXZPsERt4ymJdjV4OccDbHQAvHrCcFpbF78jkm0R6h/pZVg=="
+ },
+ "node_modules/@types/validator": {
+ "version": "13.12.2",
+ "resolved": "https://mirrors.tencent.com/npm/@types/validator/-/validator-13.12.2.tgz",
+ "integrity": "sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA=="
+ },
+ "node_modules/@types/zrender": {
+ "version": "4.0.6",
+ "resolved": "https://mirrors.tencent.com/npm/@types/zrender/-/zrender-4.0.6.tgz",
+ "integrity": "sha512-1jZ9bJn2BsfmYFPBHtl5o3uV+ILejAtGrDcYSpT4qaVKEI/0YY+arw3XHU04Ebd8Nca3SQ7uNcLaqiL+tTFVMg==",
+ "dev": true
+ },
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "5.62.0",
+ "resolved": "https://mirrors.tencent.com/npm/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz",
+ "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==",
+ "dev": true,
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.4.0",
+ "@typescript-eslint/scope-manager": "5.62.0",
+ "@typescript-eslint/type-utils": "5.62.0",
+ "@typescript-eslint/utils": "5.62.0",
+ "debug": "^4.3.4",
+ "graphemer": "^1.4.0",
+ "ignore": "^5.2.0",
+ "natural-compare-lite": "^1.4.0",
+ "semver": "^7.3.7",
+ "tsutils": "^3.21.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^5.0.0",
+ "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": {
+ "version": "7.6.3",
+ "resolved": "https://mirrors.tencent.com/npm/semver/-/semver-7.6.3.tgz",
+ "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@typescript-eslint/experimental-utils": {
+ "version": "5.62.0",
+ "resolved": "https://mirrors.tencent.com/npm/@typescript-eslint/experimental-utils/-/experimental-utils-5.62.0.tgz",
+ "integrity": "sha512-RTXpeB3eMkpoclG3ZHft6vG/Z30azNHuqY6wKPBHlVMZFuEvrtlEDe8gMqDb+SO+9hjC/pLekeSCryf9vMZlCw==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/utils": "5.62.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/parser": {
+ "version": "5.62.0",
+ "resolved": "https://mirrors.tencent.com/npm/@typescript-eslint/parser/-/parser-5.62.0.tgz",
+ "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "5.62.0",
+ "@typescript-eslint/types": "5.62.0",
+ "@typescript-eslint/typescript-estree": "5.62.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "5.62.0",
+ "resolved": "https://mirrors.tencent.com/npm/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz",
+ "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "5.62.0",
+ "@typescript-eslint/visitor-keys": "5.62.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "5.62.0",
+ "resolved": "https://mirrors.tencent.com/npm/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz",
+ "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/typescript-estree": "5.62.0",
+ "@typescript-eslint/utils": "5.62.0",
+ "debug": "^4.3.4",
+ "tsutils": "^3.21.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "*"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "5.62.0",
+ "resolved": "https://mirrors.tencent.com/npm/@typescript-eslint/types/-/types-5.62.0.tgz",
+ "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==",
+ "dev": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "5.62.0",
+ "resolved": "https://mirrors.tencent.com/npm/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz",
+ "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "5.62.0",
+ "@typescript-eslint/visitor-keys": "5.62.0",
+ "debug": "^4.3.4",
+ "globby": "^11.1.0",
+ "is-glob": "^4.0.3",
+ "semver": "^7.3.7",
+ "tsutils": "^3.21.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
+ "version": "7.6.3",
+ "resolved": "https://mirrors.tencent.com/npm/semver/-/semver-7.6.3.tgz",
+ "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "5.62.0",
+ "resolved": "https://mirrors.tencent.com/npm/@typescript-eslint/utils/-/utils-5.62.0.tgz",
+ "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==",
+ "dev": true,
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@types/json-schema": "^7.0.9",
+ "@types/semver": "^7.3.12",
+ "@typescript-eslint/scope-manager": "5.62.0",
+ "@typescript-eslint/types": "5.62.0",
+ "@typescript-eslint/typescript-estree": "5.62.0",
+ "eslint-scope": "^5.1.1",
+ "semver": "^7.3.7"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/utils/node_modules/eslint-scope": {
+ "version": "5.1.1",
+ "resolved": "https://mirrors.tencent.com/npm/eslint-scope/-/eslint-scope-5.1.1.tgz",
+ "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
+ "dev": true,
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^4.1.1"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/utils/node_modules/estraverse": {
+ "version": "4.3.0",
+ "resolved": "https://mirrors.tencent.com/npm/estraverse/-/estraverse-4.3.0.tgz",
+ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+ "dev": true,
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/@typescript-eslint/utils/node_modules/semver": {
+ "version": "7.6.3",
+ "resolved": "https://mirrors.tencent.com/npm/semver/-/semver-7.6.3.tgz",
+ "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "5.62.0",
+ "resolved": "https://mirrors.tencent.com/npm/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz",
+ "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "5.62.0",
+ "eslint-visitor-keys": "^3.3.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@ungap/structured-clone": {
+ "version": "1.2.1",
+ "resolved": "https://mirrors.tencent.com/npm/@ungap/structured-clone/-/structured-clone-1.2.1.tgz",
+ "integrity": "sha512-fEzPV3hSkSMltkw152tJKNARhOupqbH96MZWyRjNaYZOMIzbrTeQDG+MTc6Mr2pgzFQzFxAfmhGDNP5QK++2ZA==",
+ "dev": true
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "1.3.2",
+ "resolved": "https://mirrors.tencent.com/npm/@vitejs/plugin-react/-/plugin-react-1.3.2.tgz",
+ "integrity": "sha512-aurBNmMo0kz1O4qRoY+FM4epSA39y3ShWGuqfLRA/3z0oEJAdtoSfgA3aO98/PCCHAqMaduLxIxErWrVKIFzXA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/core": "^7.17.10",
+ "@babel/plugin-transform-react-jsx": "^7.17.3",
+ "@babel/plugin-transform-react-jsx-development": "^7.16.7",
+ "@babel/plugin-transform-react-jsx-self": "^7.16.7",
+ "@babel/plugin-transform-react-jsx-source": "^7.16.7",
+ "@rollup/pluginutils": "^4.2.1",
+ "react-refresh": "^0.13.0",
+ "resolve": "^1.22.0"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.14.0",
+ "resolved": "https://mirrors.tencent.com/npm/acorn/-/acorn-8.14.0.tgz",
+ "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
+ "dev": true,
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://mirrors.tencent.com/npm/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://mirrors.tencent.com/npm/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://mirrors.tencent.com/npm/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://mirrors.tencent.com/npm/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://mirrors.tencent.com/npm/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://mirrors.tencent.com/npm/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true
+ },
+ "node_modules/aria-query": {
+ "version": "5.3.2",
+ "resolved": "https://mirrors.tencent.com/npm/aria-query/-/aria-query-5.3.2.tgz",
+ "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/array-buffer-byte-length": {
+ "version": "1.0.2",
+ "resolved": "https://mirrors.tencent.com/npm/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz",
+ "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "is-array-buffer": "^3.0.5"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array-includes": {
+ "version": "3.1.8",
+ "resolved": "https://mirrors.tencent.com/npm/array-includes/-/array-includes-3.1.8.tgz",
+ "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.2",
+ "es-object-atoms": "^1.0.0",
+ "get-intrinsic": "^1.2.4",
+ "is-string": "^1.0.7"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array-union": {
+ "version": "2.1.0",
+ "resolved": "https://mirrors.tencent.com/npm/array-union/-/array-union-2.1.0.tgz",
+ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/array.prototype.findlast": {
+ "version": "1.2.5",
+ "resolved": "https://mirrors.tencent.com/npm/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz",
+ "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.2",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.0.0",
+ "es-shim-unscopables": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.findlastindex": {
+ "version": "1.2.5",
+ "resolved": "https://mirrors.tencent.com/npm/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz",
+ "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.2",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.0.0",
+ "es-shim-unscopables": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.flat": {
+ "version": "1.3.3",
+ "resolved": "https://mirrors.tencent.com/npm/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz",
+ "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.5",
+ "es-shim-unscopables": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.flatmap": {
+ "version": "1.3.3",
+ "resolved": "https://mirrors.tencent.com/npm/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz",
+ "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.5",
+ "es-shim-unscopables": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.tosorted": {
+ "version": "1.1.4",
+ "resolved": "https://mirrors.tencent.com/npm/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz",
+ "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.3",
+ "es-errors": "^1.3.0",
+ "es-shim-unscopables": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/arraybuffer.prototype.slice": {
+ "version": "1.0.4",
+ "resolved": "https://mirrors.tencent.com/npm/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz",
+ "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==",
+ "dev": true,
+ "dependencies": {
+ "array-buffer-byte-length": "^1.0.1",
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.5",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "is-array-buffer": "^3.0.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/ast-types-flow": {
+ "version": "0.0.8",
+ "resolved": "https://mirrors.tencent.com/npm/ast-types-flow/-/ast-types-flow-0.0.8.tgz",
+ "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==",
+ "dev": true
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://mirrors.tencent.com/npm/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
+ },
+ "node_modules/available-typed-arrays": {
+ "version": "1.0.7",
+ "resolved": "https://mirrors.tencent.com/npm/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
+ "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==",
+ "dev": true,
+ "dependencies": {
+ "possible-typed-array-names": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/axe-core": {
+ "version": "4.10.2",
+ "resolved": "https://mirrors.tencent.com/npm/axe-core/-/axe-core-4.10.2.tgz",
+ "integrity": "sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/axios": {
+ "version": "1.7.9",
+ "resolved": "https://mirrors.tencent.com/npm/axios/-/axios-1.7.9.tgz",
+ "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
+ "dependencies": {
+ "follow-redirects": "^1.15.6",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
+ "node_modules/axios-jsonp": {
+ "version": "1.0.4",
+ "resolved": "https://mirrors.tencent.com/npm/axios-jsonp/-/axios-jsonp-1.0.4.tgz",
+ "integrity": "sha512-KI5Fc4ery6DR+oneXG09hPZfGuNUW8Lblhe750h53Z0Eh5MRsrHn49YitDU4RsMk0HV+12zcvL2Q51QkOLGdIQ=="
+ },
+ "node_modules/axobject-query": {
+ "version": "4.1.0",
+ "resolved": "https://mirrors.tencent.com/npm/axobject-query/-/axobject-query-4.1.0.tgz",
+ "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/babel-plugin-macros": {
+ "version": "3.1.0",
+ "resolved": "https://mirrors.tencent.com/npm/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
+ "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/runtime": "^7.12.5",
+ "cosmiconfig": "^7.0.0",
+ "resolve": "^1.19.0"
+ },
+ "engines": {
+ "node": ">=10",
+ "npm": ">=6"
+ }
+ },
+ "node_modules/babel-plugin-polyfill-corejs2": {
+ "version": "0.4.12",
+ "resolved": "https://mirrors.tencent.com/npm/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.12.tgz",
+ "integrity": "sha512-CPWT6BwvhrTO2d8QVorhTCQw9Y43zOu7G9HigcfxvepOU6b8o3tcWad6oVgZIsZCTt42FFv97aA7ZJsbM4+8og==",
+ "dev": true,
+ "dependencies": {
+ "@babel/compat-data": "^7.22.6",
+ "@babel/helper-define-polyfill-provider": "^0.6.3",
+ "semver": "^6.3.1"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
+ }
+ },
+ "node_modules/babel-plugin-polyfill-corejs3": {
+ "version": "0.10.6",
+ "resolved": "https://mirrors.tencent.com/npm/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz",
+ "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-define-polyfill-provider": "^0.6.2",
+ "core-js-compat": "^3.38.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
+ }
+ },
+ "node_modules/babel-plugin-polyfill-regenerator": {
+ "version": "0.6.3",
+ "resolved": "https://mirrors.tencent.com/npm/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.3.tgz",
+ "integrity": "sha512-LiWSbl4CRSIa5x/JAU6jZiG9eit9w6mz+yVMFwDE83LAWvt0AfGBoZ7HS/mkhrKuh2ZlzfVZYKoLjXdqw6Yt7Q==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-define-polyfill-provider": "^0.6.3"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
+ }
+ },
+ "node_modules/babel-plugin-transform-react-remove-prop-types": {
+ "version": "0.4.24",
+ "resolved": "https://mirrors.tencent.com/npm/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz",
+ "integrity": "sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA==",
+ "dev": true
+ },
+ "node_modules/babel-preset-react-app": {
+ "version": "10.0.1",
+ "resolved": "https://mirrors.tencent.com/npm/babel-preset-react-app/-/babel-preset-react-app-10.0.1.tgz",
+ "integrity": "sha512-b0D9IZ1WhhCWkrTXyFuIIgqGzSkRIH5D5AmB0bXbzYAB1OBAwHcUeyWW2LorutLWF5btNo/N7r/cIdmvvKJlYg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/core": "^7.16.0",
+ "@babel/plugin-proposal-class-properties": "^7.16.0",
+ "@babel/plugin-proposal-decorators": "^7.16.4",
+ "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.0",
+ "@babel/plugin-proposal-numeric-separator": "^7.16.0",
+ "@babel/plugin-proposal-optional-chaining": "^7.16.0",
+ "@babel/plugin-proposal-private-methods": "^7.16.0",
+ "@babel/plugin-transform-flow-strip-types": "^7.16.0",
+ "@babel/plugin-transform-react-display-name": "^7.16.0",
+ "@babel/plugin-transform-runtime": "^7.16.4",
+ "@babel/preset-env": "^7.16.4",
+ "@babel/preset-react": "^7.16.0",
+ "@babel/preset-typescript": "^7.16.0",
+ "@babel/runtime": "^7.16.3",
+ "babel-plugin-macros": "^3.1.0",
+ "babel-plugin-transform-react-remove-prop-types": "^0.4.24"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://mirrors.tencent.com/npm/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true
+ },
+ "node_modules/bezier-easing": {
+ "version": "2.1.0",
+ "resolved": "https://mirrors.tencent.com/npm/bezier-easing/-/bezier-easing-2.1.0.tgz",
+ "integrity": "sha1-wE3+i5JtbsrKGBPWn/F5t8ICXYY="
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.3.0",
+ "resolved": "https://mirrors.tencent.com/npm/binary-extensions/-/binary-extensions-2.3.0.tgz",
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://mirrors.tencent.com/npm/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://mirrors.tencent.com/npm/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.24.4",
+ "resolved": "https://mirrors.tencent.com/npm/browserslist/-/browserslist-4.24.4.tgz",
+ "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "caniuse-lite": "^1.0.30001688",
+ "electron-to-chromium": "^1.5.73",
+ "node-releases": "^2.0.19",
+ "update-browserslist-db": "^1.1.1"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/call-bind": {
+ "version": "1.0.8",
+ "resolved": "https://mirrors.tencent.com/npm/call-bind/-/call-bind-1.0.8.tgz",
+ "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==",
+ "dev": true,
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.0",
+ "es-define-property": "^1.0.0",
+ "get-intrinsic": "^1.2.4",
+ "set-function-length": "^1.2.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.1",
+ "resolved": "https://mirrors.tencent.com/npm/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz",
+ "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==",
+ "dev": true,
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/call-bound": {
+ "version": "1.0.3",
+ "resolved": "https://mirrors.tencent.com/npm/call-bound/-/call-bound-1.0.3.tgz",
+ "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "get-intrinsic": "^1.2.6"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://mirrors.tencent.com/npm/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/camelcase": {
+ "version": "6.3.0",
+ "resolved": "https://mirrors.tencent.com/npm/camelcase/-/camelcase-6.3.0.tgz",
+ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001692",
+ "resolved": "https://mirrors.tencent.com/npm/caniuse-lite/-/caniuse-lite-1.0.30001692.tgz",
+ "integrity": "sha512-A95VKan0kdtrsnMubMKxEKUKImOPSuCpYgxSQBo036P5YYgVIcOYJEgt/txJWqObiRQeISNCfef9nvlQ0vbV7A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ]
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://mirrors.tencent.com/npm/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://mirrors.tencent.com/npm/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "dev": true,
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/chokidar/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://mirrors.tencent.com/npm/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/chroma-js": {
+ "version": "2.1.2",
+ "resolved": "https://mirrors.tencent.com/npm/chroma-js/-/chroma-js-2.1.2.tgz",
+ "integrity": "sha512-ri/ouYDWuxfus3UcaMxC1Tfp3IE9K5iQzxc2hSxbBRVNQFut1UuGAsZmiAf2mOUubzGJwgMSv9lHg+XqLaz1QQ==",
+ "dependencies": {
+ "cross-env": "^6.0.3"
+ }
+ },
+ "node_modules/classnames": {
+ "version": "2.5.1",
+ "resolved": "https://mirrors.tencent.com/npm/classnames/-/classnames-2.5.1.tgz",
+ "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://mirrors.tencent.com/npm/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://mirrors.tencent.com/npm/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://mirrors.tencent.com/npm/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/commander": {
+ "version": "13.0.0",
+ "resolved": "https://mirrors.tencent.com/npm/commander/-/commander-13.0.0.tgz",
+ "integrity": "sha512-oPYleIY8wmTVzkvQq10AEok6YcTC4sRUBl8F9gVuwchGVUCTbl/vhLTaQqutuuySYOsu8YTgV+OxKc/8Yvx+mQ==",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://mirrors.tencent.com/npm/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true
+ },
+ "node_modules/confusing-browser-globals": {
+ "version": "1.0.11",
+ "resolved": "https://mirrors.tencent.com/npm/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz",
+ "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==",
+ "dev": true
+ },
+ "node_modules/connect": {
+ "version": "3.7.0",
+ "resolved": "https://mirrors.tencent.com/npm/connect/-/connect-3.7.0.tgz",
+ "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==",
+ "dev": true,
+ "dependencies": {
+ "debug": "2.6.9",
+ "finalhandler": "1.1.2",
+ "parseurl": "~1.3.3",
+ "utils-merge": "1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.10.0"
+ }
+ },
+ "node_modules/connect/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://mirrors.tencent.com/npm/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/connect/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://mirrors.tencent.com/npm/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "dev": true
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://mirrors.tencent.com/npm/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true
+ },
+ "node_modules/copy-anything": {
+ "version": "2.0.6",
+ "resolved": "https://mirrors.tencent.com/npm/copy-anything/-/copy-anything-2.0.6.tgz",
+ "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==",
+ "dev": true,
+ "dependencies": {
+ "is-what": "^3.14.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mesqueeb"
+ }
+ },
+ "node_modules/core-js-compat": {
+ "version": "3.40.0",
+ "resolved": "https://mirrors.tencent.com/npm/core-js-compat/-/core-js-compat-3.40.0.tgz",
+ "integrity": "sha512-0XEDpr5y5mijvw8Lbc6E5AkjrHfp7eEoPlu36SWeAbcL8fn1G1ANe8DBlo2XoNN89oVpxWwOjYIPVzR4ZvsKCQ==",
+ "dev": true,
+ "dependencies": {
+ "browserslist": "^4.24.3"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/core-js"
+ }
+ },
+ "node_modules/core-js-pure": {
+ "version": "3.40.0",
+ "resolved": "https://mirrors.tencent.com/npm/core-js-pure/-/core-js-pure-3.40.0.tgz",
+ "integrity": "sha512-AtDzVIgRrmRKQai62yuSIN5vNiQjcJakJb4fbhVw3ehxx7Lohphvw9SGNWKhLFqSxC4ilD0g/L1huAYFQU3Q6A==",
+ "hasInstallScript": true,
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/core-js"
+ }
+ },
+ "node_modules/cosmiconfig": {
+ "version": "7.1.0",
+ "resolved": "https://mirrors.tencent.com/npm/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
+ "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==",
+ "dev": true,
+ "dependencies": {
+ "@types/parse-json": "^4.0.0",
+ "import-fresh": "^3.2.1",
+ "parse-json": "^5.0.0",
+ "path-type": "^4.0.0",
+ "yaml": "^1.10.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/cross-env": {
+ "version": "6.0.3",
+ "resolved": "https://mirrors.tencent.com/npm/cross-env/-/cross-env-6.0.3.tgz",
+ "integrity": "sha512-+KqxF6LCvfhWvADcDPqo64yVIB31gv/jQulX2NGzKS/g3GEVz6/pt4wjHFtFWsHMddebWD/sDthJemzM4MaAag==",
+ "dependencies": {
+ "cross-spawn": "^7.0.0"
+ },
+ "bin": {
+ "cross-env": "src/bin/cross-env.js",
+ "cross-env-shell": "src/bin/cross-env-shell.js"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://mirrors.tencent.com/npm/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.1.3",
+ "resolved": "https://mirrors.tencent.com/npm/csstype/-/csstype-3.1.3.tgz",
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
+ },
+ "node_modules/damerau-levenshtein": {
+ "version": "1.0.8",
+ "resolved": "https://mirrors.tencent.com/npm/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
+ "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==",
+ "dev": true
+ },
+ "node_modules/data-view-buffer": {
+ "version": "1.0.2",
+ "resolved": "https://mirrors.tencent.com/npm/data-view-buffer/-/data-view-buffer-1.0.2.tgz",
+ "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "es-errors": "^1.3.0",
+ "is-data-view": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/data-view-byte-length": {
+ "version": "1.0.2",
+ "resolved": "https://mirrors.tencent.com/npm/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz",
+ "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "es-errors": "^1.3.0",
+ "is-data-view": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/inspect-js"
+ }
+ },
+ "node_modules/data-view-byte-offset": {
+ "version": "1.0.1",
+ "resolved": "https://mirrors.tencent.com/npm/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz",
+ "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "is-data-view": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/dayjs": {
+ "version": "1.11.13",
+ "resolved": "https://mirrors.tencent.com/npm/dayjs/-/dayjs-1.11.13.tgz",
+ "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg=="
+ },
+ "node_modules/debug": {
+ "version": "4.4.0",
+ "resolved": "https://mirrors.tencent.com/npm/debug/-/debug-4.4.0.tgz",
+ "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
+ "dev": true,
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://mirrors.tencent.com/npm/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true
+ },
+ "node_modules/define-data-property": {
+ "version": "1.1.4",
+ "resolved": "https://mirrors.tencent.com/npm/define-data-property/-/define-data-property-1.1.4.tgz",
+ "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
+ "dev": true,
+ "dependencies": {
+ "es-define-property": "^1.0.0",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/define-properties": {
+ "version": "1.2.1",
+ "resolved": "https://mirrors.tencent.com/npm/define-properties/-/define-properties-1.2.1.tgz",
+ "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
+ "dev": true,
+ "dependencies": {
+ "define-data-property": "^1.0.1",
+ "has-property-descriptors": "^1.0.0",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://mirrors.tencent.com/npm/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/dir-glob": {
+ "version": "3.0.1",
+ "resolved": "https://mirrors.tencent.com/npm/dir-glob/-/dir-glob-3.0.1.tgz",
+ "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
+ "dev": true,
+ "dependencies": {
+ "path-type": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/doctrine": {
+ "version": "3.0.0",
+ "resolved": "https://mirrors.tencent.com/npm/doctrine/-/doctrine-3.0.0.tgz",
+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+ "dev": true,
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/dom-helpers": {
+ "version": "5.2.1",
+ "resolved": "https://mirrors.tencent.com/npm/dom-helpers/-/dom-helpers-5.2.1.tgz",
+ "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
+ "dependencies": {
+ "@babel/runtime": "^7.8.7",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://mirrors.tencent.com/npm/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "dev": true,
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/echarts": {
+ "version": "5.6.0",
+ "resolved": "https://mirrors.tencent.com/npm/echarts/-/echarts-5.6.0.tgz",
+ "integrity": "sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==",
+ "dependencies": {
+ "tslib": "2.3.0",
+ "zrender": "5.6.1"
+ }
+ },
+ "node_modules/echarts-for-react": {
+ "version": "3.0.2",
+ "resolved": "https://mirrors.tencent.com/npm/echarts-for-react/-/echarts-for-react-3.0.2.tgz",
+ "integrity": "sha512-DRwIiTzx8JfwPOVgGttDytBqdp5VzCSyMRIxubgU/g2n9y3VLUmF2FK7Icmg/sNVkv4+rktmrLN9w22U2yy3fA==",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "size-sensor": "^1.0.1"
+ },
+ "peerDependencies": {
+ "echarts": "^3.0.0 || ^4.0.0 || ^5.0.0",
+ "react": "^15.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://mirrors.tencent.com/npm/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "dev": true
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.80",
+ "resolved": "https://mirrors.tencent.com/npm/electron-to-chromium/-/electron-to-chromium-1.5.80.tgz",
+ "integrity": "sha512-LTrKpW0AqIuHwmlVNV+cjFYTnXtM9K37OGhpe0ZI10ScPSxqVSryZHIY3WnCS5NSYbBODRTZyhRMS2h5FAEqAw==",
+ "dev": true
+ },
+ "node_modules/emoji-regex": {
+ "version": "9.2.2",
+ "resolved": "https://mirrors.tencent.com/npm/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+ "dev": true
+ },
+ "node_modules/encodeurl": {
+ "version": "1.0.2",
+ "resolved": "https://mirrors.tencent.com/npm/encodeurl/-/encodeurl-1.0.2.tgz",
+ "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/errno": {
+ "version": "0.1.8",
+ "resolved": "https://mirrors.tencent.com/npm/errno/-/errno-0.1.8.tgz",
+ "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==",
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "prr": "~1.0.1"
+ },
+ "bin": {
+ "errno": "cli.js"
+ }
+ },
+ "node_modules/error-ex": {
+ "version": "1.3.2",
+ "resolved": "https://mirrors.tencent.com/npm/error-ex/-/error-ex-1.3.2.tgz",
+ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+ "dev": true,
+ "dependencies": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "node_modules/es-abstract": {
+ "version": "1.23.9",
+ "resolved": "https://mirrors.tencent.com/npm/es-abstract/-/es-abstract-1.23.9.tgz",
+ "integrity": "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==",
+ "dev": true,
+ "dependencies": {
+ "array-buffer-byte-length": "^1.0.2",
+ "arraybuffer.prototype.slice": "^1.0.4",
+ "available-typed-arrays": "^1.0.7",
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "data-view-buffer": "^1.0.2",
+ "data-view-byte-length": "^1.0.2",
+ "data-view-byte-offset": "^1.0.1",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.0.0",
+ "es-set-tostringtag": "^2.1.0",
+ "es-to-primitive": "^1.3.0",
+ "function.prototype.name": "^1.1.8",
+ "get-intrinsic": "^1.2.7",
+ "get-proto": "^1.0.0",
+ "get-symbol-description": "^1.1.0",
+ "globalthis": "^1.0.4",
+ "gopd": "^1.2.0",
+ "has-property-descriptors": "^1.0.2",
+ "has-proto": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "internal-slot": "^1.1.0",
+ "is-array-buffer": "^3.0.5",
+ "is-callable": "^1.2.7",
+ "is-data-view": "^1.0.2",
+ "is-regex": "^1.2.1",
+ "is-shared-array-buffer": "^1.0.4",
+ "is-string": "^1.1.1",
+ "is-typed-array": "^1.1.15",
+ "is-weakref": "^1.1.0",
+ "math-intrinsics": "^1.1.0",
+ "object-inspect": "^1.13.3",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.7",
+ "own-keys": "^1.0.1",
+ "regexp.prototype.flags": "^1.5.3",
+ "safe-array-concat": "^1.1.3",
+ "safe-push-apply": "^1.0.0",
+ "safe-regex-test": "^1.1.0",
+ "set-proto": "^1.0.0",
+ "string.prototype.trim": "^1.2.10",
+ "string.prototype.trimend": "^1.0.9",
+ "string.prototype.trimstart": "^1.0.8",
+ "typed-array-buffer": "^1.0.3",
+ "typed-array-byte-length": "^1.0.3",
+ "typed-array-byte-offset": "^1.0.4",
+ "typed-array-length": "^1.0.7",
+ "unbox-primitive": "^1.1.0",
+ "which-typed-array": "^1.1.18"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://mirrors.tencent.com/npm/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://mirrors.tencent.com/npm/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-iterator-helpers": {
+ "version": "1.2.1",
+ "resolved": "https://mirrors.tencent.com/npm/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz",
+ "integrity": "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.6",
+ "es-errors": "^1.3.0",
+ "es-set-tostringtag": "^2.0.3",
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.2.6",
+ "globalthis": "^1.0.4",
+ "gopd": "^1.2.0",
+ "has-property-descriptors": "^1.0.2",
+ "has-proto": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "internal-slot": "^1.1.0",
+ "iterator.prototype": "^1.1.4",
+ "safe-array-concat": "^1.1.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.0.0",
+ "resolved": "https://mirrors.tencent.com/npm/es-object-atoms/-/es-object-atoms-1.0.0.tgz",
+ "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==",
+ "dev": true,
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://mirrors.tencent.com/npm/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "dev": true,
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-shim-unscopables": {
+ "version": "1.0.2",
+ "resolved": "https://mirrors.tencent.com/npm/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz",
+ "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==",
+ "dev": true,
+ "dependencies": {
+ "hasown": "^2.0.0"
+ }
+ },
+ "node_modules/es-to-primitive": {
+ "version": "1.3.0",
+ "resolved": "https://mirrors.tencent.com/npm/es-to-primitive/-/es-to-primitive-1.3.0.tgz",
+ "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==",
+ "dev": true,
+ "dependencies": {
+ "is-callable": "^1.2.7",
+ "is-date-object": "^1.0.5",
+ "is-symbol": "^1.0.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.14.54",
+ "resolved": "https://mirrors.tencent.com/npm/esbuild/-/esbuild-0.14.54.tgz",
+ "integrity": "sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/linux-loong64": "0.14.54",
+ "esbuild-android-64": "0.14.54",
+ "esbuild-android-arm64": "0.14.54",
+ "esbuild-darwin-64": "0.14.54",
+ "esbuild-darwin-arm64": "0.14.54",
+ "esbuild-freebsd-64": "0.14.54",
+ "esbuild-freebsd-arm64": "0.14.54",
+ "esbuild-linux-32": "0.14.54",
+ "esbuild-linux-64": "0.14.54",
+ "esbuild-linux-arm": "0.14.54",
+ "esbuild-linux-arm64": "0.14.54",
+ "esbuild-linux-mips64le": "0.14.54",
+ "esbuild-linux-ppc64le": "0.14.54",
+ "esbuild-linux-riscv64": "0.14.54",
+ "esbuild-linux-s390x": "0.14.54",
+ "esbuild-netbsd-64": "0.14.54",
+ "esbuild-openbsd-64": "0.14.54",
+ "esbuild-sunos-64": "0.14.54",
+ "esbuild-windows-32": "0.14.54",
+ "esbuild-windows-64": "0.14.54",
+ "esbuild-windows-arm64": "0.14.54"
+ }
+ },
+ "node_modules/esbuild-android-64": {
+ "version": "0.14.54",
+ "resolved": "https://mirrors.tencent.com/npm/esbuild-android-64/-/esbuild-android-64-0.14.54.tgz",
+ "integrity": "sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-android-arm64": {
+ "version": "0.14.54",
+ "resolved": "https://mirrors.tencent.com/npm/esbuild-android-arm64/-/esbuild-android-arm64-0.14.54.tgz",
+ "integrity": "sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-darwin-64": {
+ "version": "0.14.54",
+ "resolved": "https://mirrors.tencent.com/npm/esbuild-darwin-64/-/esbuild-darwin-64-0.14.54.tgz",
+ "integrity": "sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-darwin-arm64": {
+ "version": "0.14.54",
+ "resolved": "https://mirrors.tencent.com/npm/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.54.tgz",
+ "integrity": "sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-freebsd-64": {
+ "version": "0.14.54",
+ "resolved": "https://mirrors.tencent.com/npm/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.54.tgz",
+ "integrity": "sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-freebsd-arm64": {
+ "version": "0.14.54",
+ "resolved": "https://mirrors.tencent.com/npm/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.54.tgz",
+ "integrity": "sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-32": {
+ "version": "0.14.54",
+ "resolved": "https://mirrors.tencent.com/npm/esbuild-linux-32/-/esbuild-linux-32-0.14.54.tgz",
+ "integrity": "sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-64": {
+ "version": "0.14.54",
+ "resolved": "https://mirrors.tencent.com/npm/esbuild-linux-64/-/esbuild-linux-64-0.14.54.tgz",
+ "integrity": "sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-arm": {
+ "version": "0.14.54",
+ "resolved": "https://mirrors.tencent.com/npm/esbuild-linux-arm/-/esbuild-linux-arm-0.14.54.tgz",
+ "integrity": "sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-arm64": {
+ "version": "0.14.54",
+ "resolved": "https://mirrors.tencent.com/npm/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.54.tgz",
+ "integrity": "sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-mips64le": {
+ "version": "0.14.54",
+ "resolved": "https://mirrors.tencent.com/npm/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.54.tgz",
+ "integrity": "sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-ppc64le": {
+ "version": "0.14.54",
+ "resolved": "https://mirrors.tencent.com/npm/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.54.tgz",
+ "integrity": "sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-riscv64": {
+ "version": "0.14.54",
+ "resolved": "https://mirrors.tencent.com/npm/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.54.tgz",
+ "integrity": "sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-s390x": {
+ "version": "0.14.54",
+ "resolved": "https://mirrors.tencent.com/npm/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.54.tgz",
+ "integrity": "sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-netbsd-64": {
+ "version": "0.14.54",
+ "resolved": "https://mirrors.tencent.com/npm/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.54.tgz",
+ "integrity": "sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-openbsd-64": {
+ "version": "0.14.54",
+ "resolved": "https://mirrors.tencent.com/npm/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.54.tgz",
+ "integrity": "sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-sunos-64": {
+ "version": "0.14.54",
+ "resolved": "https://mirrors.tencent.com/npm/esbuild-sunos-64/-/esbuild-sunos-64-0.14.54.tgz",
+ "integrity": "sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-windows-32": {
+ "version": "0.14.54",
+ "resolved": "https://mirrors.tencent.com/npm/esbuild-windows-32/-/esbuild-windows-32-0.14.54.tgz",
+ "integrity": "sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-windows-64": {
+ "version": "0.14.54",
+ "resolved": "https://mirrors.tencent.com/npm/esbuild-windows-64/-/esbuild-windows-64-0.14.54.tgz",
+ "integrity": "sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-windows-arm64": {
+ "version": "0.14.54",
+ "resolved": "https://mirrors.tencent.com/npm/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.54.tgz",
+ "integrity": "sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://mirrors.tencent.com/npm/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://mirrors.tencent.com/npm/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "dev": true
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://mirrors.tencent.com/npm/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "8.57.1",
+ "resolved": "https://mirrors.tencent.com/npm/eslint/-/eslint-8.57.1.tgz",
+ "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==",
+ "dev": true,
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@eslint-community/regexpp": "^4.6.1",
+ "@eslint/eslintrc": "^2.1.4",
+ "@eslint/js": "8.57.1",
+ "@humanwhocodes/config-array": "^0.13.0",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@nodelib/fs.walk": "^1.2.8",
+ "@ungap/structured-clone": "^1.2.0",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.2",
+ "debug": "^4.3.2",
+ "doctrine": "^3.0.0",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^7.2.2",
+ "eslint-visitor-keys": "^3.4.3",
+ "espree": "^9.6.1",
+ "esquery": "^1.4.2",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^6.0.1",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "globals": "^13.19.0",
+ "graphemer": "^1.4.0",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "is-path-inside": "^3.0.3",
+ "js-yaml": "^4.1.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.4.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3",
+ "strip-ansi": "^6.0.1",
+ "text-table": "^0.2.0"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-config-airbnb-base": {
+ "version": "15.0.0",
+ "resolved": "https://mirrors.tencent.com/npm/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz",
+ "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==",
+ "dev": true,
+ "dependencies": {
+ "confusing-browser-globals": "^1.0.10",
+ "object.assign": "^4.1.2",
+ "object.entries": "^1.1.5",
+ "semver": "^6.3.0"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ },
+ "peerDependencies": {
+ "eslint": "^7.32.0 || ^8.2.0",
+ "eslint-plugin-import": "^2.25.2"
+ }
+ },
+ "node_modules/eslint-config-prettier": {
+ "version": "8.10.0",
+ "resolved": "https://mirrors.tencent.com/npm/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz",
+ "integrity": "sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==",
+ "dev": true,
+ "bin": {
+ "eslint-config-prettier": "bin/cli.js"
+ },
+ "peerDependencies": {
+ "eslint": ">=7.0.0"
+ }
+ },
+ "node_modules/eslint-config-react-app": {
+ "version": "7.0.1",
+ "resolved": "https://mirrors.tencent.com/npm/eslint-config-react-app/-/eslint-config-react-app-7.0.1.tgz",
+ "integrity": "sha512-K6rNzvkIeHaTd8m/QEh1Zko0KI7BACWkkneSs6s9cKZC/J27X3eZR6Upt1jkmZ/4FK+XUOPPxMEN7+lbUXfSlA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/core": "^7.16.0",
+ "@babel/eslint-parser": "^7.16.3",
+ "@rushstack/eslint-patch": "^1.1.0",
+ "@typescript-eslint/eslint-plugin": "^5.5.0",
+ "@typescript-eslint/parser": "^5.5.0",
+ "babel-preset-react-app": "^10.0.1",
+ "confusing-browser-globals": "^1.0.11",
+ "eslint-plugin-flowtype": "^8.0.3",
+ "eslint-plugin-import": "^2.25.3",
+ "eslint-plugin-jest": "^25.3.0",
+ "eslint-plugin-jsx-a11y": "^6.5.1",
+ "eslint-plugin-react": "^7.27.1",
+ "eslint-plugin-react-hooks": "^4.3.0",
+ "eslint-plugin-testing-library": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "eslint": "^8.0.0"
+ }
+ },
+ "node_modules/eslint-import-resolver-node": {
+ "version": "0.3.9",
+ "resolved": "https://mirrors.tencent.com/npm/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz",
+ "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==",
+ "dev": true,
+ "dependencies": {
+ "debug": "^3.2.7",
+ "is-core-module": "^2.13.0",
+ "resolve": "^1.22.4"
+ }
+ },
+ "node_modules/eslint-import-resolver-node/node_modules/debug": {
+ "version": "3.2.7",
+ "resolved": "https://mirrors.tencent.com/npm/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/eslint-module-utils": {
+ "version": "2.12.0",
+ "resolved": "https://mirrors.tencent.com/npm/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz",
+ "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==",
+ "dev": true,
+ "dependencies": {
+ "debug": "^3.2.7"
+ },
+ "engines": {
+ "node": ">=4"
+ },
+ "peerDependenciesMeta": {
+ "eslint": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-module-utils/node_modules/debug": {
+ "version": "3.2.7",
+ "resolved": "https://mirrors.tencent.com/npm/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/eslint-plugin-flowtype": {
+ "version": "8.0.3",
+ "resolved": "https://mirrors.tencent.com/npm/eslint-plugin-flowtype/-/eslint-plugin-flowtype-8.0.3.tgz",
+ "integrity": "sha512-dX8l6qUL6O+fYPtpNRideCFSpmWOUVx5QcaGLVqe/vlDiBSe4vYljDWDETwnyFzpl7By/WVIu6rcrniCgH9BqQ==",
+ "dev": true,
+ "dependencies": {
+ "lodash": "^4.17.21",
+ "string-natural-compare": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "@babel/plugin-syntax-flow": "^7.14.5",
+ "@babel/plugin-transform-react-jsx": "^7.14.9",
+ "eslint": "^8.1.0"
+ }
+ },
+ "node_modules/eslint-plugin-import": {
+ "version": "2.31.0",
+ "resolved": "https://mirrors.tencent.com/npm/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz",
+ "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==",
+ "dev": true,
+ "dependencies": {
+ "@rtsao/scc": "^1.1.0",
+ "array-includes": "^3.1.8",
+ "array.prototype.findlastindex": "^1.2.5",
+ "array.prototype.flat": "^1.3.2",
+ "array.prototype.flatmap": "^1.3.2",
+ "debug": "^3.2.7",
+ "doctrine": "^2.1.0",
+ "eslint-import-resolver-node": "^0.3.9",
+ "eslint-module-utils": "^2.12.0",
+ "hasown": "^2.0.2",
+ "is-core-module": "^2.15.1",
+ "is-glob": "^4.0.3",
+ "minimatch": "^3.1.2",
+ "object.fromentries": "^2.0.8",
+ "object.groupby": "^1.0.3",
+ "object.values": "^1.2.0",
+ "semver": "^6.3.1",
+ "string.prototype.trimend": "^1.0.8",
+ "tsconfig-paths": "^3.15.0"
+ },
+ "engines": {
+ "node": ">=4"
+ },
+ "peerDependencies": {
+ "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9"
+ }
+ },
+ "node_modules/eslint-plugin-import/node_modules/debug": {
+ "version": "3.2.7",
+ "resolved": "https://mirrors.tencent.com/npm/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/eslint-plugin-import/node_modules/doctrine": {
+ "version": "2.1.0",
+ "resolved": "https://mirrors.tencent.com/npm/doctrine/-/doctrine-2.1.0.tgz",
+ "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
+ "dev": true,
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/eslint-plugin-jest": {
+ "version": "25.7.0",
+ "resolved": "https://mirrors.tencent.com/npm/eslint-plugin-jest/-/eslint-plugin-jest-25.7.0.tgz",
+ "integrity": "sha512-PWLUEXeeF7C9QGKqvdSbzLOiLTx+bno7/HC9eefePfEb257QFHg7ye3dh80AZVkaa/RQsBB1Q/ORQvg2X7F0NQ==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/experimental-utils": "^5.0.0"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/eslint-plugin": "^4.0.0 || ^5.0.0",
+ "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@typescript-eslint/eslint-plugin": {
+ "optional": true
+ },
+ "jest": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-plugin-jsx-a11y": {
+ "version": "6.10.2",
+ "resolved": "https://mirrors.tencent.com/npm/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz",
+ "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==",
+ "dev": true,
+ "dependencies": {
+ "aria-query": "^5.3.2",
+ "array-includes": "^3.1.8",
+ "array.prototype.flatmap": "^1.3.2",
+ "ast-types-flow": "^0.0.8",
+ "axe-core": "^4.10.0",
+ "axobject-query": "^4.1.0",
+ "damerau-levenshtein": "^1.0.8",
+ "emoji-regex": "^9.2.2",
+ "hasown": "^2.0.2",
+ "jsx-ast-utils": "^3.3.5",
+ "language-tags": "^1.0.9",
+ "minimatch": "^3.1.2",
+ "object.fromentries": "^2.0.8",
+ "safe-regex-test": "^1.0.3",
+ "string.prototype.includes": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependencies": {
+ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9"
+ }
+ },
+ "node_modules/eslint-plugin-prettier": {
+ "version": "4.2.1",
+ "resolved": "https://mirrors.tencent.com/npm/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz",
+ "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==",
+ "dev": true,
+ "dependencies": {
+ "prettier-linter-helpers": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "eslint": ">=7.28.0",
+ "prettier": ">=2.0.0"
+ },
+ "peerDependenciesMeta": {
+ "eslint-config-prettier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-plugin-react": {
+ "version": "7.37.4",
+ "resolved": "https://mirrors.tencent.com/npm/eslint-plugin-react/-/eslint-plugin-react-7.37.4.tgz",
+ "integrity": "sha512-BGP0jRmfYyvOyvMoRX/uoUeW+GqNj9y16bPQzqAHf3AYII/tDs+jMN0dBVkl88/OZwNGwrVFxE7riHsXVfy/LQ==",
+ "dev": true,
+ "dependencies": {
+ "array-includes": "^3.1.8",
+ "array.prototype.findlast": "^1.2.5",
+ "array.prototype.flatmap": "^1.3.3",
+ "array.prototype.tosorted": "^1.1.4",
+ "doctrine": "^2.1.0",
+ "es-iterator-helpers": "^1.2.1",
+ "estraverse": "^5.3.0",
+ "hasown": "^2.0.2",
+ "jsx-ast-utils": "^2.4.1 || ^3.0.0",
+ "minimatch": "^3.1.2",
+ "object.entries": "^1.1.8",
+ "object.fromentries": "^2.0.8",
+ "object.values": "^1.2.1",
+ "prop-types": "^15.8.1",
+ "resolve": "^2.0.0-next.5",
+ "semver": "^6.3.1",
+ "string.prototype.matchall": "^4.0.12",
+ "string.prototype.repeat": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ },
+ "peerDependencies": {
+ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7"
+ }
+ },
+ "node_modules/eslint-plugin-react-hooks": {
+ "version": "4.6.2",
+ "resolved": "https://mirrors.tencent.com/npm/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz",
+ "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0"
+ }
+ },
+ "node_modules/eslint-plugin-react/node_modules/doctrine": {
+ "version": "2.1.0",
+ "resolved": "https://mirrors.tencent.com/npm/doctrine/-/doctrine-2.1.0.tgz",
+ "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
+ "dev": true,
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/eslint-plugin-react/node_modules/resolve": {
+ "version": "2.0.0-next.5",
+ "resolved": "https://mirrors.tencent.com/npm/resolve/-/resolve-2.0.0-next.5.tgz",
+ "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==",
+ "dev": true,
+ "dependencies": {
+ "is-core-module": "^2.13.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/eslint-plugin-testing-library": {
+ "version": "5.11.1",
+ "resolved": "https://mirrors.tencent.com/npm/eslint-plugin-testing-library/-/eslint-plugin-testing-library-5.11.1.tgz",
+ "integrity": "sha512-5eX9e1Kc2PqVRed3taaLnAAqPZGEX75C+M/rXzUAI3wIg/ZxzUm1OVAwfe/O+vE+6YXOLetSe9g5GKD2ecXipw==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/utils": "^5.58.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0",
+ "npm": ">=6"
+ },
+ "peerDependencies": {
+ "eslint": "^7.5.0 || ^8.0.0"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "7.2.2",
+ "resolved": "https://mirrors.tencent.com/npm/eslint-scope/-/eslint-scope-7.2.2.tgz",
+ "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
+ "dev": true,
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://mirrors.tencent.com/npm/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint/node_modules/globals": {
+ "version": "13.24.0",
+ "resolved": "https://mirrors.tencent.com/npm/globals/-/globals-13.24.0.tgz",
+ "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
+ "dev": true,
+ "dependencies": {
+ "type-fest": "^0.20.2"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/espree": {
+ "version": "9.6.1",
+ "resolved": "https://mirrors.tencent.com/npm/espree/-/espree-9.6.1.tgz",
+ "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
+ "dev": true,
+ "dependencies": {
+ "acorn": "^8.9.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^3.4.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.6.0",
+ "resolved": "https://mirrors.tencent.com/npm/esquery/-/esquery-1.6.0.tgz",
+ "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
+ "dev": true,
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://mirrors.tencent.com/npm/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://mirrors.tencent.com/npm/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://mirrors.tencent.com/npm/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+ "dev": true
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://mirrors.tencent.com/npm/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://mirrors.tencent.com/npm/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
+ },
+ "node_modules/fast-diff": {
+ "version": "1.3.0",
+ "resolved": "https://mirrors.tencent.com/npm/fast-diff/-/fast-diff-1.3.0.tgz",
+ "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
+ "dev": true
+ },
+ "node_modules/fast-glob": {
+ "version": "3.3.3",
+ "resolved": "https://mirrors.tencent.com/npm/fast-glob/-/fast-glob-3.3.3.tgz",
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.8"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://mirrors.tencent.com/npm/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://mirrors.tencent.com/npm/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://mirrors.tencent.com/npm/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true
+ },
+ "node_modules/fastq": {
+ "version": "1.18.0",
+ "resolved": "https://mirrors.tencent.com/npm/fastq/-/fastq-1.18.0.tgz",
+ "integrity": "sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==",
+ "dev": true,
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "6.0.1",
+ "resolved": "https://mirrors.tencent.com/npm/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+ "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+ "dev": true,
+ "dependencies": {
+ "flat-cache": "^3.0.4"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://mirrors.tencent.com/npm/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/finalhandler": {
+ "version": "1.1.2",
+ "resolved": "https://mirrors.tencent.com/npm/finalhandler/-/finalhandler-1.1.2.tgz",
+ "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
+ "dev": true,
+ "dependencies": {
+ "debug": "2.6.9",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "on-finished": "~2.3.0",
+ "parseurl": "~1.3.3",
+ "statuses": "~1.5.0",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/finalhandler/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://mirrors.tencent.com/npm/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/finalhandler/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://mirrors.tencent.com/npm/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "dev": true
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://mirrors.tencent.com/npm/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "3.2.0",
+ "resolved": "https://mirrors.tencent.com/npm/flat-cache/-/flat-cache-3.2.0.tgz",
+ "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
+ "dev": true,
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.3",
+ "rimraf": "^3.0.2"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.3.2",
+ "resolved": "https://mirrors.tencent.com/npm/flatted/-/flatted-3.3.2.tgz",
+ "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==",
+ "dev": true
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.15.9",
+ "resolved": "https://mirrors.tencent.com/npm/follow-redirects/-/follow-redirects-1.15.9.tgz",
+ "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/for-each": {
+ "version": "0.3.3",
+ "resolved": "https://mirrors.tencent.com/npm/for-each/-/for-each-0.3.3.tgz",
+ "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==",
+ "dev": true,
+ "dependencies": {
+ "is-callable": "^1.1.3"
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.1",
+ "resolved": "https://mirrors.tencent.com/npm/form-data/-/form-data-4.0.1.tgz",
+ "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://mirrors.tencent.com/npm/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": true
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://mirrors.tencent.com/npm/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://mirrors.tencent.com/npm/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/function.prototype.name": {
+ "version": "1.1.8",
+ "resolved": "https://mirrors.tencent.com/npm/function.prototype.name/-/function.prototype.name-1.1.8.tgz",
+ "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "define-properties": "^1.2.1",
+ "functions-have-names": "^1.2.3",
+ "hasown": "^2.0.2",
+ "is-callable": "^1.2.7"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/functions-have-names": {
+ "version": "1.2.3",
+ "resolved": "https://mirrors.tencent.com/npm/functions-have-names/-/functions-have-names-1.2.3.tgz",
+ "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://mirrors.tencent.com/npm/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.2.7",
+ "resolved": "https://mirrors.tencent.com/npm/get-intrinsic/-/get-intrinsic-1.2.7.tgz",
+ "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.0.0",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.0",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://mirrors.tencent.com/npm/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "dev": true,
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/get-symbol-description": {
+ "version": "1.1.0",
+ "resolved": "https://mirrors.tencent.com/npm/get-symbol-description/-/get-symbol-description-1.1.0.tgz",
+ "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://mirrors.tencent.com/npm/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "dev": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://mirrors.tencent.com/npm/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/globals": {
+ "version": "11.12.0",
+ "resolved": "https://mirrors.tencent.com/npm/globals/-/globals-11.12.0.tgz",
+ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/globalthis": {
+ "version": "1.0.4",
+ "resolved": "https://mirrors.tencent.com/npm/globalthis/-/globalthis-1.0.4.tgz",
+ "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==",
+ "dev": true,
+ "dependencies": {
+ "define-properties": "^1.2.1",
+ "gopd": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/globby": {
+ "version": "11.1.0",
+ "resolved": "https://mirrors.tencent.com/npm/globby/-/globby-11.1.0.tgz",
+ "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
+ "dev": true,
+ "dependencies": {
+ "array-union": "^2.1.0",
+ "dir-glob": "^3.0.1",
+ "fast-glob": "^3.2.9",
+ "ignore": "^5.2.0",
+ "merge2": "^1.4.1",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://mirrors.tencent.com/npm/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://mirrors.tencent.com/npm/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true,
+ "optional": true
+ },
+ "node_modules/graphemer": {
+ "version": "1.4.0",
+ "resolved": "https://mirrors.tencent.com/npm/graphemer/-/graphemer-1.4.0.tgz",
+ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+ "dev": true
+ },
+ "node_modules/has-bigints": {
+ "version": "1.1.0",
+ "resolved": "https://mirrors.tencent.com/npm/has-bigints/-/has-bigints-1.1.0.tgz",
+ "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://mirrors.tencent.com/npm/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/has-property-descriptors": {
+ "version": "1.0.2",
+ "resolved": "https://mirrors.tencent.com/npm/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
+ "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
+ "dev": true,
+ "dependencies": {
+ "es-define-property": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-proto": {
+ "version": "1.2.0",
+ "resolved": "https://mirrors.tencent.com/npm/has-proto/-/has-proto-1.2.0.tgz",
+ "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==",
+ "dev": true,
+ "dependencies": {
+ "dunder-proto": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://mirrors.tencent.com/npm/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://mirrors.tencent.com/npm/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "dev": true,
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://mirrors.tencent.com/npm/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "dev": true,
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/hoist-non-react-statics": {
+ "version": "3.3.2",
+ "resolved": "https://mirrors.tencent.com/npm/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
+ "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
+ "dependencies": {
+ "react-is": "^16.7.0"
+ }
+ },
+ "node_modules/husky": {
+ "version": "8.0.3",
+ "resolved": "https://mirrors.tencent.com/npm/husky/-/husky-8.0.3.tgz",
+ "integrity": "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==",
+ "dev": true,
+ "bin": {
+ "husky": "lib/bin.js"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/typicode"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://mirrors.tencent.com/npm/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://mirrors.tencent.com/npm/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/image-size": {
+ "version": "0.5.5",
+ "resolved": "https://mirrors.tencent.com/npm/image-size/-/image-size-0.5.5.tgz",
+ "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==",
+ "dev": true,
+ "optional": true,
+ "bin": {
+ "image-size": "bin/image-size.js"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/immer": {
+ "version": "9.0.21",
+ "resolved": "https://mirrors.tencent.com/npm/immer/-/immer-9.0.21.tgz",
+ "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/immer"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.0",
+ "resolved": "https://mirrors.tencent.com/npm/import-fresh/-/import-fresh-3.3.0.tgz",
+ "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+ "dev": true,
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://mirrors.tencent.com/npm/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://mirrors.tencent.com/npm/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "dev": true,
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://mirrors.tencent.com/npm/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true
+ },
+ "node_modules/internal-slot": {
+ "version": "1.1.0",
+ "resolved": "https://mirrors.tencent.com/npm/internal-slot/-/internal-slot-1.1.0.tgz",
+ "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==",
+ "dev": true,
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "hasown": "^2.0.2",
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/is-array-buffer": {
+ "version": "3.0.5",
+ "resolved": "https://mirrors.tencent.com/npm/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
+ "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "get-intrinsic": "^1.2.6"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://mirrors.tencent.com/npm/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
+ "dev": true
+ },
+ "node_modules/is-async-function": {
+ "version": "2.1.0",
+ "resolved": "https://mirrors.tencent.com/npm/is-async-function/-/is-async-function-2.1.0.tgz",
+ "integrity": "sha512-GExz9MtyhlZyXYLxzlJRj5WUCE661zhDa1Yna52CN57AJsymh+DvXXjyveSioqSRdxvUrdKdvqB1b5cVKsNpWQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "get-proto": "^1.0.1",
+ "has-tostringtag": "^1.0.2",
+ "safe-regex-test": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-bigint": {
+ "version": "1.1.0",
+ "resolved": "https://mirrors.tencent.com/npm/is-bigint/-/is-bigint-1.1.0.tgz",
+ "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==",
+ "dev": true,
+ "dependencies": {
+ "has-bigints": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://mirrors.tencent.com/npm/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-boolean-object": {
+ "version": "1.2.1",
+ "resolved": "https://mirrors.tencent.com/npm/is-boolean-object/-/is-boolean-object-1.2.1.tgz",
+ "integrity": "sha512-l9qO6eFlUETHtuihLcYOaLKByJ1f+N4kthcU9YjHy3N+B3hWv0y/2Nd0mu/7lTFnRQHTrSdXF50HQ3bl5fEnng==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-callable": {
+ "version": "1.2.7",
+ "resolved": "https://mirrors.tencent.com/npm/is-callable/-/is-callable-1.2.7.tgz",
+ "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.16.1",
+ "resolved": "https://mirrors.tencent.com/npm/is-core-module/-/is-core-module-2.16.1.tgz",
+ "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
+ "dev": true,
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-data-view": {
+ "version": "1.0.2",
+ "resolved": "https://mirrors.tencent.com/npm/is-data-view/-/is-data-view-1.0.2.tgz",
+ "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "get-intrinsic": "^1.2.6",
+ "is-typed-array": "^1.1.13"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-date-object": {
+ "version": "1.1.0",
+ "resolved": "https://mirrors.tencent.com/npm/is-date-object/-/is-date-object-1.1.0.tgz",
+ "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://mirrors.tencent.com/npm/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-finalizationregistry": {
+ "version": "1.1.1",
+ "resolved": "https://mirrors.tencent.com/npm/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz",
+ "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-generator-function": {
+ "version": "1.1.0",
+ "resolved": "https://mirrors.tencent.com/npm/is-generator-function/-/is-generator-function-1.1.0.tgz",
+ "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "get-proto": "^1.0.0",
+ "has-tostringtag": "^1.0.2",
+ "safe-regex-test": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://mirrors.tencent.com/npm/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-map": {
+ "version": "2.0.3",
+ "resolved": "https://mirrors.tencent.com/npm/is-map/-/is-map-2.0.3.tgz",
+ "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://mirrors.tencent.com/npm/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-number-object": {
+ "version": "1.1.1",
+ "resolved": "https://mirrors.tencent.com/npm/is-number-object/-/is-number-object-1.1.1.tgz",
+ "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-path-inside": {
+ "version": "3.0.3",
+ "resolved": "https://mirrors.tencent.com/npm/is-path-inside/-/is-path-inside-3.0.3.tgz",
+ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-regex": {
+ "version": "1.2.1",
+ "resolved": "https://mirrors.tencent.com/npm/is-regex/-/is-regex-1.2.1.tgz",
+ "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "gopd": "^1.2.0",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-set": {
+ "version": "2.0.3",
+ "resolved": "https://mirrors.tencent.com/npm/is-set/-/is-set-2.0.3.tgz",
+ "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-shared-array-buffer": {
+ "version": "1.0.4",
+ "resolved": "https://mirrors.tencent.com/npm/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz",
+ "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-string": {
+ "version": "1.1.1",
+ "resolved": "https://mirrors.tencent.com/npm/is-string/-/is-string-1.1.1.tgz",
+ "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-symbol": {
+ "version": "1.1.1",
+ "resolved": "https://mirrors.tencent.com/npm/is-symbol/-/is-symbol-1.1.1.tgz",
+ "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "has-symbols": "^1.1.0",
+ "safe-regex-test": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-typed-array": {
+ "version": "1.1.15",
+ "resolved": "https://mirrors.tencent.com/npm/is-typed-array/-/is-typed-array-1.1.15.tgz",
+ "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==",
+ "dev": true,
+ "dependencies": {
+ "which-typed-array": "^1.1.16"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-weakmap": {
+ "version": "2.0.2",
+ "resolved": "https://mirrors.tencent.com/npm/is-weakmap/-/is-weakmap-2.0.2.tgz",
+ "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-weakref": {
+ "version": "1.1.0",
+ "resolved": "https://mirrors.tencent.com/npm/is-weakref/-/is-weakref-1.1.0.tgz",
+ "integrity": "sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-weakset": {
+ "version": "2.0.4",
+ "resolved": "https://mirrors.tencent.com/npm/is-weakset/-/is-weakset-2.0.4.tgz",
+ "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "get-intrinsic": "^1.2.6"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-what": {
+ "version": "3.14.1",
+ "resolved": "https://mirrors.tencent.com/npm/is-what/-/is-what-3.14.1.tgz",
+ "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==",
+ "dev": true
+ },
+ "node_modules/isarray": {
+ "version": "2.0.5",
+ "resolved": "https://mirrors.tencent.com/npm/isarray/-/isarray-2.0.5.tgz",
+ "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
+ "dev": true
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://mirrors.tencent.com/npm/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
+ },
+ "node_modules/iterator.prototype": {
+ "version": "1.1.5",
+ "resolved": "https://mirrors.tencent.com/npm/iterator.prototype/-/iterator.prototype-1.1.5.tgz",
+ "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==",
+ "dev": true,
+ "dependencies": {
+ "define-data-property": "^1.1.4",
+ "es-object-atoms": "^1.0.0",
+ "get-intrinsic": "^1.2.6",
+ "get-proto": "^1.0.0",
+ "has-symbols": "^1.1.0",
+ "set-function-name": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://mirrors.tencent.com/npm/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://mirrors.tencent.com/npm/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://mirrors.tencent.com/npm/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://mirrors.tencent.com/npm/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true
+ },
+ "node_modules/json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://mirrors.tencent.com/npm/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "dev": true
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://mirrors.tencent.com/npm/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://mirrors.tencent.com/npm/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://mirrors.tencent.com/npm/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/jsx-ast-utils": {
+ "version": "3.3.5",
+ "resolved": "https://mirrors.tencent.com/npm/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
+ "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==",
+ "dev": true,
+ "dependencies": {
+ "array-includes": "^3.1.6",
+ "array.prototype.flat": "^1.3.1",
+ "object.assign": "^4.1.4",
+ "object.values": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://mirrors.tencent.com/npm/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/language-subtag-registry": {
+ "version": "0.3.23",
+ "resolved": "https://mirrors.tencent.com/npm/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz",
+ "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==",
+ "dev": true
+ },
+ "node_modules/language-tags": {
+ "version": "1.0.9",
+ "resolved": "https://mirrors.tencent.com/npm/language-tags/-/language-tags-1.0.9.tgz",
+ "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==",
+ "dev": true,
+ "dependencies": {
+ "language-subtag-registry": "^0.3.20"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/less": {
+ "version": "4.2.1",
+ "resolved": "https://mirrors.tencent.com/npm/less/-/less-4.2.1.tgz",
+ "integrity": "sha512-CasaJidTIhWmjcqv0Uj5vccMI7pJgfD9lMkKtlnTHAdJdYK/7l8pM9tumLyJ0zhbD4KJLo/YvTj+xznQd5NBhg==",
+ "dev": true,
+ "dependencies": {
+ "copy-anything": "^2.0.1",
+ "parse-node-version": "^1.0.1",
+ "tslib": "^2.3.0"
+ },
+ "bin": {
+ "lessc": "bin/lessc"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "optionalDependencies": {
+ "errno": "^0.1.1",
+ "graceful-fs": "^4.1.2",
+ "image-size": "~0.5.0",
+ "make-dir": "^2.1.0",
+ "mime": "^1.4.1",
+ "needle": "^3.1.0",
+ "source-map": "~0.6.0"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://mirrors.tencent.com/npm/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://mirrors.tencent.com/npm/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "dev": true
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://mirrors.tencent.com/npm/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://mirrors.tencent.com/npm/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+ },
+ "node_modules/lodash-es": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
+ "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
+ },
+ "node_modules/lodash.debounce": {
+ "version": "4.0.8",
+ "resolved": "https://mirrors.tencent.com/npm/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
+ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
+ "dev": true
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://mirrors.tencent.com/npm/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://mirrors.tencent.com/npm/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://mirrors.tencent.com/npm/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/make-dir": {
+ "version": "2.1.0",
+ "resolved": "https://mirrors.tencent.com/npm/make-dir/-/make-dir-2.1.0.tgz",
+ "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "pify": "^4.0.1",
+ "semver": "^5.6.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/make-dir/node_modules/semver": {
+ "version": "5.7.2",
+ "resolved": "https://mirrors.tencent.com/npm/semver/-/semver-5.7.2.tgz",
+ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
+ "dev": true,
+ "optional": true,
+ "bin": {
+ "semver": "bin/semver"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://mirrors.tencent.com/npm/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://mirrors.tencent.com/npm/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://mirrors.tencent.com/npm/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/mime": {
+ "version": "1.6.0",
+ "resolved": "https://mirrors.tencent.com/npm/mime/-/mime-1.6.0.tgz",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+ "dev": true,
+ "optional": true,
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://mirrors.tencent.com/npm/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://mirrors.tencent.com/npm/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://mirrors.tencent.com/npm/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://mirrors.tencent.com/npm/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/mitt": {
+ "version": "3.0.1",
+ "resolved": "https://mirrors.tencent.com/npm/mitt/-/mitt-3.0.1.tgz",
+ "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="
+ },
+ "node_modules/mockjs": {
+ "version": "1.1.0",
+ "resolved": "https://mirrors.tencent.com/npm/mockjs/-/mockjs-1.1.0.tgz",
+ "integrity": "sha512-eQsKcWzIaZzEZ07NuEyO4Nw65g0hdWAyurVol1IPl1gahRwY+svqzfgfey8U8dahLwG44d6/RwEzuK52rSa/JQ==",
+ "dependencies": {
+ "commander": "*"
+ },
+ "bin": {
+ "random": "bin/random"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://mirrors.tencent.com/npm/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.8",
+ "resolved": "https://mirrors.tencent.com/npm/nanoid/-/nanoid-3.3.8.tgz",
+ "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://mirrors.tencent.com/npm/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true
+ },
+ "node_modules/natural-compare-lite": {
+ "version": "1.4.0",
+ "resolved": "https://mirrors.tencent.com/npm/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz",
+ "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==",
+ "dev": true
+ },
+ "node_modules/needle": {
+ "version": "3.3.1",
+ "resolved": "https://mirrors.tencent.com/npm/needle/-/needle-3.3.1.tgz",
+ "integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==",
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "iconv-lite": "^0.6.3",
+ "sax": "^1.2.4"
+ },
+ "bin": {
+ "needle": "bin/needle"
+ },
+ "engines": {
+ "node": ">= 4.4.x"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.19",
+ "resolved": "https://mirrors.tencent.com/npm/node-releases/-/node-releases-2.0.19.tgz",
+ "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
+ "dev": true
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://mirrors.tencent.com/npm/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://mirrors.tencent.com/npm/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.3",
+ "resolved": "https://mirrors.tencent.com/npm/object-inspect/-/object-inspect-1.13.3.tgz",
+ "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object-keys": {
+ "version": "1.1.1",
+ "resolved": "https://mirrors.tencent.com/npm/object-keys/-/object-keys-1.1.1.tgz",
+ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/object.assign": {
+ "version": "4.1.7",
+ "resolved": "https://mirrors.tencent.com/npm/object.assign/-/object.assign-4.1.7.tgz",
+ "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0",
+ "has-symbols": "^1.1.0",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object.entries": {
+ "version": "1.1.8",
+ "resolved": "https://mirrors.tencent.com/npm/object.entries/-/object.entries-1.1.8.tgz",
+ "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/object.fromentries": {
+ "version": "2.0.8",
+ "resolved": "https://mirrors.tencent.com/npm/object.fromentries/-/object.fromentries-2.0.8.tgz",
+ "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.2",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object.groupby": {
+ "version": "1.0.3",
+ "resolved": "https://mirrors.tencent.com/npm/object.groupby/-/object.groupby-1.0.3.tgz",
+ "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/object.values": {
+ "version": "1.2.1",
+ "resolved": "https://mirrors.tencent.com/npm/object.values/-/object.values-1.2.1.tgz",
+ "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.3.0",
+ "resolved": "https://mirrors.tencent.com/npm/on-finished/-/on-finished-2.3.0.tgz",
+ "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==",
+ "dev": true,
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://mirrors.tencent.com/npm/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dev": true,
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://mirrors.tencent.com/npm/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/own-keys": {
+ "version": "1.0.1",
+ "resolved": "https://mirrors.tencent.com/npm/own-keys/-/own-keys-1.0.1.tgz",
+ "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==",
+ "dev": true,
+ "dependencies": {
+ "get-intrinsic": "^1.2.6",
+ "object-keys": "^1.1.1",
+ "safe-push-apply": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://mirrors.tencent.com/npm/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://mirrors.tencent.com/npm/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://mirrors.tencent.com/npm/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parse-json": {
+ "version": "5.2.0",
+ "resolved": "https://mirrors.tencent.com/npm/parse-json/-/parse-json-5.2.0.tgz",
+ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.0.0",
+ "error-ex": "^1.3.1",
+ "json-parse-even-better-errors": "^2.3.0",
+ "lines-and-columns": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parse-node-version": {
+ "version": "1.0.1",
+ "resolved": "https://mirrors.tencent.com/npm/parse-node-version/-/parse-node-version-1.0.1.tgz",
+ "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://mirrors.tencent.com/npm/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://mirrors.tencent.com/npm/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://mirrors.tencent.com/npm/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://mirrors.tencent.com/npm/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://mirrors.tencent.com/npm/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true
+ },
+ "node_modules/path-to-regexp": {
+ "version": "6.3.0",
+ "resolved": "https://mirrors.tencent.com/npm/path-to-regexp/-/path-to-regexp-6.3.0.tgz",
+ "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==",
+ "dev": true
+ },
+ "node_modules/path-type": {
+ "version": "4.0.0",
+ "resolved": "https://mirrors.tencent.com/npm/path-type/-/path-type-4.0.0.tgz",
+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/performance-now": {
+ "version": "2.1.0",
+ "resolved": "https://mirrors.tencent.com/npm/performance-now/-/performance-now-2.1.0.tgz",
+ "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow=="
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://mirrors.tencent.com/npm/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://mirrors.tencent.com/npm/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pify": {
+ "version": "4.0.1",
+ "resolved": "https://mirrors.tencent.com/npm/pify/-/pify-4.0.1.tgz",
+ "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
+ "dev": true,
+ "optional": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/possible-typed-array-names": {
+ "version": "1.0.0",
+ "resolved": "https://mirrors.tencent.com/npm/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz",
+ "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.0",
+ "resolved": "https://mirrors.tencent.com/npm/postcss/-/postcss-8.5.0.tgz",
+ "integrity": "sha512-27VKOqrYfPncKA2NrFOVhP5MGAfHKLYn/Q0mz9cNQyRAKYi3VNHwYU2qKKqPCqgBmeeJ0uAFB56NumXZ5ZReXg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "nanoid": "^3.3.8",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://mirrors.tencent.com/npm/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/prettier": {
+ "version": "2.8.8",
+ "resolved": "https://mirrors.tencent.com/npm/prettier/-/prettier-2.8.8.tgz",
+ "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
+ "dev": true,
+ "bin": {
+ "prettier": "bin-prettier.js"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/prettier-linter-helpers": {
+ "version": "1.0.0",
+ "resolved": "https://mirrors.tencent.com/npm/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
+ "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
+ "dev": true,
+ "dependencies": {
+ "fast-diff": "^1.1.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/prop-types": {
+ "version": "15.8.1",
+ "resolved": "https://mirrors.tencent.com/npm/prop-types/-/prop-types-15.8.1.tgz",
+ "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
+ "dependencies": {
+ "loose-envify": "^1.4.0",
+ "object-assign": "^4.1.1",
+ "react-is": "^16.13.1"
+ }
+ },
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://mirrors.tencent.com/npm/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+ },
+ "node_modules/prr": {
+ "version": "1.0.1",
+ "resolved": "https://mirrors.tencent.com/npm/prr/-/prr-1.0.1.tgz",
+ "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==",
+ "dev": true,
+ "optional": true
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://mirrors.tencent.com/npm/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://mirrors.tencent.com/npm/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/raf": {
+ "version": "3.4.1",
+ "resolved": "https://mirrors.tencent.com/npm/raf/-/raf-3.4.1.tgz",
+ "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==",
+ "dependencies": {
+ "performance-now": "^2.1.0"
+ }
+ },
+ "node_modules/react": {
+ "version": "18.3.1",
+ "resolved": "https://mirrors.tencent.com/npm/react/-/react-18.3.1.tgz",
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.3.1",
+ "resolved": "https://mirrors.tencent.com/npm/react-dom/-/react-dom-18.3.1.tgz",
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.2"
+ },
+ "peerDependencies": {
+ "react": "^18.3.1"
+ }
+ },
+ "node_modules/react-fast-compare": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz",
+ "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ=="
+ },
+ "node_modules/react-is": {
+ "version": "16.13.1",
+ "resolved": "https://mirrors.tencent.com/npm/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+ },
+ "node_modules/react-redux": {
+ "version": "7.2.9",
+ "resolved": "https://mirrors.tencent.com/npm/react-redux/-/react-redux-7.2.9.tgz",
+ "integrity": "sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.15.4",
+ "@types/react-redux": "^7.1.20",
+ "hoist-non-react-statics": "^3.3.2",
+ "loose-envify": "^1.4.0",
+ "prop-types": "^15.7.2",
+ "react-is": "^17.0.2"
+ },
+ "peerDependencies": {
+ "react": "^16.8.3 || ^17 || ^18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ },
+ "react-native": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-redux/node_modules/react-is": {
+ "version": "17.0.2",
+ "resolved": "https://mirrors.tencent.com/npm/react-is/-/react-is-17.0.2.tgz",
+ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
+ },
+ "node_modules/react-refresh": {
+ "version": "0.13.0",
+ "resolved": "https://mirrors.tencent.com/npm/react-refresh/-/react-refresh-0.13.0.tgz",
+ "integrity": "sha512-XP8A9BT0CpRBD+NYLLeIhld/RqG9+gktUjW1FkE+Vm7OCinbG1SshcK5tb9ls4kzvjZr9mOQc7HYgBngEyPAXg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-router": {
+ "version": "6.28.1",
+ "resolved": "https://mirrors.tencent.com/npm/react-router/-/react-router-6.28.1.tgz",
+ "integrity": "sha512-2omQTA3rkMljmrvvo6WtewGdVh45SpL9hGiCI9uUrwGGfNFDIvGK4gYJsKlJoNVi6AQZcopSCballL+QGOm7fA==",
+ "dependencies": {
+ "@remix-run/router": "1.21.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8"
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "6.28.1",
+ "resolved": "https://mirrors.tencent.com/npm/react-router-dom/-/react-router-dom-6.28.1.tgz",
+ "integrity": "sha512-YraE27C/RdjcZwl5UCqF/ffXnZDxpJdk9Q6jw38SZHjXs7NNdpViq2l2c7fO7+4uWaEfcwfGCv3RSg4e1By/fQ==",
+ "dependencies": {
+ "@remix-run/router": "1.21.0",
+ "react-router": "6.28.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8",
+ "react-dom": ">=16.8"
+ }
+ },
+ "node_modules/react-transition-group": {
+ "version": "4.4.5",
+ "resolved": "https://mirrors.tencent.com/npm/react-transition-group/-/react-transition-group-4.4.5.tgz",
+ "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
+ "dependencies": {
+ "@babel/runtime": "^7.5.5",
+ "dom-helpers": "^5.0.1",
+ "loose-envify": "^1.4.0",
+ "prop-types": "^15.6.2"
+ },
+ "peerDependencies": {
+ "react": ">=16.6.0",
+ "react-dom": ">=16.6.0"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://mirrors.tencent.com/npm/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/redux": {
+ "version": "4.2.1",
+ "resolved": "https://mirrors.tencent.com/npm/redux/-/redux-4.2.1.tgz",
+ "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
+ "dependencies": {
+ "@babel/runtime": "^7.9.2"
+ }
+ },
+ "node_modules/redux-thunk": {
+ "version": "2.4.2",
+ "resolved": "https://mirrors.tencent.com/npm/redux-thunk/-/redux-thunk-2.4.2.tgz",
+ "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==",
+ "peerDependencies": {
+ "redux": "^4"
+ }
+ },
+ "node_modules/reflect.getprototypeof": {
+ "version": "1.0.10",
+ "resolved": "https://mirrors.tencent.com/npm/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
+ "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.9",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.0.0",
+ "get-intrinsic": "^1.2.7",
+ "get-proto": "^1.0.1",
+ "which-builtin-type": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/regenerate": {
+ "version": "1.4.2",
+ "resolved": "https://mirrors.tencent.com/npm/regenerate/-/regenerate-1.4.2.tgz",
+ "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==",
+ "dev": true
+ },
+ "node_modules/regenerate-unicode-properties": {
+ "version": "10.2.0",
+ "resolved": "https://mirrors.tencent.com/npm/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz",
+ "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==",
+ "dev": true,
+ "dependencies": {
+ "regenerate": "^1.4.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/regenerator-runtime": {
+ "version": "0.14.1",
+ "resolved": "https://mirrors.tencent.com/npm/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
+ "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
+ },
+ "node_modules/regenerator-transform": {
+ "version": "0.15.2",
+ "resolved": "https://mirrors.tencent.com/npm/regenerator-transform/-/regenerator-transform-0.15.2.tgz",
+ "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/runtime": "^7.8.4"
+ }
+ },
+ "node_modules/regexp.prototype.flags": {
+ "version": "1.5.4",
+ "resolved": "https://mirrors.tencent.com/npm/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
+ "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-errors": "^1.3.0",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "set-function-name": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/regexpu-core": {
+ "version": "6.2.0",
+ "resolved": "https://mirrors.tencent.com/npm/regexpu-core/-/regexpu-core-6.2.0.tgz",
+ "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==",
+ "dev": true,
+ "dependencies": {
+ "regenerate": "^1.4.2",
+ "regenerate-unicode-properties": "^10.2.0",
+ "regjsgen": "^0.8.0",
+ "regjsparser": "^0.12.0",
+ "unicode-match-property-ecmascript": "^2.0.0",
+ "unicode-match-property-value-ecmascript": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/regjsgen": {
+ "version": "0.8.0",
+ "resolved": "https://mirrors.tencent.com/npm/regjsgen/-/regjsgen-0.8.0.tgz",
+ "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==",
+ "dev": true
+ },
+ "node_modules/regjsparser": {
+ "version": "0.12.0",
+ "resolved": "https://mirrors.tencent.com/npm/regjsparser/-/regjsparser-0.12.0.tgz",
+ "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==",
+ "dev": true,
+ "dependencies": {
+ "jsesc": "~3.0.2"
+ },
+ "bin": {
+ "regjsparser": "bin/parser"
+ }
+ },
+ "node_modules/regjsparser/node_modules/jsesc": {
+ "version": "3.0.2",
+ "resolved": "https://mirrors.tencent.com/npm/jsesc/-/jsesc-3.0.2.tgz",
+ "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==",
+ "dev": true,
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/reselect": {
+ "version": "4.1.8",
+ "resolved": "https://mirrors.tencent.com/npm/reselect/-/reselect-4.1.8.tgz",
+ "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ=="
+ },
+ "node_modules/resolve": {
+ "version": "1.22.10",
+ "resolved": "https://mirrors.tencent.com/npm/resolve/-/resolve-1.22.10.tgz",
+ "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
+ "dev": true,
+ "dependencies": {
+ "is-core-module": "^2.16.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://mirrors.tencent.com/npm/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.0.4",
+ "resolved": "https://mirrors.tencent.com/npm/reusify/-/reusify-1.0.4.tgz",
+ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+ "dev": true,
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://mirrors.tencent.com/npm/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "dev": true,
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "2.77.3",
+ "resolved": "https://mirrors.tencent.com/npm/rollup/-/rollup-2.77.3.tgz",
+ "integrity": "sha512-/qxNTG7FbmefJWoeeYJFbHehJ2HNWnjkAFRKzWN/45eNBBF/r8lo992CwcJXEzyVxs5FmfId+vTSTQDb+bxA+g==",
+ "dev": true,
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://mirrors.tencent.com/npm/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/safe-array-concat": {
+ "version": "1.1.3",
+ "resolved": "https://mirrors.tencent.com/npm/safe-array-concat/-/safe-array-concat-1.1.3.tgz",
+ "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.2",
+ "get-intrinsic": "^1.2.6",
+ "has-symbols": "^1.1.0",
+ "isarray": "^2.0.5"
+ },
+ "engines": {
+ "node": ">=0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/safe-push-apply": {
+ "version": "1.0.0",
+ "resolved": "https://mirrors.tencent.com/npm/safe-push-apply/-/safe-push-apply-1.0.0.tgz",
+ "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==",
+ "dev": true,
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "isarray": "^2.0.5"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/safe-regex-test": {
+ "version": "1.1.0",
+ "resolved": "https://mirrors.tencent.com/npm/safe-regex-test/-/safe-regex-test-1.1.0.tgz",
+ "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "is-regex": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://mirrors.tencent.com/npm/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true,
+ "optional": true
+ },
+ "node_modules/sax": {
+ "version": "1.4.1",
+ "resolved": "https://mirrors.tencent.com/npm/sax/-/sax-1.4.1.tgz",
+ "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
+ "dev": true,
+ "optional": true
+ },
+ "node_modules/scheduler": {
+ "version": "0.23.2",
+ "resolved": "https://mirrors.tencent.com/npm/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://mirrors.tencent.com/npm/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/set-function-length": {
+ "version": "1.2.2",
+ "resolved": "https://mirrors.tencent.com/npm/set-function-length/-/set-function-length-1.2.2.tgz",
+ "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
+ "dev": true,
+ "dependencies": {
+ "define-data-property": "^1.1.4",
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.2.4",
+ "gopd": "^1.0.1",
+ "has-property-descriptors": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/set-function-name": {
+ "version": "2.0.2",
+ "resolved": "https://mirrors.tencent.com/npm/set-function-name/-/set-function-name-2.0.2.tgz",
+ "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==",
+ "dev": true,
+ "dependencies": {
+ "define-data-property": "^1.1.4",
+ "es-errors": "^1.3.0",
+ "functions-have-names": "^1.2.3",
+ "has-property-descriptors": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/set-proto": {
+ "version": "1.0.0",
+ "resolved": "https://mirrors.tencent.com/npm/set-proto/-/set-proto-1.0.0.tgz",
+ "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==",
+ "dev": true,
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://mirrors.tencent.com/npm/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://mirrors.tencent.com/npm/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/side-channel": {
+ "version": "1.1.0",
+ "resolved": "https://mirrors.tencent.com/npm/side-channel/-/side-channel-1.1.0.tgz",
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+ "dev": true,
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3",
+ "side-channel-list": "^1.0.0",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.0",
+ "resolved": "https://mirrors.tencent.com/npm/side-channel-list/-/side-channel-list-1.0.0.tgz",
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+ "dev": true,
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://mirrors.tencent.com/npm/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://mirrors.tencent.com/npm/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/size-sensor": {
+ "version": "1.0.2",
+ "resolved": "https://mirrors.tencent.com/npm/size-sensor/-/size-sensor-1.0.2.tgz",
+ "integrity": "sha512-2NCmWxY7A9pYKGXNBfteo4hy14gWu47rg5692peVMst6lQLPKrVjhY+UTEsPI5ceFRJSl3gVgMYaUi/hKuaiKw=="
+ },
+ "node_modules/slash": {
+ "version": "3.0.0",
+ "resolved": "https://mirrors.tencent.com/npm/slash/-/slash-3.0.0.tgz",
+ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/sortablejs": {
+ "version": "1.15.6",
+ "resolved": "https://mirrors.tencent.com/npm/sortablejs/-/sortablejs-1.15.6.tgz",
+ "integrity": "sha512-aNfiuwMEpfBM/CN6LY0ibyhxPfPbyFeBTYJKCvzkJ2GkUpazIt3H+QIPAMHwqQ7tMKaHz1Qj+rJJCqljnf4p3A=="
+ },
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://mirrors.tencent.com/npm/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "optional": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://mirrors.tencent.com/npm/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/statuses": {
+ "version": "1.5.0",
+ "resolved": "https://mirrors.tencent.com/npm/statuses/-/statuses-1.5.0.tgz",
+ "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/string-natural-compare": {
+ "version": "3.0.1",
+ "resolved": "https://mirrors.tencent.com/npm/string-natural-compare/-/string-natural-compare-3.0.1.tgz",
+ "integrity": "sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==",
+ "dev": true
+ },
+ "node_modules/string.prototype.includes": {
+ "version": "2.0.1",
+ "resolved": "https://mirrors.tencent.com/npm/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz",
+ "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/string.prototype.matchall": {
+ "version": "4.0.12",
+ "resolved": "https://mirrors.tencent.com/npm/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz",
+ "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.6",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.0.0",
+ "get-intrinsic": "^1.2.6",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "internal-slot": "^1.1.0",
+ "regexp.prototype.flags": "^1.5.3",
+ "set-function-name": "^2.0.2",
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.repeat": {
+ "version": "1.0.0",
+ "resolved": "https://mirrors.tencent.com/npm/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz",
+ "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==",
+ "dev": true,
+ "dependencies": {
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.17.5"
+ }
+ },
+ "node_modules/string.prototype.trim": {
+ "version": "1.2.10",
+ "resolved": "https://mirrors.tencent.com/npm/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz",
+ "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.2",
+ "define-data-property": "^1.1.4",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.5",
+ "es-object-atoms": "^1.0.0",
+ "has-property-descriptors": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.trimend": {
+ "version": "1.0.9",
+ "resolved": "https://mirrors.tencent.com/npm/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz",
+ "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.2",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.trimstart": {
+ "version": "1.0.8",
+ "resolved": "https://mirrors.tencent.com/npm/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz",
+ "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://mirrors.tencent.com/npm/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-bom": {
+ "version": "3.0.0",
+ "resolved": "https://mirrors.tencent.com/npm/strip-bom/-/strip-bom-3.0.0.tgz",
+ "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://mirrors.tencent.com/npm/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://mirrors.tencent.com/npm/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://mirrors.tencent.com/npm/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/svg-parser": {
+ "version": "2.0.4",
+ "resolved": "https://mirrors.tencent.com/npm/svg-parser/-/svg-parser-2.0.4.tgz",
+ "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==",
+ "dev": true
+ },
+ "node_modules/tdesign-icons-react": {
+ "version": "0.6.10",
+ "resolved": "https://registry.npmmirror.com/tdesign-icons-react/-/tdesign-icons-react-0.6.10.tgz",
+ "integrity": "sha512-XcCprZa8lcPeKt0ivLGDnl1VF7gNKpdrxnx644R5kHdM3RxPQesT7O4Dqd3DKoaVHjYXWHI4ClIPYWVpmSHaOg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.16.5",
+ "classnames": "^2.2.6"
+ },
+ "peerDependencies": {
+ "react": ">=16.13.1",
+ "react-dom": ">=16.13.1"
+ }
+ },
+ "node_modules/tdesign-react": {
+ "version": "1.15.1",
+ "resolved": "https://registry.npmjs.org/tdesign-react/-/tdesign-react-1.15.1.tgz",
+ "integrity": "sha512-5htrIZtYvek54Fpz/8VZp+yC2zWmzY00S7BztIz3khZpyrYBrwQgLdMKkE5I/ueEBiQQFjwNh1oAo4oFZIPSrw==",
+ "dependencies": {
+ "@babel/runtime": "~7.26.7",
+ "@popperjs/core": "~2.11.2",
+ "@types/sortablejs": "^1.10.7",
+ "@types/validator": "^13.1.3",
+ "classnames": "~2.5.1",
+ "dayjs": "1.11.10",
+ "hoist-non-react-statics": "~3.3.2",
+ "lodash-es": "^4.17.21",
+ "mitt": "^3.0.0",
+ "raf": "~3.4.1",
+ "react-fast-compare": "^3.2.2",
+ "react-is": "^18.2.0",
+ "react-transition-group": "~4.4.1",
+ "sortablejs": "^1.15.0",
+ "tdesign-icons-react": "^0.6.1",
+ "tslib": "~2.3.1",
+ "validator": "~13.15.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.13.1",
+ "react-dom": ">=16.13.1"
+ }
+ },
+ "node_modules/tdesign-react/node_modules/dayjs": {
+ "version": "1.11.10",
+ "resolved": "https://mirrors.tencent.com/npm/dayjs/-/dayjs-1.11.10.tgz",
+ "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ=="
+ },
+ "node_modules/tdesign-react/node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://mirrors.tencent.com/npm/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="
+ },
+ "node_modules/tdesign-react/node_modules/tslib": {
+ "version": "2.3.1",
+ "resolved": "https://mirrors.tencent.com/npm/tslib/-/tslib-2.3.1.tgz",
+ "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
+ },
+ "node_modules/text-table": {
+ "version": "0.2.0",
+ "resolved": "https://mirrors.tencent.com/npm/text-table/-/text-table-0.2.0.tgz",
+ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+ "dev": true
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://mirrors.tencent.com/npm/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/tsconfig-paths": {
+ "version": "3.15.0",
+ "resolved": "https://mirrors.tencent.com/npm/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",
+ "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==",
+ "dev": true,
+ "dependencies": {
+ "@types/json5": "^0.0.29",
+ "json5": "^1.0.2",
+ "minimist": "^1.2.6",
+ "strip-bom": "^3.0.0"
+ }
+ },
+ "node_modules/tsconfig-paths/node_modules/json5": {
+ "version": "1.0.2",
+ "resolved": "https://mirrors.tencent.com/npm/json5/-/json5-1.0.2.tgz",
+ "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
+ "dev": true,
+ "dependencies": {
+ "minimist": "^1.2.0"
+ },
+ "bin": {
+ "json5": "lib/cli.js"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.3.0",
+ "resolved": "https://mirrors.tencent.com/npm/tslib/-/tslib-2.3.0.tgz",
+ "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
+ },
+ "node_modules/tsutils": {
+ "version": "3.21.0",
+ "resolved": "https://mirrors.tencent.com/npm/tsutils/-/tsutils-3.21.0.tgz",
+ "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==",
+ "dev": true,
+ "dependencies": {
+ "tslib": "^1.8.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ },
+ "peerDependencies": {
+ "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta"
+ }
+ },
+ "node_modules/tsutils/node_modules/tslib": {
+ "version": "1.14.1",
+ "resolved": "https://mirrors.tencent.com/npm/tslib/-/tslib-1.14.1.tgz",
+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+ "dev": true
+ },
+ "node_modules/tvision-color": {
+ "version": "1.6.0",
+ "resolved": "https://mirrors.tencent.com/npm/tvision-color/-/tvision-color-1.6.0.tgz",
+ "integrity": "sha512-pu8LkA4ZLBjbnmC9keqce4TLnExcQphKSoExauncSybmbqAhslXQvGdsI/FeWpxItGa4mcjGsHz5YE8bjpLjRw==",
+ "dependencies": {
+ "@babel/runtime-corejs3": "7.18.9",
+ "@material/material-color-utilities": "0.1.2",
+ "bezier-easing": "^2.1.0",
+ "chroma-js": "2.1.2"
+ }
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://mirrors.tencent.com/npm/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "0.20.2",
+ "resolved": "https://mirrors.tencent.com/npm/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/typed-array-buffer": {
+ "version": "1.0.3",
+ "resolved": "https://mirrors.tencent.com/npm/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz",
+ "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "es-errors": "^1.3.0",
+ "is-typed-array": "^1.1.14"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/typed-array-byte-length": {
+ "version": "1.0.3",
+ "resolved": "https://mirrors.tencent.com/npm/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz",
+ "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "for-each": "^0.3.3",
+ "gopd": "^1.2.0",
+ "has-proto": "^1.2.0",
+ "is-typed-array": "^1.1.14"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/typed-array-byte-offset": {
+ "version": "1.0.4",
+ "resolved": "https://mirrors.tencent.com/npm/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz",
+ "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==",
+ "dev": true,
+ "dependencies": {
+ "available-typed-arrays": "^1.0.7",
+ "call-bind": "^1.0.8",
+ "for-each": "^0.3.3",
+ "gopd": "^1.2.0",
+ "has-proto": "^1.2.0",
+ "is-typed-array": "^1.1.15",
+ "reflect.getprototypeof": "^1.0.9"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/typed-array-length": {
+ "version": "1.0.7",
+ "resolved": "https://mirrors.tencent.com/npm/typed-array-length/-/typed-array-length-1.0.7.tgz",
+ "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "for-each": "^0.3.3",
+ "gopd": "^1.0.1",
+ "is-typed-array": "^1.1.13",
+ "possible-typed-array-names": "^1.0.0",
+ "reflect.getprototypeof": "^1.0.6"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "4.9.5",
+ "resolved": "https://mirrors.tencent.com/npm/typescript/-/typescript-4.9.5.tgz",
+ "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
+ "dev": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=4.2.0"
+ }
+ },
+ "node_modules/unbox-primitive": {
+ "version": "1.1.0",
+ "resolved": "https://mirrors.tencent.com/npm/unbox-primitive/-/unbox-primitive-1.1.0.tgz",
+ "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "has-bigints": "^1.0.2",
+ "has-symbols": "^1.1.0",
+ "which-boxed-primitive": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "6.19.8",
+ "resolved": "https://mirrors.tencent.com/npm/undici-types/-/undici-types-6.19.8.tgz",
+ "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
+ "dev": true
+ },
+ "node_modules/unicode-canonical-property-names-ecmascript": {
+ "version": "2.0.1",
+ "resolved": "https://mirrors.tencent.com/npm/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz",
+ "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/unicode-match-property-ecmascript": {
+ "version": "2.0.0",
+ "resolved": "https://mirrors.tencent.com/npm/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz",
+ "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==",
+ "dev": true,
+ "dependencies": {
+ "unicode-canonical-property-names-ecmascript": "^2.0.0",
+ "unicode-property-aliases-ecmascript": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/unicode-match-property-value-ecmascript": {
+ "version": "2.2.0",
+ "resolved": "https://mirrors.tencent.com/npm/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz",
+ "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/unicode-property-aliases-ecmascript": {
+ "version": "2.1.0",
+ "resolved": "https://mirrors.tencent.com/npm/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz",
+ "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://mirrors.tencent.com/npm/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.1.2",
+ "resolved": "https://mirrors.tencent.com/npm/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz",
+ "integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://mirrors.tencent.com/npm/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/utils-merge": {
+ "version": "1.0.1",
+ "resolved": "https://mirrors.tencent.com/npm/utils-merge/-/utils-merge-1.0.1.tgz",
+ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/validator": {
+ "version": "13.15.15",
+ "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.15.tgz",
+ "integrity": "sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A==",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/vite": {
+ "version": "2.9.18",
+ "resolved": "https://mirrors.tencent.com/npm/vite/-/vite-2.9.18.tgz",
+ "integrity": "sha512-sAOqI5wNM9QvSEE70W3UGMdT8cyEn0+PmJMTFvTB8wB0YbYUWw3gUbY62AOyrXosGieF2htmeLATvNxpv/zNyQ==",
+ "dev": true,
+ "dependencies": {
+ "esbuild": "^0.14.27",
+ "postcss": "^8.4.13",
+ "resolve": "^1.22.0",
+ "rollup": ">=2.59.0 <2.78.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": ">=12.2.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ },
+ "peerDependencies": {
+ "less": "*",
+ "sass": "*",
+ "stylus": "*"
+ },
+ "peerDependenciesMeta": {
+ "less": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite-plugin-mock": {
+ "version": "2.9.8",
+ "resolved": "https://mirrors.tencent.com/npm/vite-plugin-mock/-/vite-plugin-mock-2.9.8.tgz",
+ "integrity": "sha512-YTQM5Sn7t+/DNOwTkr+W26QGTCk1PrDkhGHslTJ90lIPJhJtDTwuSkEYMAuLP9TcVQ/qExTFx/x/GE3kxJ05sw==",
+ "dev": true,
+ "dependencies": {
+ "@types/mockjs": "^1.0.4",
+ "chalk": "^4.1.2",
+ "chokidar": "^3.5.2",
+ "connect": "^3.7.0",
+ "debug": "^4.3.2",
+ "esbuild": "^0.14.36",
+ "fast-glob": "^3.2.7",
+ "path-to-regexp": "^6.2.0"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "mockjs": ">=1.1.0",
+ "vite": ">=2.0.0"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://mirrors.tencent.com/npm/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/which-boxed-primitive": {
+ "version": "1.1.1",
+ "resolved": "https://mirrors.tencent.com/npm/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz",
+ "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==",
+ "dev": true,
+ "dependencies": {
+ "is-bigint": "^1.1.0",
+ "is-boolean-object": "^1.2.1",
+ "is-number-object": "^1.1.1",
+ "is-string": "^1.1.1",
+ "is-symbol": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/which-builtin-type": {
+ "version": "1.2.1",
+ "resolved": "https://mirrors.tencent.com/npm/which-builtin-type/-/which-builtin-type-1.2.1.tgz",
+ "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "function.prototype.name": "^1.1.6",
+ "has-tostringtag": "^1.0.2",
+ "is-async-function": "^2.0.0",
+ "is-date-object": "^1.1.0",
+ "is-finalizationregistry": "^1.1.0",
+ "is-generator-function": "^1.0.10",
+ "is-regex": "^1.2.1",
+ "is-weakref": "^1.0.2",
+ "isarray": "^2.0.5",
+ "which-boxed-primitive": "^1.1.0",
+ "which-collection": "^1.0.2",
+ "which-typed-array": "^1.1.16"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/which-collection": {
+ "version": "1.0.2",
+ "resolved": "https://mirrors.tencent.com/npm/which-collection/-/which-collection-1.0.2.tgz",
+ "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==",
+ "dev": true,
+ "dependencies": {
+ "is-map": "^2.0.3",
+ "is-set": "^2.0.3",
+ "is-weakmap": "^2.0.2",
+ "is-weakset": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/which-typed-array": {
+ "version": "1.1.18",
+ "resolved": "https://mirrors.tencent.com/npm/which-typed-array/-/which-typed-array-1.1.18.tgz",
+ "integrity": "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==",
+ "dev": true,
+ "dependencies": {
+ "available-typed-arrays": "^1.0.7",
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "for-each": "^0.3.3",
+ "gopd": "^1.2.0",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://mirrors.tencent.com/npm/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://mirrors.tencent.com/npm/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "dev": true
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://mirrors.tencent.com/npm/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true
+ },
+ "node_modules/yaml": {
+ "version": "1.10.2",
+ "resolved": "https://mirrors.tencent.com/npm/yaml/-/yaml-1.10.2.tgz",
+ "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://mirrors.tencent.com/npm/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/zrender": {
+ "version": "5.6.1",
+ "resolved": "https://mirrors.tencent.com/npm/zrender/-/zrender-5.6.1.tgz",
+ "integrity": "sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==",
+ "dependencies": {
+ "tslib": "2.3.0"
+ }
+ }
+ }
+}
diff --git a/admin/package.json b/admin/package.json
new file mode 100644
index 0000000..8beae24
--- /dev/null
+++ b/admin/package.json
@@ -0,0 +1,74 @@
+{
+ "name": "@tencent/tdesign-react-starter",
+ "version": "0.3.1",
+ "private": true,
+ "scripts": {
+ "dev:mock": "vite --open --mode mock",
+ "dev": "vite --open --mode development",
+ "dev:linux": "vite --mode development",
+ "build:test": "vite build --mode test",
+ "build": "vite build --mode release",
+ "build:site": "vite build --mode site",
+ "site:preview": "npm run build && cp -r dist _site",
+ "preview": "vite preview --mode test",
+ "test": "echo \"no test specified,work in process\"",
+ "test:coverage": "echo \"no test specified,work in process\"",
+ "lint": "eslint ./src --ext ts,tsx",
+ "lint:fix": "eslint ./src --ext ts,tsx --fix",
+ "prepare": "husky install"
+ },
+ "devDependencies": {
+ "@honkhonk/vite-plugin-svgr": "^1.1.0",
+ "@types/echarts": "^4.9.13",
+ "@types/lodash": "^4.14.178",
+ "@types/mockjs": "^1.0.6",
+ "@types/node": "^20.2.5",
+ "@types/react": "^18.2.0",
+ "@types/react-dom": "^18.2.0",
+ "@types/react-router-dom": "^5.3.3",
+ "@vitejs/plugin-react": "^1.3.2",
+ "eslint": "^8.3.0",
+ "eslint-config-airbnb-base": "^15.0.0",
+ "eslint-config-prettier": "^8.4.0",
+ "eslint-config-react-app": "^7.0.0",
+ "eslint-plugin-import": "^2.25.4",
+ "eslint-plugin-prettier": "^4.0.0",
+ "eslint-plugin-react": "^7.28.0",
+ "husky": "^8.0.1",
+ "less": "^4.1.3",
+ "prettier": "^2.5.1",
+ "typescript": "^4.8.4",
+ "vite": "^2.9.15",
+ "vite-plugin-mock": "^2.9.6"
+ },
+ "dependencies": {
+ "@reduxjs/toolkit": "^1.8.5",
+ "axios": "^1.2.0",
+ "axios-jsonp": "^1.0.4",
+ "classnames": "^2.3.1",
+ "dayjs": "^1.10.7",
+ "echarts": "^5.3.0",
+ "echarts-for-react": "^3.0.2",
+ "lodash": "^4.17.21",
+ "mockjs": "^1.1.0",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-redux": "^7.2.4",
+ "react-router-dom": "^6.3.0",
+ "tdesign-icons-react": "^0.6.0",
+ "tdesign-react": "^1.15.0",
+ "tvision-color": "^1.5.0"
+ },
+ "browserslist": {
+ "production": [
+ ">0.2%",
+ "not dead",
+ "not op_mini all"
+ ],
+ "development": [
+ "last 1 chrome version",
+ "last 1 firefox version",
+ "last 1 safari version"
+ ]
+ }
+}
diff --git a/admin/public/favicon.ico b/admin/public/favicon.ico
new file mode 100644
index 0000000..086ac80
Binary files /dev/null and b/admin/public/favicon.ico differ
diff --git a/admin/public/robots.txt b/admin/public/robots.txt
new file mode 100644
index 0000000..e9e57dc
--- /dev/null
+++ b/admin/public/robots.txt
@@ -0,0 +1,3 @@
+# https://www.robotstxt.org/robotstxt.html
+User-agent: *
+Disallow:
diff --git a/admin/src/assets/image/assets-login-bg-black.png b/admin/src/assets/image/assets-login-bg-black.png
new file mode 100644
index 0000000..b4dde4b
Binary files /dev/null and b/admin/src/assets/image/assets-login-bg-black.png differ
diff --git a/admin/src/assets/image/assets-login-bg-white.png b/admin/src/assets/image/assets-login-bg-white.png
new file mode 100644
index 0000000..4275549
Binary files /dev/null and b/admin/src/assets/image/assets-login-bg-white.png differ
diff --git a/admin/src/assets/svg/assets-logo-full.svg b/admin/src/assets/svg/assets-logo-full.svg
new file mode 100644
index 0000000..335359b
--- /dev/null
+++ b/admin/src/assets/svg/assets-logo-full.svg
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/admin/src/assets/svg/assets-product-1.svg b/admin/src/assets/svg/assets-product-1.svg
new file mode 100644
index 0000000..29e97f0
--- /dev/null
+++ b/admin/src/assets/svg/assets-product-1.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/admin/src/assets/svg/assets-product-2.svg b/admin/src/assets/svg/assets-product-2.svg
new file mode 100644
index 0000000..827c8bf
--- /dev/null
+++ b/admin/src/assets/svg/assets-product-2.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/admin/src/assets/svg/assets-product-3.svg b/admin/src/assets/svg/assets-product-3.svg
new file mode 100644
index 0000000..a3d461b
--- /dev/null
+++ b/admin/src/assets/svg/assets-product-3.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/admin/src/assets/svg/assets-product-4.svg b/admin/src/assets/svg/assets-product-4.svg
new file mode 100644
index 0000000..7ac1abc
--- /dev/null
+++ b/admin/src/assets/svg/assets-product-4.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/admin/src/assets/svg/assets-result-403.svg b/admin/src/assets/svg/assets-result-403.svg
new file mode 100644
index 0000000..343a25e
--- /dev/null
+++ b/admin/src/assets/svg/assets-result-403.svg
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/admin/src/assets/svg/assets-result-404.svg b/admin/src/assets/svg/assets-result-404.svg
new file mode 100644
index 0000000..db1cb36
--- /dev/null
+++ b/admin/src/assets/svg/assets-result-404.svg
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/admin/src/assets/svg/assets-result-500.svg b/admin/src/assets/svg/assets-result-500.svg
new file mode 100644
index 0000000..e820fe2
--- /dev/null
+++ b/admin/src/assets/svg/assets-result-500.svg
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/admin/src/assets/svg/assets-result-browser-incompatible.svg b/admin/src/assets/svg/assets-result-browser-incompatible.svg
new file mode 100644
index 0000000..ebaf5ba
--- /dev/null
+++ b/admin/src/assets/svg/assets-result-browser-incompatible.svg
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/admin/src/assets/svg/assets-result-maintenance.svg b/admin/src/assets/svg/assets-result-maintenance.svg
new file mode 100644
index 0000000..f0cc27e
--- /dev/null
+++ b/admin/src/assets/svg/assets-result-maintenance.svg
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/admin/src/assets/svg/assets-result-network-error.svg b/admin/src/assets/svg/assets-result-network-error.svg
new file mode 100644
index 0000000..eeee8b5
--- /dev/null
+++ b/admin/src/assets/svg/assets-result-network-error.svg
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/admin/src/assets/svg/assets-setting-auto.svg b/admin/src/assets/svg/assets-setting-auto.svg
new file mode 100644
index 0000000..66f57de
--- /dev/null
+++ b/admin/src/assets/svg/assets-setting-auto.svg
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/admin/src/assets/svg/assets-setting-dark.svg b/admin/src/assets/svg/assets-setting-dark.svg
new file mode 100644
index 0000000..0f15649
--- /dev/null
+++ b/admin/src/assets/svg/assets-setting-dark.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/admin/src/assets/svg/assets-setting-light.svg b/admin/src/assets/svg/assets-setting-light.svg
new file mode 100644
index 0000000..ffa8f34
--- /dev/null
+++ b/admin/src/assets/svg/assets-setting-light.svg
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/admin/src/assets/svg/assets-t-logo.svg b/admin/src/assets/svg/assets-t-logo.svg
new file mode 100644
index 0000000..ed0464d
--- /dev/null
+++ b/admin/src/assets/svg/assets-t-logo.svg
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/admin/src/components/Board/index.module.less b/admin/src/components/Board/index.module.less
new file mode 100644
index 0000000..44205ad
--- /dev/null
+++ b/admin/src/components/Board/index.module.less
@@ -0,0 +1,86 @@
+.boardPanelDark {
+ background: var(--td-brand-color) !important;
+ .boardTitle,
+ .boardItemLeft,
+ .boardItemDesc,
+ .trendColorUp,
+ .trendColorDown,
+ .boardItemBottom {
+ color: var(--td-text-color-anti);
+ }
+ .trendIconUp,
+ .trendIconDown {
+ background: var(--td-brand-color-5);
+ }
+}
+.boardPanel {
+ :global {
+ .t-card__body {
+ padding-top: 0;
+ }
+ }
+}
+.boardTitle {
+ line-height: 22px;
+ font-size: 14px;
+ color: var(--td-text-color-secondary);
+}
+
+.boardItem {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.boardItemLeft {
+ display: inline-block;
+ color: var(--td-text-color-primary);
+ font-size: 27px;
+ line-height: 44px;
+}
+
+.boardItemBottom {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.boardItemIcon {
+ opacity: 0.6;
+}
+
+.boardItemDesc {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ line-height: 22px;
+ color: var(--td-text-color-placeholder);
+}
+
+.trendIcon {
+ border-radius: 50%;
+ width: 16px;
+ height: 16px;
+ margin-left: 8px;
+ margin-right: 8px;
+}
+
+.trendIconUp {
+ background: var(--td-error-color-2);
+}
+
+.trendIconDown {
+ background: var(--td-success-color-2);
+}
+
+.trendColorUp {
+ color: #e34d59;
+ display: flex;
+ align-items: center;
+}
+
+.trendColorDown {
+ color: #00a870;
+ display: flex;
+ align-items: center;
+}
diff --git a/admin/src/components/Board/index.tsx b/admin/src/components/Board/index.tsx
new file mode 100644
index 0000000..feb6ccf
--- /dev/null
+++ b/admin/src/components/Board/index.tsx
@@ -0,0 +1,77 @@
+import React from 'react';
+import { ChevronRightIcon } from 'tdesign-icons-react';
+import { Card } from 'tdesign-react';
+import classnames from 'classnames';
+import Style from './index.module.less';
+
+export enum ETrend {
+ up,
+ down,
+}
+
+export interface IBoardProps extends React.HTMLAttributes {
+ title?: string;
+ count?: string;
+ Icon?: React.ReactElement;
+ desc?: string;
+ trend?: ETrend;
+ trendNum?: string;
+ dark?: boolean;
+ border?: boolean;
+}
+
+export const TrendIcon = ({ trend, trendNum }: { trend?: ETrend; trendNum?: string | number }) => (
+
+
+ {trend === ETrend.up ? (
+
+
+
+
+ ) : (
+
+
+
+
+ )}
+
+ {trendNum}
+
+);
+
+const Board = ({ title, count, desc, trend, trendNum, Icon, dark, border = false }: IBoardProps) => (
+ {title}}
+ className={classnames({
+ [Style.boardPanelDark]: dark,
+ [Style.boardPanel]: true,
+ })}
+ bordered={border}
+ footer={
+
+ }
+ >
+
+
+);
+
+export default React.memo(Board);
diff --git a/admin/src/components/DatePicker/index.tsx b/admin/src/components/DatePicker/index.tsx
new file mode 100644
index 0000000..8d1d6fc
--- /dev/null
+++ b/admin/src/components/DatePicker/index.tsx
@@ -0,0 +1,20 @@
+import React from 'react';
+import { DateRangePicker, DateRangeValue } from 'tdesign-react';
+import dayjs from 'dayjs';
+
+const RECENT_7_DAYS: DateRangeValue = [
+ dayjs().subtract(7, 'day').format('YYYY-MM-DD'),
+ dayjs().subtract(1, 'day').format('YYYY-MM-DD'),
+];
+
+const LastWeekDatePicker = (onChange: (value: DateRangeValue) => void) => (
+ onChange(value)}
+ />
+);
+
+export default LastWeekDatePicker;
diff --git a/admin/src/components/ErrorPage/index.module.less b/admin/src/components/ErrorPage/index.module.less
new file mode 100644
index 0000000..75937c8
--- /dev/null
+++ b/admin/src/components/ErrorPage/index.module.less
@@ -0,0 +1,33 @@
+.errorBox {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ height: 75vh;
+ padding: 24px;
+ min-height: 400px;
+ color: var(--td-brand-color);
+ img {
+ width: 200px;
+ height: 140px;
+ color: var(--td-brand-color);
+ }
+
+ .title {
+ font-weight: 500;
+ font-size: 20px;
+ line-height: 28px;
+ margin-top: 8px;
+ color: var(--td-text-color-primary);
+ }
+ .description {
+ margin: 8px 0 32px;
+ font-size: 14px;
+ line-height: 22px;
+ color: var(--td-text-color-secondary);
+ }
+
+ .rightButton {
+ margin-left: 8px;
+ }
+}
diff --git a/admin/src/components/ErrorPage/index.tsx b/admin/src/components/ErrorPage/index.tsx
new file mode 100644
index 0000000..7a71403
--- /dev/null
+++ b/admin/src/components/ErrorPage/index.tsx
@@ -0,0 +1,51 @@
+import React, { memo } from 'react';
+import { Button } from 'tdesign-react';
+
+import Light403Icon from 'assets/svg/assets-result-403.svg?component';
+import Light404Icon from 'assets/svg/assets-result-404.svg?component';
+import Light500Icon from 'assets/svg/assets-result-500.svg?component';
+import style from './index.module.less';
+
+enum ECode {
+ forbidden = 403,
+ notFount = 404,
+ error = 500,
+}
+
+interface IErrorPageProps {
+ code: ECode;
+ title?: string;
+ desc?: string;
+}
+
+const errorInfo = {
+ [ECode.forbidden]: {
+ title: '403 Forbidden',
+ desc: '抱歉,您无权限访问此页面',
+ icon: ,
+ },
+ [ECode.notFount]: {
+ title: '404 Not Found',
+ desc: '抱歉,您访问的页面不存在。',
+ icon: ,
+ },
+ [ECode.error]: {
+ title: '500 Internal Server Error',
+ desc: '抱歉,服务器出错啦!',
+ icon: ,
+ },
+};
+
+const ErrorPage: React.FC = (props) => {
+ const info = errorInfo[props.code];
+ return (
+
+ {info?.icon}
+
{info?.title}
+
{info?.desc}
+
返回首页
+
+ );
+};
+
+export default memo(ErrorPage);
diff --git a/admin/src/configs/color.ts b/admin/src/configs/color.ts
new file mode 100644
index 0000000..46b4fff
--- /dev/null
+++ b/admin/src/configs/color.ts
@@ -0,0 +1,20 @@
+import { ETheme } from 'types/index.d';
+
+export const defaultColor = ['#0052d9', '#0594fa', '#00a870', '#ebb105', '#ed7b2f', '#e34d59', '#ed49b4', '#834ec2'];
+
+export const darkColor = ['#4582e6', '#29a4fb', '#03a56f', '#ca8d03', '#ed7b2f', '#ea7b84', '#f172c5', '#ab87d5'];
+
+export const CHART_COLORS = {
+ [ETheme.light]: {
+ textColor: 'rgba(0, 0, 0, 0.9)',
+ placeholderColor: 'rgba(0, 0, 0, 0.35)',
+ borderColor: '#dcdcdc',
+ containerColor: '#fff',
+ },
+ [ETheme.dark]: {
+ textColor: 'rgba(255, 255, 255, 0.9)',
+ placeholderColor: 'rgba(255, 255, 255, 0.35)',
+ borderColor: '#5e5e5e',
+ containerColor: '#242424',
+ },
+};
diff --git a/admin/src/configs/host.ts b/admin/src/configs/host.ts
new file mode 100644
index 0000000..1a408ee
--- /dev/null
+++ b/admin/src/configs/host.ts
@@ -0,0 +1,22 @@
+export default {
+ mock: {
+ // 本地mock数据
+ API: '',
+ },
+ development: {
+ // 开发环境接口请求
+ API: 'https://service-exndqyuk-1257786608.gz.apigw.tencentcs.com',
+ },
+ test: {
+ // 测试环境接口地址
+ API: 'https://service-exndqyuk-1257786608.gz.apigw.tencentcs.com',
+ },
+ release: {
+ // 正式环境接口地址
+ API: 'https://service-bv448zsw-1257786608.gz.apigw.tencentcs.com',
+ },
+ site: {
+ // TDesign部署特殊需要 与release功能一致
+ API: 'https://service-bv448zsw-1257786608.gz.apigw.tencentcs.com',
+ },
+};
diff --git a/admin/src/global.d.ts b/admin/src/global.d.ts
new file mode 100644
index 0000000..bad0def
--- /dev/null
+++ b/admin/src/global.d.ts
@@ -0,0 +1,53 @@
+declare module '*.avif' {
+ export default src as string;
+}
+
+declare module '*.bmp' {
+ export default src as string;
+}
+
+declare module '*.gif' {
+ export default src as string;
+}
+
+declare module '*.jpg' {
+ export default src as string;
+}
+
+declare module '*.jpeg' {
+ export default src as string;
+}
+
+declare module '*.png' {
+ export default src as string;
+}
+
+declare module '*.webp' {
+ export default src as string;
+}
+
+declare module '*.svg' {
+ export default src as string;
+}
+declare module '*.svg?component' {
+ export default src as string;
+}
+declare module '*.module.css' {
+ export default classes as { readonly [key: string]: string };
+}
+
+declare module '*.module.less' {
+ export default classes as { readonly [key: string]: string };
+}
+
+declare module '*.less' {
+ export default classes as { readonly [key: string]: string };
+}
+
+declare module 'tvision-color';
+
+declare interface ImportMeta {
+ env: {
+ MODE: 'development' | 'test' | 'release' | 'mock' | 'site';
+ };
+}
diff --git a/admin/src/hooks/useDynamicChart.ts b/admin/src/hooks/useDynamicChart.ts
new file mode 100644
index 0000000..9afff8b
--- /dev/null
+++ b/admin/src/hooks/useDynamicChart.ts
@@ -0,0 +1,36 @@
+import { useMemo } from 'react';
+import { useAppSelector } from 'modules/store';
+import { selectGlobal } from 'modules/global';
+import { getChartColor } from 'utils/color';
+import { CHART_COLORS } from 'configs/color';
+import lodashSet from 'lodash/set';
+import lodashMap from 'lodash/map';
+import { ETheme } from '../types';
+
+export type TChartColorKey = keyof (typeof CHART_COLORS)[ETheme.light];
+/**
+ * 根据当前主题色返回动态的图表颜色列表
+ * @param options 图表的固定配置
+ * @param configs 需要动态变换颜色的字段
+ * @returns string[]
+ */
+export default function useDynamicChart(
+ options: Record,
+ configs?: Partial>>,
+) {
+ const { theme, color } = useAppSelector(selectGlobal);
+ return useMemo(() => {
+ const dynamicColor = getChartColor(theme, color);
+ const newOptions = {
+ ...options,
+ };
+ // 设置动态的图表颜色
+ lodashSet(newOptions, 'color', dynamicColor.colorList);
+ if (configs) {
+ lodashMap(configs, (config, configKey: TChartColorKey) => {
+ config?.map((val) => lodashSet(newOptions, val, dynamicColor[configKey]));
+ });
+ }
+ return newOptions;
+ }, [theme, color, options]);
+}
diff --git a/admin/src/layouts/components/AppLayout.module.less b/admin/src/layouts/components/AppLayout.module.less
new file mode 100644
index 0000000..052c362
--- /dev/null
+++ b/admin/src/layouts/components/AppLayout.module.less
@@ -0,0 +1,29 @@
+.sidePanel {
+ height: 100%;
+ display: flex;
+ flex-direction: row!important;
+}
+
+.sideContainer {
+ flex: 1;
+ min-width: 760px;
+ overflow: auto;
+}
+
+.topPanel {
+ min-width: 1150px;
+}
+
+.mixPanel {
+ height: 100%;
+}
+
+.mixMain {
+ flex-direction: row!important;
+ flex: 1;
+ overflow: auto;
+}
+
+.mixContent {
+ overflow: auto;
+}
diff --git a/admin/src/layouts/components/AppLayout.tsx b/admin/src/layouts/components/AppLayout.tsx
new file mode 100644
index 0000000..911b593
--- /dev/null
+++ b/admin/src/layouts/components/AppLayout.tsx
@@ -0,0 +1,51 @@
+import React from 'react';
+import { Layout } from 'tdesign-react';
+import { ELayout } from 'modules/global';
+import Header from './Header';
+import Footer from './Footer';
+import Menu from './Menu';
+import classnames from 'classnames';
+import Content from './AppRouter';
+
+import Style from './AppLayout.module.less';
+
+const SideLayout = React.memo(() => (
+
+
+
+
+
+
+
+
+));
+
+const TopLayout = React.memo(() => (
+
+
+
+
+
+));
+
+const MixLayout = React.memo(() => (
+
+
+
+
+
+
+
+
+
+
+));
+
+const FullPageLayout = React.memo(() => );
+
+export default {
+ [ELayout.side]: SideLayout,
+ [ELayout.top]: TopLayout,
+ [ELayout.mix]: MixLayout,
+ [ELayout.fullPage]: FullPageLayout,
+};
diff --git a/admin/src/layouts/components/AppRouter.module.less b/admin/src/layouts/components/AppRouter.module.less
new file mode 100644
index 0000000..53fd177
--- /dev/null
+++ b/admin/src/layouts/components/AppRouter.module.less
@@ -0,0 +1,7 @@
+.loading {
+ height: 100%;
+ width: 100%;
+ display: flex;
+ justify-content: center;
+ align-content: center;
+}
diff --git a/admin/src/layouts/components/AppRouter.tsx b/admin/src/layouts/components/AppRouter.tsx
new file mode 100644
index 0000000..9294461
--- /dev/null
+++ b/admin/src/layouts/components/AppRouter.tsx
@@ -0,0 +1,65 @@
+import React, { Suspense, memo } from 'react';
+import { Routes, Route, Navigate } from 'react-router-dom';
+import { Layout, Loading } from 'tdesign-react';
+import routers, { IRouter } from 'router';
+import { resolve } from 'utils/path';
+import Page from './Page';
+import Style from './AppRouter.module.less';
+
+const { Content } = Layout;
+
+type TRenderRoutes = (routes: IRouter[], parentPath?: string, breadcrumbs?: string[]) => React.ReactNode[];
+/**
+ * 渲染应用路由
+ * @param routes
+ * @param parentPath
+ * @param breadcrumb
+ */
+const renderRoutes: TRenderRoutes = (routes, parentPath = '', breadcrumb = []) =>
+ routes.map((route, index: number) => {
+ const { Component, children, redirect, meta } = route;
+ const currentPath = resolve(parentPath, route.path);
+ let currentBreadcrumb = breadcrumb;
+
+ if (meta?.title) {
+ currentBreadcrumb = currentBreadcrumb.concat([meta?.title]);
+ }
+
+ if (redirect) {
+ // 重定向
+ return } />;
+ }
+
+ if (Component) {
+ // 有路由菜单
+ return (
+
+
+
+ }
+ />
+ );
+ }
+ // 无路由菜单
+ return children ? renderRoutes(children, currentPath, currentBreadcrumb) : null;
+ });
+
+const AppRouter = () => (
+
+
+
+
+ }
+ >
+ {renderRoutes(routers)}
+
+
+);
+
+export default memo(AppRouter);
diff --git a/admin/src/layouts/components/Footer.tsx b/admin/src/layouts/components/Footer.tsx
new file mode 100644
index 0000000..44b87d6
--- /dev/null
+++ b/admin/src/layouts/components/Footer.tsx
@@ -0,0 +1,21 @@
+import React from 'react';
+import { Layout, Row } from 'tdesign-react';
+import { useAppSelector } from 'modules/store';
+import { selectGlobal } from 'modules/global';
+
+const { Footer: TFooter } = Layout;
+
+const Footer = () => {
+ const globalState = useAppSelector(selectGlobal);
+ if (!globalState.showFooter) {
+ return null;
+ }
+
+ return (
+
+ Copyright © 2022-{new Date().getFullYear()} Tencent. All Rights Reserved
+
+ );
+};
+
+export default React.memo(Footer);
diff --git a/admin/src/layouts/components/Header/HeaderIcon.module.less b/admin/src/layouts/components/Header/HeaderIcon.module.less
new file mode 100644
index 0000000..21d593c
--- /dev/null
+++ b/admin/src/layouts/components/Header/HeaderIcon.module.less
@@ -0,0 +1,54 @@
+.header {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+.badge {
+ :global {
+ .t-badge--circle {
+ top: 5px;
+ right: 6px;
+ }
+ }
+}
+.menuIcon {
+ width: 30px !important;
+ height: 30px !important;
+}
+.dropItem {
+ display: flex;
+ align-items: center;
+ span {
+ margin-left: 8px;
+ }
+}
+.dropdown {
+ padding-left: 0;
+ padding-right: 0;
+ .icon {
+ font-size: 20px;
+ display: inline-block;
+ margin: 0 5px;
+ }
+ :global {
+ .t-button--variant-text {
+ padding-left: 9px;
+ padding-right: 9px;
+ }
+ .t-button__text {
+ display: flex;
+ align-items: center;
+ }
+ .t-dropdown__item {
+ max-width: none !important;
+ width: 117px;
+ &-text {
+ display: flex;
+ align-items: center;
+ }
+ .t-icon {
+ margin-right: 8px;
+ }
+ }
+ }
+}
diff --git a/admin/src/layouts/components/Header/HeaderIcon.tsx b/admin/src/layouts/components/Header/HeaderIcon.tsx
new file mode 100644
index 0000000..2262f10
--- /dev/null
+++ b/admin/src/layouts/components/Header/HeaderIcon.tsx
@@ -0,0 +1,100 @@
+import React, { memo } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { Button, Popup, Badge, Dropdown, Space } from 'tdesign-react';
+import {
+ Icon,
+ LogoGithubIcon,
+ MailIcon,
+ HelpCircleIcon,
+ SettingIcon,
+ PoweroffIcon,
+ UserCircleIcon,
+} from 'tdesign-icons-react';
+import { useAppDispatch } from 'modules/store';
+import { toggleSetting } from 'modules/global';
+import { logout } from 'modules/user';
+import Style from './HeaderIcon.module.less';
+
+const { DropdownMenu, DropdownItem } = Dropdown;
+
+export default memo(() => {
+ const dispatch = useAppDispatch();
+ const navigate = useNavigate();
+
+ const gotoWiki = () => {
+ window.open('https://tdesign.tencent.com/react/overview');
+ };
+
+ const gotoGitHub = () => {
+ window.open('https://github.com/Tencent/tdesign-react-starter');
+ };
+
+ const clickHandler = (data: any) => {
+ if (data.value === 1) {
+ navigate('/user/index');
+ }
+ };
+ const handleLogout = async () => {
+ await dispatch(logout());
+ navigate('/login/index');
+ };
+
+ return (
+
+
+ } />
+
+
+ }
+ />
+
+
+ }
+ />
+
+
+
+
+ Tencent
+
+
+
+
+
+
+ 个人中心
+
+
+
+
+
+
+
+
+ dispatch(toggleSetting())}
+ icon={ }
+ />
+
+
+ );
+});
diff --git a/admin/src/layouts/components/Header/Search.module.less b/admin/src/layouts/components/Header/Search.module.less
new file mode 100644
index 0000000..45db8de
--- /dev/null
+++ b/admin/src/layouts/components/Header/Search.module.less
@@ -0,0 +1,14 @@
+.panel {
+ :global {
+ .t-input {
+ border: none;
+ }
+ .t-input:hover {
+ background: var(--td-bg-color-secondarycontainer);
+ transition: background var(--td-anim-duration-base) linear
+ }
+ .t-input--focused {
+ box-shadow: none;
+ }
+ }
+}
diff --git a/admin/src/layouts/components/Header/Search.tsx b/admin/src/layouts/components/Header/Search.tsx
new file mode 100644
index 0000000..a50d7cb
--- /dev/null
+++ b/admin/src/layouts/components/Header/Search.tsx
@@ -0,0 +1,8 @@
+import React from 'react';
+import { Input } from 'tdesign-react';
+import { SearchIcon } from 'tdesign-icons-react';
+import Style from './Search.module.less';
+
+const Search = () => } placeholder='请输入搜索内容' />;
+
+export default React.memo(Search);
diff --git a/admin/src/layouts/components/Header/index.module.less b/admin/src/layouts/components/Header/index.module.less
new file mode 100644
index 0000000..f45eeae
--- /dev/null
+++ b/admin/src/layouts/components/Header/index.module.less
@@ -0,0 +1,11 @@
+.panel {
+ flex-shrink: 0;
+ padding-left: 20px;
+ padding-right: 20px;
+ position: sticky;
+ top: 0;
+ z-index: 101;
+ display: flex;
+ justify-content: space-between;
+ border-bottom: 1px solid var(--td-component-stroke);
+}
diff --git a/admin/src/layouts/components/Header/index.tsx b/admin/src/layouts/components/Header/index.tsx
new file mode 100644
index 0000000..c81bec2
--- /dev/null
+++ b/admin/src/layouts/components/Header/index.tsx
@@ -0,0 +1,49 @@
+import React, { memo } from 'react';
+import { Layout, Button, Space } from 'tdesign-react';
+import { ViewListIcon } from 'tdesign-icons-react';
+import { useAppDispatch, useAppSelector } from 'modules/store';
+import { selectGlobal, toggleMenu } from 'modules/global';
+import HeaderIcon from './HeaderIcon';
+import { HeaderMenu } from '../Menu';
+import Search from './Search';
+import Style from './index.module.less';
+
+const { Header } = Layout;
+
+export default memo((props: { showMenu?: boolean }) => {
+ const globalState = useAppSelector(selectGlobal);
+ const dispatch = useAppDispatch();
+
+ if (!globalState.showHeader) {
+ return null;
+ }
+
+ let HeaderLeft;
+ if (props.showMenu) {
+ HeaderLeft = (
+
+
+
+ );
+ } else {
+ HeaderLeft = (
+
+ dispatch(toggleMenu(null))}
+ icon={ }
+ />
+
+
+ );
+ }
+
+ return (
+
+ );
+});
diff --git a/admin/src/layouts/components/Menu.module.less b/admin/src/layouts/components/Menu.module.less
new file mode 100644
index 0000000..54f83d0
--- /dev/null
+++ b/admin/src/layouts/components/Menu.module.less
@@ -0,0 +1,34 @@
+.menuPanel {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+}
+
+.menuTip {
+ color: var(--td-text-color-primary);
+ opacity: 0.4;
+ height: 38px;
+ line-height: 38px;
+ text-align: center;
+}
+
+.menuLogo {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ cursor: pointer;
+ color: var(--td-text-color-primary);
+}
+
+.menuMiniLogo {
+ width: 28px;
+ height: 28px;
+}
+
+.logoFull {
+ color: var(--td-text-color-primary);
+ width: 184px;
+ height: 32px;
+}
diff --git a/admin/src/layouts/components/Menu.tsx b/admin/src/layouts/components/Menu.tsx
new file mode 100644
index 0000000..b355f61
--- /dev/null
+++ b/admin/src/layouts/components/Menu.tsx
@@ -0,0 +1,115 @@
+import React, { memo, useState } from 'react';
+import { useNavigate, useLocation } from 'react-router-dom';
+import { Menu, MenuValue } from 'tdesign-react';
+import router, { IRouter } from 'router';
+import { resolve } from 'utils/path';
+import { useAppSelector } from 'modules/store';
+import { selectGlobal } from 'modules/global';
+import MenuLogo from './MenuLogo';
+import Style from './Menu.module.less';
+
+const { SubMenu, MenuItem, HeadMenu } = Menu;
+
+interface IMenuProps {
+ showLogo?: boolean;
+ showOperation?: boolean;
+}
+
+const renderMenuItems = (menu: IRouter[], parentPath = '') => {
+ const navigate = useNavigate();
+ return menu.map((item) => {
+ const { children, meta, path } = item;
+
+ if (!meta || meta?.hidden === true) {
+ // 无meta信息 或 hidden == true,路由不显示为菜单
+ return null;
+ }
+
+ const { Icon, title, single } = meta;
+ const routerPath = resolve(parentPath, path);
+
+ if (!children || children.length === 0) {
+ return (
+ : undefined}
+ onClick={() => navigate(routerPath)}
+ >
+ {title}
+
+ );
+ }
+
+ if (single && children?.length > 0) {
+ const firstChild = children[0];
+ if (firstChild?.meta && !firstChild?.meta?.hidden) {
+ const { Icon, title } = meta;
+ const singlePath = resolve(resolve(parentPath, path), firstChild.path);
+ return (
+ : undefined}
+ onClick={() => navigate(singlePath)}
+ >
+ {title}
+
+ );
+ }
+ }
+
+ return (
+ : undefined}>
+ {renderMenuItems(children, routerPath)}
+
+ );
+ });
+};
+
+/**
+ * 顶部菜单
+ */
+export const HeaderMenu = memo(() => {
+ const globalState = useAppSelector(selectGlobal);
+ const location = useLocation();
+ const [active, setActive] = useState(location.pathname); // todo
+
+ return (
+ setActive(v)}
+ >
+ {renderMenuItems(router)}
+
+ );
+});
+
+/**
+ * 左侧菜单
+ */
+export default memo((props: IMenuProps) => {
+ const location = useLocation();
+ const globalState = useAppSelector(selectGlobal);
+
+ const { version } = globalState;
+ const bottomText = globalState.collapsed ? version : `元境幻生管理台 ${version}`;
+
+ return (
+ {bottomText} : undefined}
+ logo={props.showLogo ? : undefined}
+ >
+ {renderMenuItems(router)}
+
+ );
+});
diff --git a/admin/src/layouts/components/MenuLogo.tsx b/admin/src/layouts/components/MenuLogo.tsx
new file mode 100644
index 0000000..73d1f9a
--- /dev/null
+++ b/admin/src/layouts/components/MenuLogo.tsx
@@ -0,0 +1,23 @@
+import React, { memo } from 'react';
+import Style from './Menu.module.less';
+import FullLogo from 'assets/svg/assets-logo-full.svg?component';
+import MiniLogo from 'assets/svg/assets-t-logo.svg?component';
+import { useNavigate } from 'react-router-dom';
+
+interface IProps {
+ collapsed?: boolean;
+}
+
+export default memo((props: IProps) => {
+ const navigate = useNavigate();
+
+ const handleClick = () => {
+ navigate('/');
+ };
+
+ return (
+
+ {props.collapsed ? : }
+
+ );
+});
diff --git a/admin/src/layouts/components/Page.module.less b/admin/src/layouts/components/Page.module.less
new file mode 100644
index 0000000..076937b
--- /dev/null
+++ b/admin/src/layouts/components/Page.module.less
@@ -0,0 +1,9 @@
+.panel {
+ margin: 24px;
+ padding: 0;
+ overflow: auto;
+}
+
+.breadcrumb {
+ margin-bottom: 8px;
+}
diff --git a/admin/src/layouts/components/Page.tsx b/admin/src/layouts/components/Page.tsx
new file mode 100644
index 0000000..b054d83
--- /dev/null
+++ b/admin/src/layouts/components/Page.tsx
@@ -0,0 +1,39 @@
+import React, { useEffect } from 'react';
+import { useAppDispatch, useAppSelector } from '../../modules/store';
+import { selectGlobal, switchFullPage } from '../../modules/global';
+import { Layout, Breadcrumb } from 'tdesign-react';
+import Style from './Page.module.less';
+
+const { Content } = Layout;
+const { BreadcrumbItem } = Breadcrumb;
+
+const Page = ({
+ children,
+ isFullPage,
+ breadcrumbs,
+}: React.PropsWithChildren<{ isFullPage?: boolean; breadcrumbs?: string[] }>) => {
+ const globalState = useAppSelector(selectGlobal);
+ const dispatch = useAppDispatch();
+ useEffect(() => {
+ dispatch(switchFullPage(isFullPage));
+ }, [isFullPage]);
+
+ if (isFullPage) {
+ return <>{children}>;
+ }
+
+ return (
+
+ {globalState.showBreadcrumbs && (
+
+ {breadcrumbs?.map((item, index) => (
+ {item}
+ ))}
+
+ )}
+ {children}
+
+ );
+};
+
+export default React.memo(Page);
diff --git a/admin/src/layouts/components/Setting/RadioColor.module.less b/admin/src/layouts/components/Setting/RadioColor.module.less
new file mode 100644
index 0000000..7e1faaf
--- /dev/null
+++ b/admin/src/layouts/components/Setting/RadioColor.module.less
@@ -0,0 +1,17 @@
+.panel {
+ display: flex;
+ justify-content: space-between;
+}
+
+.box {
+ cursor: pointer;
+ border: 2px solid;
+ padding: 4px;
+ border-radius: 50%;
+}
+
+.item {
+ width: 24px;
+ height: 24px;
+ border-radius: 50%;
+}
diff --git a/admin/src/layouts/components/Setting/RadioColor.tsx b/admin/src/layouts/components/Setting/RadioColor.tsx
new file mode 100644
index 0000000..47ec72d
--- /dev/null
+++ b/admin/src/layouts/components/Setting/RadioColor.tsx
@@ -0,0 +1,67 @@
+import React, { useState } from 'react';
+import { defaultColor } from 'configs/color';
+import { Popup, ColorPickerPanel } from 'tdesign-react';
+import Style from './RadioColor.module.less';
+
+interface IProps {
+ defaultValue?: number | string;
+ onChange: (color: string) => void;
+}
+
+const RadioColor = (props: IProps) => {
+ const [isColoPickerDisplay, onPopupVisibleChange] = useState(false);
+
+ return (
+
+ {defaultColor.map((color, index) => (
+
props?.onChange(color)}
+ className={Style.box}
+ style={{ borderColor: props.defaultValue === color && !isColoPickerDisplay ? color : 'transparent' }}
+ >
+
+
+ ))}
+
+
onPopupVisibleChange(v)}
+ overlayInnerStyle={{ padding: 0 }}
+ content={
+ props?.onChange(v)}
+ colorModes={['monochrome']}
+ format='HEX'
+ swatchColors={[]}
+ defaultValue={props.defaultValue as string}
+ />
+ }
+ >
+
+
+
+ );
+};
+
+export default React.memo(RadioColor);
diff --git a/admin/src/layouts/components/Setting/RadioRect.module.less b/admin/src/layouts/components/Setting/RadioRect.module.less
new file mode 100644
index 0000000..174daf8
--- /dev/null
+++ b/admin/src/layouts/components/Setting/RadioRect.module.less
@@ -0,0 +1,28 @@
+.radioRectPanel {
+ display: flex;
+ justify-content: space-between;
+}
+
+.rectItem {
+ border: 2px solid #e3e6eb;
+ padding: 8px;
+ border-radius: 3px;
+ cursor: pointer;
+}
+
+.rectItemSelected {
+ border-color: var(--td-brand-color);
+}
+
+.rectText {
+ text-align: center;
+ font-size: 14px;
+ margin-top: 8px;
+ line-height: 21px;
+}
+
+.rectImg {
+ width: 88px;
+ height: 48px;
+ background-size: 100% 100%;
+}
diff --git a/admin/src/layouts/components/Setting/RadioRect.tsx b/admin/src/layouts/components/Setting/RadioRect.tsx
new file mode 100644
index 0000000..620cff6
--- /dev/null
+++ b/admin/src/layouts/components/Setting/RadioRect.tsx
@@ -0,0 +1,47 @@
+import React, { memo, useState } from 'react';
+import classname from 'classnames';
+import Style from './RadioRect.module.less';
+
+interface IOption {
+ value?: any;
+ image: JSX.Element | string;
+ name?: string;
+}
+
+interface IProps {
+ defaultValue?: number | string;
+ onChange: (value?: any) => void;
+ options: IOption[];
+}
+
+export default memo((props: IProps) => {
+ const [selectValue, setSelectValue] = useState(props.defaultValue);
+
+ const handleClick = (option: IOption) => {
+ setSelectValue(option.value);
+ props?.onChange(option.value);
+ };
+
+ return (
+
+ {props.options.map((item, index) => {
+ let ImageItem = item.image;
+ if (typeof item.image === 'string') {
+ ImageItem =
;
+ }
+
+ return (
+
+
handleClick(item)}
+ >
+ {ImageItem}
+
+ {item.name &&
{item.name}
}
+
+ );
+ })}
+
+ );
+});
diff --git a/admin/src/layouts/components/Setting/index.module.less b/admin/src/layouts/components/Setting/index.module.less
new file mode 100644
index 0000000..626a66f
--- /dev/null
+++ b/admin/src/layouts/components/Setting/index.module.less
@@ -0,0 +1,13 @@
+.settingTitle {
+ margin: 32px 0 24px 0;
+ color: var(--td-text-color-primary);
+ font-size: 14px;
+ line-height: 22px;
+ font-weight: 500;
+}
+
+.settingSubTitle {
+ font-size: 14px;
+ line-height: 21px;
+ margin-bottom: 24px;
+}
diff --git a/admin/src/layouts/components/Setting/index.tsx b/admin/src/layouts/components/Setting/index.tsx
new file mode 100644
index 0000000..fa185bd
--- /dev/null
+++ b/admin/src/layouts/components/Setting/index.tsx
@@ -0,0 +1,123 @@
+import React, { memo } from 'react';
+import { Row, Col, Switch } from 'tdesign-react';
+import { useAppDispatch, useAppSelector } from 'modules/store';
+import {
+ selectGlobal,
+ toggleShowHeader,
+ toggleShowBreadcrumbs,
+ toggleShowFooter,
+ switchTheme,
+ switchColor,
+ switchLayout,
+ openSystemTheme,
+ ELayout,
+} from 'modules/global';
+import { ETheme } from 'types/index.d';
+import RadioColor from './RadioColor';
+import RadioRect from './RadioRect';
+
+import Light from 'assets/svg/assets-setting-light.svg?component';
+import Dark from 'assets/svg/assets-setting-dark.svg?component';
+import System from 'assets/svg/assets-setting-auto.svg?component';
+
+import Style from './index.module.less';
+
+enum ESettingTheme {
+ system,
+}
+
+const themeList = [
+ {
+ value: ETheme.light,
+ image: ,
+ name: '明亮',
+ },
+ {
+ value: ETheme.dark,
+ image: ,
+ name: '黑暗',
+ },
+ {
+ value: ESettingTheme.system,
+ image: ,
+ name: '跟随系统',
+ },
+];
+
+const layoutList = [
+ {
+ value: ELayout.side,
+ image: 'https://tdesign.gtimg.com/starter/setting/side.png',
+ },
+ {
+ value: ELayout.top,
+ image: 'https://tdesign.gtimg.com/starter/setting/top.png',
+ },
+ {
+ value: ELayout.mix,
+ image: 'https://tdesign.gtimg.com/starter/setting/mix.png',
+ },
+];
+
+export default memo(() => {
+ const dispatch = useAppDispatch();
+ const globalState = useAppSelector(selectGlobal);
+
+ const handleThemeSwitch = (value: any) => {
+ if (value === ESettingTheme.system) {
+ dispatch(openSystemTheme());
+ } else {
+ dispatch(switchTheme(value));
+ dispatch(switchColor(globalState.color));
+ }
+ };
+
+ return (
+
+
主题模式
+
+
+
主题色
+
dispatch(switchColor(value))} />
+
+ 导航布局
+ dispatch(switchLayout(value))}
+ options={layoutList}
+ />
+
+ 元素开关
+
+
+ 显示 Header
+
+
+ dispatch(toggleShowHeader())} />
+
+
+
+
+
+ 显示 Breadcrumbs
+
+
+ dispatch(toggleShowBreadcrumbs())} />
+
+
+
+
+
+ 显示 Footer
+
+
+ dispatch(toggleShowFooter())} />
+
+
+
+ );
+});
diff --git a/admin/src/layouts/index.module.less b/admin/src/layouts/index.module.less
new file mode 100644
index 0000000..0d4d602
--- /dev/null
+++ b/admin/src/layouts/index.module.less
@@ -0,0 +1,27 @@
+.panel {
+ height: 100%;
+
+ :global {
+ .t-menu--scroll::-webkit-scrollbar {
+ width: 8px;
+ background: transparent;
+ }
+ .t-menu--scroll::-webkit-scrollbar-thumb {
+ border-radius: 6px;
+ border: 2px solid transparent;
+ background-clip: content-box;
+ background-color: #eeeeee;
+ }
+
+ .t-default-menu.t-menu--dark {
+ background: var(--td-gray-color-13);
+ }
+ .t-default-menu:not(.t-menu--dark) .t-menu__item.t-is-active:not(.t-is-opened) {
+ background-color: var(--td-brand-color-1);
+ color: var(--td-brand-color);
+ .t-icon {
+ color: var(--td-brand-color);
+ }
+ }
+ }
+}
diff --git a/admin/src/layouts/index.tsx b/admin/src/layouts/index.tsx
new file mode 100644
index 0000000..8abb9cf
--- /dev/null
+++ b/admin/src/layouts/index.tsx
@@ -0,0 +1,46 @@
+import React, { memo, useEffect } from 'react';
+import { Drawer, Layout } from 'tdesign-react';
+import throttle from 'lodash/throttle';
+import { useAppSelector, useAppDispatch } from 'modules/store';
+import { selectGlobal, toggleSetting, toggleMenu, ELayout, switchTheme } from 'modules/global';
+import Setting from './components/Setting';
+import AppLayout from './components/AppLayout';
+import Style from './index.module.less';
+
+export default memo(() => {
+ const globalState = useAppSelector(selectGlobal);
+ const dispatch = useAppDispatch();
+
+ const AppContainer = AppLayout[globalState.isFullPage ? ELayout.fullPage : globalState.layout];
+
+ useEffect(() => {
+ dispatch(switchTheme(globalState.theme));
+ const handleResize = throttle(() => {
+ if (window.innerWidth < 900) {
+ dispatch(toggleMenu(true));
+ } else if (window.innerWidth > 1000) {
+ dispatch(toggleMenu(false));
+ }
+ }, 100);
+ window.addEventListener('resize', handleResize);
+ return () => {
+ window.removeEventListener('resize', handleResize);
+ };
+ }, []);
+
+ return (
+
+
+ dispatch(toggleSetting())}
+ >
+
+
+
+ );
+});
diff --git a/admin/src/main.tsx b/admin/src/main.tsx
new file mode 100644
index 0000000..c99e57d
--- /dev/null
+++ b/admin/src/main.tsx
@@ -0,0 +1,28 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import { Provider } from 'react-redux';
+import { BrowserRouter } from 'react-router-dom';
+import store from 'modules/store';
+import App from 'layouts/index';
+
+import 'tdesign-react/es/style/index.css';
+
+import './styles/index.less';
+
+const env = import.meta.env.MODE || 'development';
+const baseRouterName = env === 'site' ? '/starter/react/' : import.meta.env.BASE_URL.replace(/\/$/, '');
+
+// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+const root = document.getElementById('app')!;
+
+const renderApp = () => {
+ ReactDOM.createRoot(root).render(
+
+
+
+
+ ,
+ );
+};
+
+renderApp();
diff --git a/admin/src/modules/global/index.ts b/admin/src/modules/global/index.ts
new file mode 100644
index 0000000..9f68e32
--- /dev/null
+++ b/admin/src/modules/global/index.ts
@@ -0,0 +1,151 @@
+import { createSlice, PayloadAction } from '@reduxjs/toolkit';
+import { ETheme } from 'types/index.d';
+import { Color } from 'tvision-color';
+import { CHART_COLORS, defaultColor } from 'configs/color';
+import { generateColorMap, insertThemeStylesheet } from 'utils/color';
+import { RootState } from '../store';
+import { version } from '../../../package.json';
+
+const namespace = 'global';
+
+export enum ELayout {
+ side = 1,
+ top,
+ mix,
+ fullPage,
+}
+
+export interface IGlobalState {
+ loading: boolean;
+ collapsed: boolean;
+ /**
+ * 是否显示面包屑导航
+ */
+ setting: boolean;
+ version: string;
+ color: string;
+ /**
+ * 主题:深色 浅色
+ */
+ theme: ETheme;
+ /**
+ * 是否开启跟随系统主题
+ */
+ systemTheme: boolean;
+ layout: ELayout;
+ isFullPage: boolean;
+ showHeader: boolean;
+ showBreadcrumbs: boolean;
+ showFooter: boolean;
+ chartColors: Record;
+}
+
+const defaultTheme = ETheme.light;
+
+const initialState: IGlobalState = {
+ loading: true,
+ collapsed: window.innerWidth < 1000, // 宽度小于1000 菜单闭合
+ setting: false,
+ version,
+ theme: defaultTheme,
+ systemTheme: false,
+ layout: ELayout.side,
+ isFullPage: false,
+ color: defaultColor?.[0],
+ showHeader: true,
+ showBreadcrumbs: true,
+ showFooter: true,
+ chartColors: CHART_COLORS[defaultTheme],
+};
+
+// 创建带有命名空间的reducer
+const globalSlice = createSlice({
+ name: namespace,
+ initialState,
+ reducers: {
+ toggleMenu: (state, action) => {
+ if (action.payload === null) {
+ state.collapsed = !state.collapsed;
+ } else {
+ state.collapsed = !!action.payload;
+ }
+ },
+ toggleSetting: (state) => {
+ state.setting = !state.setting;
+ },
+ toggleShowHeader: (state) => {
+ state.showHeader = !state.showHeader;
+ },
+ toggleShowBreadcrumbs: (state) => {
+ state.showBreadcrumbs = !state.showBreadcrumbs;
+ },
+ toggleShowFooter: (state) => {
+ state.showFooter = !state.showFooter;
+ },
+ switchTheme: (state, action: PayloadAction) => {
+ const finalTheme = action?.payload;
+ // 切换 chart 颜色
+ state.chartColors = CHART_COLORS[finalTheme];
+ // 切换主题颜色
+ state.theme = finalTheme;
+ // 关闭跟随系统
+ state.systemTheme = false;
+ document.documentElement.setAttribute('theme-mode', finalTheme);
+ },
+ openSystemTheme: (state) => {
+ const media = window.matchMedia('(prefers-color-scheme:dark)');
+ if (media.matches) {
+ const finalTheme = media.matches ? ETheme.dark : ETheme.light;
+ state.chartColors = CHART_COLORS[finalTheme];
+ // 切换主题颜色
+ state.theme = finalTheme;
+ state.systemTheme = true;
+ document.documentElement.setAttribute('theme-mode', finalTheme);
+ }
+ },
+ switchColor: (state, action) => {
+ if (action?.payload) {
+ state.color = action?.payload;
+ const mode = state.theme;
+
+ const hex = action?.payload;
+
+ const { colors: newPalette, primary: brandColorIndex } = Color.getColorGradations({
+ colors: [hex],
+ step: 10,
+ remainInput: false, // 是否保留输入 不保留会矫正不合适的主题色
+ })[0];
+ const newColorMap = generateColorMap(hex, newPalette, mode, brandColorIndex);
+ insertThemeStylesheet(hex, newColorMap, mode);
+
+ document.documentElement.setAttribute('theme-color', hex || '');
+ }
+ },
+ switchLayout: (state, action) => {
+ if (action?.payload) {
+ state.layout = action?.payload;
+ }
+ },
+ switchFullPage: (state, action) => {
+ state.isFullPage = !!action?.payload;
+ },
+ },
+ extraReducers: () => {},
+});
+
+export const selectGlobal = (state: RootState) => state.global;
+
+export const {
+ toggleMenu,
+ toggleSetting,
+ toggleShowHeader,
+ toggleShowBreadcrumbs,
+ toggleShowFooter,
+ switchTheme,
+ switchColor,
+ switchLayout,
+ switchFullPage,
+ openSystemTheme,
+} = globalSlice.actions;
+
+export default globalSlice.reducer;
diff --git a/admin/src/modules/list/base.ts b/admin/src/modules/list/base.ts
new file mode 100644
index 0000000..18dd6fd
--- /dev/null
+++ b/admin/src/modules/list/base.ts
@@ -0,0 +1,64 @@
+import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
+import { RootState } from '../store';
+import { getContractList, IContract } from 'services/contract';
+
+const namespace = 'list/base';
+
+interface IInitialState {
+ loading: boolean;
+ current: number;
+ pageSize: number;
+ total: number;
+ contractList: IContract[];
+}
+
+const initialState: IInitialState = {
+ loading: true,
+ current: 1,
+ pageSize: 10,
+ total: 0,
+ contractList: [],
+};
+
+export const getList = createAsyncThunk(
+ `${namespace}/getList`,
+ async (params: { pageSize: number; current: number }) => {
+ const result = await getContractList(params);
+ return {
+ list: result?.list,
+ total: result?.total,
+ pageSize: params.pageSize,
+ current: params.current,
+ };
+ },
+);
+
+const listBaseSlice = createSlice({
+ name: namespace,
+ initialState,
+ reducers: {
+ clearPageState: () => initialState,
+ },
+ extraReducers: (builder) => {
+ builder
+ .addCase(getList.pending, (state) => {
+ state.loading = true;
+ })
+ .addCase(getList.fulfilled, (state, action) => {
+ state.loading = false;
+ state.contractList = action.payload?.list;
+ state.total = action.payload?.total;
+ state.pageSize = action.payload?.pageSize;
+ state.current = action.payload?.current;
+ })
+ .addCase(getList.rejected, (state) => {
+ state.loading = false;
+ });
+ },
+});
+
+export const { clearPageState } = listBaseSlice.actions;
+
+export const selectListBase = (state: RootState) => state.listBase;
+
+export default listBaseSlice.reducer;
diff --git a/admin/src/modules/list/card.ts b/admin/src/modules/list/card.ts
new file mode 100644
index 0000000..30d032b
--- /dev/null
+++ b/admin/src/modules/list/card.ts
@@ -0,0 +1,73 @@
+import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
+import { RootState } from '../store';
+import { getProductList, IProduct } from 'services/product';
+
+const namespace = 'list/card';
+
+interface IInitialState {
+ pageLoading: boolean;
+ loading: boolean;
+ current: number;
+ pageSize: number;
+ total: number;
+ productList: IProduct[];
+}
+
+const initialState: IInitialState = {
+ pageLoading: true,
+ loading: true,
+ current: 1,
+ pageSize: 12,
+ total: 0,
+ productList: [],
+};
+
+export const getList = createAsyncThunk(
+ `${namespace}/getList`,
+ async (params: { pageSize: number; current: number }) => {
+ const { pageSize, current } = params;
+ const result = await getProductList({
+ pageSize,
+ current,
+ });
+ return {
+ list: result?.list,
+ total: result?.total,
+ pageSize: params.pageSize,
+ current: params.current,
+ };
+ },
+);
+
+const listCardSlice = createSlice({
+ name: namespace,
+ initialState,
+ reducers: {
+ clearPageState: () => initialState,
+ switchPageLoading: (state, action: PayloadAction) => {
+ state.pageLoading = action.payload;
+ },
+ },
+ extraReducers: (builder) => {
+ builder
+ .addCase(getList.pending, (state) => {
+ state.loading = true;
+ })
+ .addCase(getList.fulfilled, (state, action) => {
+ state.loading = false;
+ state.productList = action.payload?.list;
+ state.total = action.payload?.total;
+ state.pageSize = action.payload?.pageSize;
+ state.current = action.payload?.current;
+ })
+ .addCase(getList.rejected, (state) => {
+ state.loading = false;
+ });
+ },
+});
+
+export const { clearPageState, switchPageLoading } = listCardSlice.actions;
+
+export const selectListCard = (state: RootState) => state.listCard;
+
+export default listCardSlice.reducer;
diff --git a/admin/src/modules/list/select.ts b/admin/src/modules/list/select.ts
new file mode 100644
index 0000000..82294af
--- /dev/null
+++ b/admin/src/modules/list/select.ts
@@ -0,0 +1,64 @@
+import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
+import { RootState } from '../store';
+import { getContractList, IContract } from 'services/contract';
+
+const namespace = 'list/select';
+
+interface IInitialState {
+ loading: boolean;
+ current: number;
+ pageSize: number;
+ total: number;
+ contractList: IContract[];
+}
+
+const initialState: IInitialState = {
+ loading: true,
+ current: 1,
+ pageSize: 10,
+ total: 0,
+ contractList: [],
+};
+
+export const getList = createAsyncThunk(
+ `${namespace}/getSelectList`,
+ async (params: { pageSize: number; current: number }) => {
+ const result = await getContractList(params);
+ return {
+ list: result?.list,
+ total: result?.total,
+ pageSize: params.pageSize,
+ current: params.current,
+ };
+ },
+);
+
+const listSelectSlice = createSlice({
+ name: namespace,
+ initialState,
+ reducers: {
+ clearPageState: () => initialState,
+ },
+ extraReducers: (builder) => {
+ builder
+ .addCase(getList.pending, (state) => {
+ state.loading = true;
+ })
+ .addCase(getList.fulfilled, (state, action) => {
+ state.loading = false;
+ state.contractList = action.payload?.list;
+ state.total = action.payload?.total;
+ state.pageSize = action.payload?.pageSize;
+ state.current = action.payload?.current;
+ })
+ .addCase(getList.rejected, (state) => {
+ state.loading = false;
+ });
+ },
+});
+
+export const { clearPageState } = listSelectSlice.actions;
+
+export const selectListSelect = (state: RootState) => state.listSelect;
+
+export default listSelectSlice.reducer;
diff --git a/admin/src/modules/store.ts b/admin/src/modules/store.ts
new file mode 100644
index 0000000..c60b3d3
--- /dev/null
+++ b/admin/src/modules/store.ts
@@ -0,0 +1,28 @@
+import { configureStore, combineReducers } from '@reduxjs/toolkit';
+import { TypedUseSelectorHook, useSelector, useDispatch } from 'react-redux';
+
+import global from './global';
+import user from './user';
+import listBase from './list/base';
+import listSelect from './list/select';
+import listCard from './list/card';
+
+const reducer = combineReducers({
+ global,
+ user,
+ listBase,
+ listSelect,
+ listCard,
+});
+
+export const store = configureStore({
+ reducer,
+});
+
+export type RootState = ReturnType;
+export type AppDispatch = typeof store.dispatch;
+
+export const useAppDispatch = () => useDispatch();
+export const useAppSelector: TypedUseSelectorHook = useSelector;
+
+export default store;
diff --git a/admin/src/modules/user/index.ts b/admin/src/modules/user/index.ts
new file mode 100644
index 0000000..cd84d1c
--- /dev/null
+++ b/admin/src/modules/user/index.ts
@@ -0,0 +1,99 @@
+import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
+import { RootState } from '../store';
+
+const namespace = 'user';
+const TOKEN_NAME = 'tdesign-starter';
+
+const initialState = {
+ token: localStorage.getItem(TOKEN_NAME) || 'main_token', // 默认token不走权限
+ userInfo: {},
+};
+
+// login
+export const login = createAsyncThunk(`${namespace}/login`, async (userInfo: Record) => {
+ const mockLogin = async (userInfo: Record) => {
+ // 登录请求流程
+ console.log(userInfo);
+ // const { account, password } = userInfo;
+ // if (account !== 'td') {
+ // return {
+ // code: 401,
+ // message: '账号不存在',
+ // };
+ // }
+ // if (['main_', 'dev_'].indexOf(password) === -1) {
+ // return {
+ // code: 401,
+ // message: '密码错误',
+ // };
+ // }
+ // const token = {
+ // main_: 'main_token',
+ // dev_: 'dev_token',
+ // }[password];
+ return {
+ code: 200,
+ message: '登陆成功',
+ data: 'main_token',
+ };
+ };
+
+ const res = await mockLogin(userInfo);
+ if (res.code === 200) {
+ return res.data;
+ }
+ throw res;
+});
+
+// getUserInfo
+export const getUserInfo = createAsyncThunk(`${namespace}/getUserInfo`, async (_, { getState }: any) => {
+ const { token } = getState();
+ const mockRemoteUserInfo = async (token: string) => {
+ if (token === 'main_token') {
+ return {
+ name: 'td_main',
+ roles: ['all'],
+ };
+ }
+ return {
+ name: 'td_dev',
+ roles: ['userIndex', 'dashboardBase', 'login'],
+ };
+ };
+
+ const res = await mockRemoteUserInfo(token);
+
+ return res;
+});
+
+const userSlice = createSlice({
+ name: namespace,
+ initialState,
+ reducers: {
+ logout: (state) => {
+ localStorage.removeItem(TOKEN_NAME);
+ state.token = '';
+ state.userInfo = {};
+ },
+ remove: (state) => {
+ state.token = '';
+ },
+ },
+ extraReducers: (builder) => {
+ builder
+ .addCase(login.fulfilled, (state, action) => {
+ localStorage.setItem(TOKEN_NAME, action.payload);
+
+ state.token = action.payload;
+ })
+ .addCase(getUserInfo.fulfilled, (state, action) => {
+ state.userInfo = action.payload;
+ });
+ },
+});
+
+export const selectListBase = (state: RootState) => state.listBase;
+
+export const { logout, remove } = userSlice.actions;
+
+export default userSlice.reducer;
diff --git a/admin/src/pages/Dashboard/Base/chart.ts b/admin/src/pages/Dashboard/Base/chart.ts
new file mode 100644
index 0000000..f8caa22
--- /dev/null
+++ b/admin/src/pages/Dashboard/Base/chart.ts
@@ -0,0 +1,261 @@
+import type { EChartOption } from 'echarts';
+import { getChartDataSet, ONE_WEEK_LIST } from 'utils/chart';
+
+export const getLineChartOptions = (dateTime: Array = []): EChartOption => {
+ const [timeArray, inArray, outArray] = getChartDataSet(dateTime);
+ return {
+ tooltip: {
+ trigger: 'item',
+ },
+ grid: {
+ left: '0',
+ right: '20px',
+ top: '5px',
+ bottom: '36px',
+ containLabel: true,
+ },
+ legend: {
+ left: 'center',
+ bottom: '0',
+ orient: 'horizontal', // legend 横向布局。
+ data: ['本月', '上月'],
+ textStyle: {
+ fontSize: 12,
+ },
+ },
+ xAxis: {
+ type: 'category',
+ data: timeArray,
+ boundaryGap: false,
+ axisLine: {
+ lineStyle: {
+ color: '#E3E6EB',
+ width: 1,
+ },
+ },
+ },
+ yAxis: {
+ type: 'value',
+ },
+ series: [
+ {
+ name: '本月',
+ data: outArray,
+ type: 'line',
+ smooth: false,
+ showSymbol: true,
+ symbol: 'circle',
+ symbolSize: 8,
+ itemStyle: {
+ borderWidth: 1,
+ },
+ areaStyle: {
+ color: '#0053D92F',
+ },
+ },
+ {
+ name: '上月',
+ data: inArray,
+ type: 'line',
+ smooth: false,
+ showSymbol: true,
+ symbol: 'circle',
+ symbolSize: 8,
+ itemStyle: {
+ borderWidth: 1,
+ },
+ },
+ ],
+ };
+};
+
+export const getPieChartOptions = (radius = 42): EChartOption => ({
+ tooltip: {
+ trigger: 'item',
+ },
+ grid: {
+ top: '0',
+ right: '0',
+ },
+ legend: {
+ itemWidth: 12,
+ itemHeight: 4,
+ textStyle: {
+ fontSize: 12,
+ },
+ left: 'center',
+ bottom: '0',
+ orient: 'horizontal', // legend 横向布局。
+ },
+ series: [
+ {
+ name: '销售渠道',
+ type: 'pie',
+ radius: ['48%', '60%'],
+ avoidLabelOverlap: false,
+ silent: true,
+ itemStyle: {
+ borderWidth: 1,
+ },
+ label: {
+ show: true,
+ position: 'center',
+ formatter: ['{value|{d}%}', '{name|{b}渠道占比}'].join('\n'),
+ rich: {
+ value: {
+ fontSize: 28,
+ fontWeight: 'normal',
+ lineHeight: 46,
+ },
+ name: {
+ color: '#909399',
+ fontSize: 12,
+ lineHeight: 14,
+ },
+ },
+ },
+ labelLine: {
+ show: false,
+ },
+ data: [
+ { value: 1048, name: '线上' },
+ { value: radius * 7, name: '门店' },
+ ],
+ },
+ ],
+});
+
+export const getBarChartOptions = (dateTime: Array = []): EChartOption => {
+ const [timeArray, inArray, outArray] = getChartDataSet(dateTime);
+ return {
+ tooltip: {
+ trigger: 'item',
+ },
+ xAxis: {
+ type: 'category',
+ data: timeArray,
+ axisLine: {
+ lineStyle: {
+ width: 1,
+ },
+ },
+ },
+ yAxis: {
+ type: 'value',
+ },
+ grid: {
+ top: '5%',
+ left: '25px',
+ right: 0,
+ bottom: '60px',
+ },
+ legend: {
+ icon: 'rect',
+ itemWidth: 12,
+ itemHeight: 4,
+ itemGap: 48,
+ textStyle: {
+ fontSize: 12,
+ color: 'rgba(0, 0, 0, 0.6)',
+ },
+ left: 'center',
+ bottom: '0',
+ orient: 'horizontal',
+ data: ['本月', '上月'],
+ },
+ series: [
+ {
+ name: '本月',
+ data: outArray,
+ type: 'bar',
+ },
+ {
+ name: '上月',
+ data: inArray,
+ type: 'bar',
+ },
+ ],
+ };
+};
+
+// PieChartIcon Data
+export const MICRO_CHART_OPTIONS_LINE: EChartOption = {
+ xAxis: {
+ type: 'category',
+ show: false,
+ data: ONE_WEEK_LIST,
+ },
+ yAxis: {
+ show: false,
+ type: 'value',
+ },
+ grid: {
+ top: 0,
+ left: 0,
+ right: 0,
+ bottom: 0,
+ tooltip: {
+ show: false,
+ },
+ },
+ color: ['#fff'],
+ series: [
+ {
+ data: [150, 230, 224, 218, 135, 147, 260],
+ type: 'line',
+ showSymbol: false,
+ },
+ ],
+};
+
+// BarChartIcon Data
+export const MICRO_CHART_OPTIONS_BAR: EChartOption = {
+ xAxis: {
+ type: 'category',
+ show: false,
+ data: ONE_WEEK_LIST,
+ },
+ yAxis: {
+ show: false,
+ type: 'value',
+ },
+ grid: {
+ top: 0,
+ left: 0,
+ right: 0,
+ bottom: 0,
+ tooltip: {
+ show: false,
+ },
+ },
+ series: [
+ {
+ data: [
+ 100,
+ 130,
+ 184,
+ 218,
+ {
+ value: 135,
+ itemStyle: {
+ opacity: 0.2,
+ },
+ },
+ {
+ value: 118,
+ itemStyle: {
+ opacity: 0.2,
+ },
+ },
+ {
+ value: 60,
+ itemStyle: {
+ opacity: 0.2,
+ },
+ },
+ ],
+ type: 'bar',
+ barWidth: 9,
+ },
+ ],
+};
diff --git a/admin/src/pages/Dashboard/Base/components/MiddleChart.module.less b/admin/src/pages/Dashboard/Base/components/MiddleChart.module.less
new file mode 100644
index 0000000..0c8afcd
--- /dev/null
+++ b/admin/src/pages/Dashboard/Base/components/MiddleChart.module.less
@@ -0,0 +1,3 @@
+.middleChartPanel {
+ margin-top: 16px;
+}
diff --git a/admin/src/pages/Dashboard/Base/components/MiddleChart.tsx b/admin/src/pages/Dashboard/Base/components/MiddleChart.tsx
new file mode 100644
index 0000000..c3d4c17
--- /dev/null
+++ b/admin/src/pages/Dashboard/Base/components/MiddleChart.tsx
@@ -0,0 +1,47 @@
+import React, { useState } from 'react';
+import { Col, Row, Card } from 'tdesign-react';
+import ReactEcharts from 'echarts-for-react';
+import useDynamicChart from 'hooks/useDynamicChart';
+import LastWeekDatePicker from 'components/DatePicker';
+import { getLineChartOptions, getPieChartOptions } from '../chart';
+import Style from './MiddleChart.module.less';
+
+const lineOptions = getLineChartOptions();
+const pieOptions = getPieChartOptions();
+
+const MiddleChart = () => {
+ const [customOptions, setCustomOptions] = useState(lineOptions);
+
+ const onTimeChange = (value: Array) => {
+ const options = getLineChartOptions(value);
+ setCustomOptions(options);
+ };
+
+ const dynamicLineChartOption = useDynamicChart(customOptions, {
+ placeholderColor: ['legend.textStyle.color', 'xAxis.axisLabel.color', 'yAxis.axisLabel.color'],
+ borderColor: ['series.0.itemStyle.borderColor', 'series.1.itemStyle.borderColor'],
+ });
+
+ const dynamicPieChartOption = useDynamicChart(pieOptions, {
+ placeholderColor: ['legend.textStyle.color'],
+ containerColor: ['series.0.itemStyle.borderColor'],
+ textColor: ['label.color', 'label.color'],
+ });
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default React.memo(MiddleChart);
diff --git a/admin/src/pages/Dashboard/Base/components/Overview.module.less b/admin/src/pages/Dashboard/Base/components/Overview.module.less
new file mode 100644
index 0000000..745fe90
--- /dev/null
+++ b/admin/src/pages/Dashboard/Base/components/Overview.module.less
@@ -0,0 +1,5 @@
+.overviewPanel {
+ background-color: var(--td-bg-color-container);
+ border-radius: 3px;
+ margin-top: 16px;
+}
diff --git a/admin/src/pages/Dashboard/Base/components/Overview.tsx b/admin/src/pages/Dashboard/Base/components/Overview.tsx
new file mode 100644
index 0000000..1195048
--- /dev/null
+++ b/admin/src/pages/Dashboard/Base/components/Overview.tsx
@@ -0,0 +1,69 @@
+import React, { useState } from 'react';
+import { Button, Col, Row, Card } from 'tdesign-react';
+import ReactEcharts from 'echarts-for-react';
+import useDynamicChart from 'hooks/useDynamicChart';
+import Board, { ETrend } from 'components/Board';
+import LastWeekDatePicker from 'components/DatePicker';
+import { getBarChartOptions } from '../chart';
+import Style from './Overview.module.less';
+
+const options = getBarChartOptions();
+const Overview = (): React.ReactElement => {
+ const [customOptions, setCustomOptions] = useState(options);
+ const onTimeChange = (value: any) => {
+ // eslint-disable-next-line no-shadow
+ const options = getBarChartOptions(value);
+ setCustomOptions(options);
+ };
+
+ const dynamicChartOption = useDynamicChart(customOptions, {
+ placeholderColor: ['legend.textStyle.color', 'xAxis.axisLabel.color', 'yAxis.axisLabel.color'],
+ });
+
+ return (
+
+
+
+
+
+
+
+
+ 导出数据} bordered={false}>
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default React.memo(Overview);
diff --git a/admin/src/pages/Dashboard/Base/components/RankList.module.less b/admin/src/pages/Dashboard/Base/components/RankList.module.less
new file mode 100644
index 0000000..c214369
--- /dev/null
+++ b/admin/src/pages/Dashboard/Base/components/RankList.module.less
@@ -0,0 +1,19 @@
+.rankListPanel {
+ margin-top: 16px;
+}
+
+.rankIndex {
+ display: inline-flex;
+ width: 24px;
+ height: 24px;
+ border-radius: 50%;
+ color: #fff;
+ font-size: 14px;
+ background-color: var(--td-gray-color-5);
+ align-items: center;
+ justify-content: center;
+ font-weight: 700;
+}
+.rankIndexTop {
+ background: var(--td-brand-color);
+}
diff --git a/admin/src/pages/Dashboard/Base/components/RankList.tsx b/admin/src/pages/Dashboard/Base/components/RankList.tsx
new file mode 100644
index 0000000..e04de55
--- /dev/null
+++ b/admin/src/pages/Dashboard/Base/components/RankList.tsx
@@ -0,0 +1,133 @@
+import React from 'react';
+import { Col, Radio, Row, Table, Button, Card } from 'tdesign-react';
+import { TdPrimaryTableProps } from 'tdesign-react/es/table';
+import classnames from 'classnames';
+import { TrendIcon, ETrend } from 'components/Board';
+import { PURCHASE_TREND_LIST, SALE_TREND_LIST } from '../constant';
+import Style from './RankList.module.less';
+
+const DateRadioGroup = (
+
+ 本周
+ 近三个月
+
+);
+
+const SALE_COLUMNS: TdPrimaryTableProps['columns'] = [
+ {
+ align: 'center',
+ colKey: 'index',
+ title: '排名',
+ width: 80,
+ fixed: 'left',
+ cell: ({ rowIndex }) => (
+ {rowIndex + 1}
+ ),
+ },
+ {
+ align: 'left',
+ ellipsis: true,
+ colKey: 'productName',
+ title: '客户名称',
+ width: 200,
+ },
+ {
+ align: 'center',
+ colKey: 'growUp',
+ width: 100,
+ title: '较上周',
+ cell: ({ row }) => ,
+ },
+ {
+ align: 'center',
+ colKey: 'count',
+ title: '订单量',
+ width: 100,
+ },
+ {
+ align: 'center',
+ colKey: 'date',
+ width: 140,
+ title: '合同签订日期',
+ },
+ {
+ align: 'center',
+ colKey: 'operation',
+ fixed: 'right',
+ title: '操作',
+ width: 80,
+ cell: ({ row }) => (
+ console.log(row)}>
+ 操作
+
+ ),
+ },
+];
+
+const PURCHASE_COLUMNS: TdPrimaryTableProps['columns'] = [
+ {
+ align: 'center',
+ colKey: 'index',
+ title: '排名',
+ width: 80,
+ fixed: 'left',
+ cell: ({ rowIndex }) => (
+ {rowIndex + 1}
+ ),
+ },
+ {
+ align: 'left',
+ ellipsis: true,
+ colKey: 'productName',
+ title: '供应商名称',
+ width: 200,
+ },
+ {
+ align: 'center',
+ colKey: 'growUp',
+ width: 100,
+ title: '较上周',
+ cell: ({ row }) => ,
+ },
+ {
+ align: 'center',
+ colKey: 'count',
+ title: '订单量',
+ width: 100,
+ },
+ {
+ align: 'center',
+ colKey: 'date',
+ width: 140,
+ title: '合同签订日期',
+ },
+ {
+ align: 'center',
+ colKey: 'operation',
+ title: '操作',
+ fixed: 'right',
+ width: 80,
+ cell: ({ row }) => (
+ console.log(row)}>
+ 操作
+
+ ),
+ },
+];
+
+const RankList = () => (
+
+
+
+
+
+
+
+
+
+
+
+
+);
+
+export default React.memo(RankList);
diff --git a/admin/src/pages/Dashboard/Base/components/TopPanel.module.less b/admin/src/pages/Dashboard/Base/components/TopPanel.module.less
new file mode 100644
index 0000000..69a3cd8
--- /dev/null
+++ b/admin/src/pages/Dashboard/Base/components/TopPanel.module.less
@@ -0,0 +1,35 @@
+.iconWrap {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 56px;
+ height: 56px;
+ background: var(--td-brand-color-1);
+ border-radius: 50%;
+ position: absolute;
+ top: 32px;
+ right: 32px;
+}
+
+.svgIcon {
+ font-size: 24px;
+ color: var(--td-brand-color);
+}
+
+.paneLineChart {
+ width: 103px;
+ height: 56px;
+ overflow: hidden;
+ position: absolute;
+ top: 55px;
+ right: 32px;
+}
+.paneBarChart {
+ width: 103px;
+ height: 36px;
+ -webkit-tap-highlight-color: transparent;
+ user-select: none;
+ position: absolute;
+ top: 55px;
+ right: 32px;
+}
diff --git a/admin/src/pages/Dashboard/Base/components/TopPanel.tsx b/admin/src/pages/Dashboard/Base/components/TopPanel.tsx
new file mode 100644
index 0000000..3ead175
--- /dev/null
+++ b/admin/src/pages/Dashboard/Base/components/TopPanel.tsx
@@ -0,0 +1,92 @@
+import React, { memo } from 'react';
+import { Col, Row } from 'tdesign-react';
+import { UsergroupIcon, FileIcon } from 'tdesign-icons-react';
+import ReactEcharts from 'echarts-for-react';
+import Board, { ETrend, IBoardProps } from 'components/Board';
+import useDynamicChart from 'hooks/useDynamicChart';
+import { MICRO_CHART_OPTIONS_BAR, MICRO_CHART_OPTIONS_LINE } from '../chart';
+import Style from './TopPanel.module.less';
+
+const BarChartIcon = memo(() => {
+ const dynamicChartOption = useDynamicChart(MICRO_CHART_OPTIONS_BAR);
+ return (
+
+
+
+ );
+});
+
+const PieChartIcon = memo(() => (
+
+
+
+));
+
+const PANE_LIST: Array = [
+ {
+ title: '总收入',
+ count: '¥ 28,425.00',
+ trend: ETrend.up,
+ trendNum: '20.5%',
+ Icon: ,
+ },
+ {
+ title: '总退款',
+ count: '¥ 768.00',
+ trend: ETrend.down,
+ trendNum: '20.5%',
+ Icon: ,
+ },
+ {
+ title: '活跃用户(个)',
+ count: '1126',
+ trend: ETrend.down,
+ trendNum: '20.5%',
+ Icon: (
+
+
+
+ ),
+ },
+ {
+ title: '产生订单(个)',
+ count: '527',
+ trend: ETrend.down,
+ trendNum: '20.5%',
+ Icon: (
+
+
+
+ ),
+ },
+];
+
+const TopPanel = () => (
+
+ {PANE_LIST.map((item, index) => (
+
+
+
+ ))}
+
+);
+
+export default React.memo(TopPanel);
diff --git a/admin/src/pages/Dashboard/Base/constant.ts b/admin/src/pages/Dashboard/Base/constant.ts
new file mode 100644
index 0000000..cd7dc6a
--- /dev/null
+++ b/admin/src/pages/Dashboard/Base/constant.ts
@@ -0,0 +1,77 @@
+export const SALE_TREND_LIST = [
+ {
+ growUp: 1,
+ productName: '国家电网有限公司',
+ count: 7059,
+ date: '2021-09-01',
+ },
+ {
+ growUp: -1,
+ productName: '深圳燃气集团股份有限公司',
+ count: 6437,
+ date: '2021-09-01',
+ },
+ {
+ growUp: 4,
+ productName: '国家烟草专卖局',
+ count: 4221,
+ date: '2021-09-01',
+ },
+ {
+ growUp: 3,
+ productName: '中国电信集团有限公司',
+ count: 3317,
+ date: '2021-09-01',
+ },
+ {
+ growUp: -3,
+ productName: '中国移动通信集团有限公司',
+ count: 3015,
+ date: '2021-09-01',
+ },
+ {
+ growUp: -3,
+ productName: '新余市办公用户采购项目',
+ count: 2015,
+ date: '2021-09-12',
+ },
+];
+
+export const PURCHASE_TREND_LIST = [
+ {
+ growUp: 1,
+ productName: '腾讯科技(深圳)有限公司',
+ count: 3015,
+ date: '2021-09-01',
+ },
+ {
+ growUp: -1,
+ productName: '大润发有限公司',
+ count: 2015,
+ date: '2021-09-01',
+ },
+ {
+ growUp: 6,
+ productName: '四川海底捞股份有限公司',
+ count: 1815,
+ date: '2021-09-11',
+ },
+ {
+ growUp: -3,
+ productName: '索尼(中国)有限公司',
+ count: 1015,
+ date: '2021-09-21',
+ },
+ {
+ growUp: -4,
+ productName: '松下电器(中国)有限公司',
+ count: 445,
+ date: '2021-09-19',
+ },
+ {
+ growUp: -3,
+ productName: '新余市办公用户采购项目',
+ count: 2015,
+ date: '2021-09-12',
+ },
+];
diff --git a/admin/src/pages/Dashboard/Base/index.tsx b/admin/src/pages/Dashboard/Base/index.tsx
new file mode 100644
index 0000000..653ee98
--- /dev/null
+++ b/admin/src/pages/Dashboard/Base/index.tsx
@@ -0,0 +1,16 @@
+import React, { memo } from 'react';
+import TopPanel from './components/TopPanel';
+import MiddleChart from './components/MiddleChart';
+import RankList from './components/RankList';
+import Overview from './components/Overview';
+
+const DashBoard = () => (
+
+
+
+
+
+
+);
+
+export default memo(DashBoard);
diff --git a/admin/src/pages/Dashboard/Detail/chart.ts b/admin/src/pages/Dashboard/Detail/chart.ts
new file mode 100644
index 0000000..68b5790
--- /dev/null
+++ b/admin/src/pages/Dashboard/Detail/chart.ts
@@ -0,0 +1,203 @@
+import type { EChartOption } from 'echarts';
+
+import { ONE_WEEK_LIST, getChartDataSet, getTimeArray, getRandomInt } from 'utils/chart';
+
+// line chart options
+export const getLineChartOptions = (dateTime: Array = []): EChartOption => {
+ let dateArray: Array = ONE_WEEK_LIST;
+ if (dateTime.length > 0) {
+ const dividedNum = 7;
+ dateArray = getTimeArray(dateTime, dividedNum, 'YYYY-MM-DD');
+ }
+
+ return {
+ grid: {
+ top: '5%',
+ right: '10px',
+ left: '30px',
+ bottom: '60px',
+ },
+ legend: {
+ left: 'center',
+ bottom: '0',
+ orient: 'horizontal', // legend 横向布局。
+ data: ['杯子', '茶叶', '蜂蜜', '面粉'],
+ },
+ xAxis: {
+ type: 'category',
+ data: dateArray,
+ boundaryGap: false,
+ axisLabel: {
+ color: 'rgba(0, 0, 0, 0.4)',
+ },
+ axisLine: {
+ lineStyle: {
+ color: '#E3E6EB',
+ width: 1,
+ },
+ },
+ },
+ yAxis: {
+ type: 'value',
+ axisLabel: {
+ color: 'rgba(0, 0, 0, 0.4)',
+ },
+ },
+ tooltip: {
+ trigger: 'item',
+ },
+ series: [
+ {
+ showSymbol: true,
+ symbol: 'circle',
+ symbolSize: 8,
+ name: '杯子',
+ stack: '总量',
+ data: [
+ getRandomInt(),
+ getRandomInt(),
+ getRandomInt(),
+ getRandomInt(),
+ getRandomInt(),
+ getRandomInt(),
+ getRandomInt(),
+ ],
+ type: 'line',
+ itemStyle: {
+ borderColor: '#ffffff',
+ borderWidth: 1,
+ },
+ },
+ {
+ showSymbol: true,
+ symbol: 'circle',
+ symbolSize: 8,
+ name: '茶叶',
+ stack: '总量',
+ data: [
+ getRandomInt(),
+ getRandomInt(),
+ getRandomInt(),
+ getRandomInt(),
+ getRandomInt(),
+ getRandomInt(),
+ getRandomInt(),
+ ],
+ type: 'line',
+ itemStyle: {
+ borderColor: '#ffffff',
+ borderWidth: 1,
+ },
+ },
+ {
+ showSymbol: true,
+ symbol: 'circle',
+ symbolSize: 8,
+ name: '蜂蜜',
+ stack: '总量',
+ data: [
+ getRandomInt(),
+ getRandomInt(),
+ getRandomInt(),
+ getRandomInt(),
+ getRandomInt(),
+ getRandomInt(),
+ getRandomInt(),
+ ],
+ type: 'line',
+ itemStyle: {
+ borderColor: '#ffffff',
+ borderWidth: 1,
+ },
+ },
+ {
+ showSymbol: true,
+ symbol: 'circle',
+ symbolSize: 8,
+ name: '面粉',
+ stack: '总量',
+ data: [
+ getRandomInt(),
+ getRandomInt(),
+ getRandomInt(),
+ getRandomInt(),
+ getRandomInt(),
+ getRandomInt(),
+ getRandomInt(),
+ ],
+ type: 'line',
+ itemStyle: {
+ borderColor: '#ffffff',
+ borderWidth: 1,
+ },
+ },
+ ],
+ };
+};
+
+export const getScatterChartOptions = (dateTime: Array = []): EChartOption => {
+ const [timeArray, inArray, outArray] = getChartDataSet(dateTime);
+
+ return {
+ xAxis: {
+ data: timeArray,
+ axisLabel: {
+ color: 'rgba(0, 0, 0, 0.4)',
+ },
+ splitLine: { show: false },
+ axisLine: {
+ lineStyle: {
+ color: '#E3E6EB',
+ width: 1,
+ },
+ },
+ },
+ yAxis: {
+ type: 'value',
+ // splitLine: { show: false},
+ axisLabel: {
+ color: 'rgba(0, 0, 0, 0.4)',
+ },
+ nameTextStyle: {
+ padding: [0, 0, 0, 60],
+ },
+ axisTick: {
+ show: false,
+ },
+ axisLine: {
+ show: false,
+ },
+ },
+ tooltip: {
+ trigger: 'item',
+ },
+ grid: {
+ top: '5px',
+ left: '25px',
+ right: '5px',
+ bottom: '60px',
+ },
+ legend: {
+ left: 'center',
+ bottom: '0',
+ orient: 'horizontal', // legend 横向布局。
+ data: ['按摩仪', '咖啡机'],
+ itemHeight: 8,
+ itemWidth: 8,
+ },
+ series: [
+ {
+ name: '按摩仪',
+ symbolSize: 10,
+ data: outArray,
+ type: 'scatter',
+ },
+ {
+ name: '咖啡机',
+ symbolSize: 10,
+ data: inArray,
+ type: 'scatter',
+ },
+ ],
+ };
+};
diff --git a/admin/src/pages/Dashboard/Detail/components/MonthPurchase.tsx b/admin/src/pages/Dashboard/Detail/components/MonthPurchase.tsx
new file mode 100644
index 0000000..c00afd4
--- /dev/null
+++ b/admin/src/pages/Dashboard/Detail/components/MonthPurchase.tsx
@@ -0,0 +1,25 @@
+import React from 'react';
+import { Col, Row, Card } from 'tdesign-react';
+import Board from 'components/Board';
+import { PANE_LIST } from '../constant';
+
+const MonthPurchase = () => (
+
+
+ {PANE_LIST.map((item) => (
+
+
+
+ ))}
+
+
+);
+
+export default React.memo(MonthPurchase);
diff --git a/admin/src/pages/Dashboard/Detail/components/PurchaseThrend.module.less b/admin/src/pages/Dashboard/Detail/components/PurchaseThrend.module.less
new file mode 100644
index 0000000..834f579
--- /dev/null
+++ b/admin/src/pages/Dashboard/Detail/components/PurchaseThrend.module.less
@@ -0,0 +1,56 @@
+.purchaseTrendPanel {
+ margin-top: 16px;
+}
+
+.productTrendPanel {
+ display: flex;
+ flex-direction: column;
+ border-radius: 3px;
+ overflow: hidden;
+ cursor: pointer;
+ color: var(--td-text-color-secondary);
+ background: var(--td-bg-color-container);
+ padding: 24px 32px;
+}
+
+.productLogo {
+ width: 56px;
+ height: 56px;
+ border-radius: 50%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ background: var(--td-brand-color-1);
+ font-size: 32px;
+ color: var(--td-brand-color);
+}
+
+.productName {
+ margin: 24px 0 8px;
+ font-size: 16px;
+ font-weight: 400;
+ color: var(--td-text-color-primary);
+}
+
+.productDesc {
+ font-size: 12px;
+ line-height: 20px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ margin-bottom: 24px;
+ height: 40px;
+}
+
+.iconWrap {
+ position: relative;
+ .lightBtn {
+ position: absolute;
+ left: 22px;
+ background-color: var(--td-brand-color-2);
+ --ripple-color: var(--td-brand-color-2);
+ color: var(--td-brand-color);
+ }
+}
diff --git a/admin/src/pages/Dashboard/Detail/components/PurchaseTrend.tsx b/admin/src/pages/Dashboard/Detail/components/PurchaseTrend.tsx
new file mode 100644
index 0000000..36726a0
--- /dev/null
+++ b/admin/src/pages/Dashboard/Detail/components/PurchaseTrend.tsx
@@ -0,0 +1,101 @@
+import React, { useState } from 'react';
+import { Col, Dropdown, Row, Tag, Card, Avatar } from 'tdesign-react';
+import { Icon, AddIcon } from 'tdesign-icons-react';
+import ReactEcharts from 'echarts-for-react';
+import LastWeekDatePicker from 'components/DatePicker';
+import useDynamicChart from 'hooks/useDynamicChart';
+import { PRODUCT_LIST } from '../constant';
+import { getLineChartOptions } from '../chart';
+import Style from './PurchaseThrend.module.less';
+
+interface IProductTrendProps {
+ description: string;
+ isSetup: boolean;
+ name: string;
+ type: string;
+ icon: string;
+}
+
+const ProductTrend = ({ type, isSetup, description, name, icon }: IProductTrendProps) => (
+
+
+
+
+
+ {isSetup ? '已启用' : '已停用'}
+
+
+
{name}
+
{description}
+
+
+ {type}
+ }>
+
+ {},
+ },
+ {
+ content: '删除',
+ value: 'delete',
+ onClick: () => {},
+ },
+ ]}
+ >
+
+
+
+
+);
+
+const options = getLineChartOptions();
+
+const PurchaseTrend = () => {
+ const [customOptions, setCustomOptions] = useState(options);
+
+ const onTimeChange = (value: Array) => {
+ const options = getLineChartOptions(value);
+ setCustomOptions(options);
+ };
+
+ const dynamicChartOptions = useDynamicChart(customOptions, {
+ placeholderColor: ['legend.textStyle.color', 'xAxis.axisLabel.color', 'yAxis.axisLabel.color'],
+ });
+
+ return (
+
+
+
+
+
+
+
+
+ {PRODUCT_LIST.map((item, index) => (
+
+
+
+ ))}
+
+
+
+ );
+};
+
+export default React.memo(PurchaseTrend);
diff --git a/admin/src/pages/Dashboard/Detail/components/Satisfaction.module.less b/admin/src/pages/Dashboard/Detail/components/Satisfaction.module.less
new file mode 100644
index 0000000..6cf0dd7
--- /dev/null
+++ b/admin/src/pages/Dashboard/Detail/components/Satisfaction.module.less
@@ -0,0 +1,14 @@
+.satisfactionPanel {
+ margin-top: 16px;
+}
+
+.operation {
+ display: flex;
+}
+
+.exportBtn {
+ margin-left: 8px;
+}
+
+
+
diff --git a/admin/src/pages/Dashboard/Detail/components/Satisfaction.tsx b/admin/src/pages/Dashboard/Detail/components/Satisfaction.tsx
new file mode 100644
index 0000000..15e15eb
--- /dev/null
+++ b/admin/src/pages/Dashboard/Detail/components/Satisfaction.tsx
@@ -0,0 +1,44 @@
+import React, { useState } from 'react';
+import { Button, Card } from 'tdesign-react';
+import ReactEcharts from 'echarts-for-react';
+import LastWeekDatePicker from 'components/DatePicker';
+import useDynamicChart from 'hooks/useDynamicChart';
+import { getScatterChartOptions } from '../chart';
+import Style from './Satisfaction.module.less';
+
+const Satisfaction = () => {
+ const options = getScatterChartOptions();
+ const [customOptions, setCustomOptions] = useState(options);
+ const onTimeChange = (value: Array) => {
+ const options = getScatterChartOptions(value);
+ setCustomOptions(options);
+ };
+
+ const dynamicChartOption = useDynamicChart(customOptions, {
+ placeholderColor: ['legend.textStyle.color', 'xAxis.axisLabel.color', 'yAxis.axisLabel.color'],
+ });
+
+ return (
+
+
+ {LastWeekDatePicker(onTimeChange)}
+ 导出数据
+
+ }
+ bordered={false}
+ >
+
+
+
+ );
+};
+
+export default React.memo(Satisfaction);
diff --git a/admin/src/pages/Dashboard/Detail/constant.ts b/admin/src/pages/Dashboard/Detail/constant.ts
new file mode 100644
index 0000000..4381a4a
--- /dev/null
+++ b/admin/src/pages/Dashboard/Detail/constant.ts
@@ -0,0 +1,59 @@
+import { IBoardProps, ETrend } from 'components/Board';
+
+export const PANE_LIST: Array = [
+ {
+ title: '总申请数(次)',
+ count: '1126',
+ trendNum: '10%',
+ trend: ETrend.up,
+ },
+ {
+ title: '供应商数量(个)',
+ count: '13',
+ trendNum: '13%',
+ trend: ETrend.down,
+ },
+ {
+ title: '采购商品品类(类)',
+ count: '4',
+ trendNum: '10%',
+ trend: ETrend.up,
+ },
+ {
+ title: '申请人数量(人)',
+ count: '90',
+ trendNum: '44%',
+ trend: ETrend.down,
+ },
+ {
+ title: '申请完成率(%)',
+ count: '80.5',
+ trendNum: '70%',
+ trend: ETrend.up,
+ },
+ {
+ title: '到货及时率(%)',
+ count: '78',
+ trendNum: '16%',
+ trend: ETrend.up,
+ },
+];
+
+export const PRODUCT_LIST = [
+ {
+ description: 'SSL证书又叫服务器证书,腾讯云为您提供证书的一站式服务,包括免费、付费证书的申请、管理及部',
+ index: 1,
+ isSetup: true,
+ name: 'SSL证书',
+ type: 'D',
+ icon: 'user',
+ },
+ {
+ description: 'SSL证书又叫服务器证书,腾讯云为您提供证书的一站式服务,包括免费、付费证书的申请、管理及部',
+ index: 1,
+ isSetup: true,
+ name: 'SSL证书',
+ type: 'C',
+ icon: 'calendar',
+ },
+];
diff --git a/admin/src/pages/Dashboard/Detail/index.tsx b/admin/src/pages/Dashboard/Detail/index.tsx
new file mode 100644
index 0000000..19c893f
--- /dev/null
+++ b/admin/src/pages/Dashboard/Detail/index.tsx
@@ -0,0 +1,12 @@
+import React, { memo } from 'react';
+import MonthPurchase from './components/MonthPurchase';
+import PurchaseTrend from './components/PurchaseTrend';
+import PurchaseSatisfaction from './components/Satisfaction';
+
+export default memo(() => (
+
+));
diff --git a/admin/src/pages/Detail/Advanced/components/Base.module.less b/admin/src/pages/Detail/Advanced/components/Base.module.less
new file mode 100644
index 0000000..6876837
--- /dev/null
+++ b/admin/src/pages/Detail/Advanced/components/Base.module.less
@@ -0,0 +1,41 @@
+.infoBox {
+ column-count: 2;
+ .infoBoxItem {
+ padding: 12px 0;
+ display: flex;
+ color: var(--td-text-color-primary);
+ h1 {
+ font-weight: 400;
+ font-size: 14px;
+ color: var(--td-text-color-secondary);
+ text-align: left;
+ line-height: 22px;
+ margin: 0;
+ }
+ span {
+ margin-left: 24px;
+ font-size: 14px;
+ line-height: 1.5;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ }
+ i {
+ display: inline-block;
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background: var(--td-success-color-5);
+ margin-right: 5px;
+ }
+ .inProgress {
+ color: var(--td-success-color-5);
+ }
+ .pdf {
+ color: var(--td-brand-color);
+ &:hover {
+ cursor: pointer;
+ }
+ }
+ }
+}
diff --git a/admin/src/pages/Detail/Advanced/components/Base.tsx b/admin/src/pages/Detail/Advanced/components/Base.tsx
new file mode 100644
index 0000000..488d9c1
--- /dev/null
+++ b/admin/src/pages/Detail/Advanced/components/Base.tsx
@@ -0,0 +1,28 @@
+import React from 'react';
+import { Card } from 'tdesign-react';
+import classnames from 'classnames';
+import { dataInfo } from '../consts';
+import Style from './Base.module.less';
+
+const Base = () => (
+
+
+ {dataInfo.map((item) => (
+
+
{item.name}
+
+ {item.type === 'status' && }
+ {item.value}
+
+
+ ))}
+
+
+);
+
+export default React.memo(Base);
diff --git a/admin/src/pages/Detail/Advanced/components/Detail.tsx b/admin/src/pages/Detail/Advanced/components/Detail.tsx
new file mode 100644
index 0000000..71dc894
--- /dev/null
+++ b/admin/src/pages/Detail/Advanced/components/Detail.tsx
@@ -0,0 +1,110 @@
+import React from 'react';
+import { Button, Table, Tag, Card } from 'tdesign-react';
+import { dataBuyList, total } from '../consts';
+import Style from '../index.module.less';
+
+const Detail = () => (
+
+
+ {row.name}
+
+ {row.tag}
+
+ >
+ );
+ },
+ },
+ {
+ align: 'left',
+ width: 200,
+ ellipsis: true,
+ colKey: 'code',
+ title: '产品编号',
+ },
+ {
+ align: 'left',
+ width: 100,
+ ellipsis: true,
+ colKey: 'amount',
+ title: '采购数量',
+ cell({ row }) {
+ return row.amount;
+ },
+ },
+ {
+ align: 'left',
+ width: 200,
+ ellipsis: true,
+ colKey: 'department',
+ title: '申请部门',
+ },
+ {
+ align: 'left',
+ width: 500,
+ ellipsis: true,
+ colKey: 'createtime',
+ title: '创建时间',
+ },
+ {
+ align: 'left',
+ fixed: 'right',
+ width: 300,
+ colKey: 'op',
+ title: '操作',
+ cell() {
+ return (
+ <>
+
+ 管理
+
+
+ 删除
+
+ >
+ );
+ },
+ },
+ ]}
+ rowKey='id'
+ verticalAlign='top'
+ hover
+ pagination={{
+ defaultPageSize: 10,
+ total,
+ defaultCurrent: 1,
+ showJumper: true,
+ onChange(pageInfo) {
+ console.log(pageInfo, 'onChange pageInfo');
+ },
+ onCurrentChange(current, pageInfo) {
+ console.log(current, 'onCurrentChange current');
+ console.log(pageInfo, 'onCurrentChange pageInfo');
+ },
+ onPageSizeChange(size, pageInfo) {
+ console.log(size, 'onPageSizeChange size');
+ console.log(pageInfo, 'onPageSizeChange pageInfo');
+ },
+ }}
+ />
+
+);
+
+export default React.memo(Detail);
diff --git a/admin/src/pages/Detail/Advanced/components/Product.tsx b/admin/src/pages/Detail/Advanced/components/Product.tsx
new file mode 100644
index 0000000..4370f62
--- /dev/null
+++ b/admin/src/pages/Detail/Advanced/components/Product.tsx
@@ -0,0 +1,41 @@
+import React from 'react';
+import { Col, Row, Card } from 'tdesign-react';
+import ProductCard from './ProductCard';
+import Style from '../index.module.less';
+
+const Product = () => (
+
+
+
+);
+
+export default React.memo(Product);
diff --git a/admin/src/pages/Detail/Advanced/components/ProductCard.module.less b/admin/src/pages/Detail/Advanced/components/ProductCard.module.less
new file mode 100644
index 0000000..49c512b
--- /dev/null
+++ b/admin/src/pages/Detail/Advanced/components/ProductCard.module.less
@@ -0,0 +1,108 @@
+.productionCard {
+ position: relative;
+ background-color: var(--td-bg-color-container);
+ border: 1px solid var(--td-component-border);
+ border-radius: 3px;
+ padding: 20px 32px 24px;
+ height: 212px;
+ box-sizing: content-box;
+}
+
+.productionTitle {
+ margin-bottom: 25px;
+ position: relative;
+}
+
+.title {
+ font-weight: 500;
+ font-size: 24px;
+ color: var(--td-text-color-primary);
+}
+
+.productionIcon {
+ position: absolute;
+ top: 0px;
+ right: 0px;
+ background: var(--td-brand-color-1);
+ color: var(--td-brand-color);
+ font-size: 56px;
+ padding: 14px;
+ border-radius: 100%;
+}
+
+.tag {
+ margin-right: 4px;
+ margin-top: 8px;
+ margin-left: unset;
+ border: unset;
+}
+
+.item {
+ position: relative;
+ padding-top: 8px;
+ padding-bottom: 8px;
+}
+
+.info {
+ display: inline-block;
+ width: 60%;
+ text-align: left;
+ font-size: 14px;
+ color: var(--td-text-color-placeholder);
+}
+
+.icon {
+ position: absolute;
+ bottom: 8px;
+ right: 0;
+ color: rgba(0, 0, 0, 0.26);
+}
+
+.footer {
+ position: absolute;
+ width: 100%;
+ bottom: 0px;
+ left: 0;
+ .percent {
+ position: absolute;
+ bottom: 15px;
+ right: 32px;
+ color: var(--td-text-color-placeholder);
+ }
+ .progress {
+ margin: 0;
+ }
+}
+
+.productionAdd {
+ position: relative;
+ background-color: var(--td-bg-color-container);
+ border: 1px dashed var(--td-component-border);
+ border-radius: 3px;
+ padding: 20px 32px 24px;
+ height: 212px;
+ box-sizing: content-box;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.productionAddBtn {
+ font-size: 14px;
+ color: var(--td-text-color-secondary);
+ margin: 0 auto;
+ text-align: center;
+ line-height: 22px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ cursor: pointer;
+ .productionAddIcon {
+ background: var(--td-brand-color-1);
+ color: var(--td-brand-color);
+ font-size: 33px;
+ padding: 8px;
+ border-radius: 100%;
+ margin-bottom: 12px;
+ }
+}
diff --git a/admin/src/pages/Detail/Advanced/components/ProductCard.tsx b/admin/src/pages/Detail/Advanced/components/ProductCard.tsx
new file mode 100644
index 0000000..e9963bf
--- /dev/null
+++ b/admin/src/pages/Detail/Advanced/components/ProductCard.tsx
@@ -0,0 +1,55 @@
+import React from 'react';
+import { Progress, Tag } from 'tdesign-react';
+import { AddIcon, ChevronRightIcon, Icon } from 'tdesign-icons-react';
+import Styles from './ProductCard.module.less';
+
+interface IProps {
+ isAdd?: boolean;
+ title?: string;
+ tags?: string[];
+ desc?: string;
+ progress?: number;
+ percent?: string;
+ Icon?: string;
+ color?: string;
+ trackColor?: string;
+}
+
+const ProductCard = (props: IProps) => (
+
+ {props.isAdd ? (
+
+ ) : (
+
+
+
+
{props.title}
+
+ {props?.tags?.map((tag, index) => (
+
+ {tag}
+
+ ))}
+
+
+
+ {props.desc}
+
+
+
+
+ )}
+
+);
+
+export default React.memo(ProductCard);
diff --git a/admin/src/pages/Detail/Advanced/components/Progress.tsx b/admin/src/pages/Detail/Advanced/components/Progress.tsx
new file mode 100644
index 0000000..084201c
--- /dev/null
+++ b/admin/src/pages/Detail/Advanced/components/Progress.tsx
@@ -0,0 +1,18 @@
+import React from 'react';
+import { Steps, Card } from 'tdesign-react';
+import { dataStep, stepCurrent } from '../consts';
+import Style from '../index.module.less';
+
+const { StepItem } = Steps;
+
+const Progress = () => (
+
+
+ {dataStep.map((item) => (
+
+ ))}
+
+
+);
+
+export default React.memo(Progress);
diff --git a/admin/src/pages/Detail/Advanced/consts.ts b/admin/src/pages/Detail/Advanced/consts.ts
new file mode 100644
index 0000000..4b90042
--- /dev/null
+++ b/admin/src/pages/Detail/Advanced/consts.ts
@@ -0,0 +1,54 @@
+// Mock Data of 基本信息
+interface InfoItem {
+ id: number;
+ name: string;
+ value: string;
+ type?: string;
+}
+export const dataInfo: InfoItem[] = [
+ { id: 1, name: '合同名称', value: '总部办公用品采购项目' },
+ { id: 2, name: '合同状态', value: '履行中', type: 'status' },
+ { id: 3, name: '合同编号', value: 'BH00010' },
+ { id: 4, name: '合同类型', value: '主合同' },
+ { id: 5, name: '合同收付类型', value: '付款' },
+ { id: 6, name: '合同金额', value: '5,000,000元' },
+ { id: 7, name: '甲方', value: '腾讯科技(深圳)有限公司' },
+ { id: 8, name: '乙方', value: '欧尚' },
+ { id: 9, name: '合同签订日期', value: '2020-12-20' },
+ { id: 10, name: '合同生效日期', value: '2021-01-20' },
+ { id: 11, name: '合同结束日期', value: '2022-12-20' },
+ { id: 12, name: '合同附件', value: '总部办公用品采购项目合同.pdf', type: 'link' },
+ { id: 13, name: '备注', value: '--' },
+ { id: 14, name: '创建时间', value: '2020-12-22 10:00:00' },
+];
+
+// Mock Data of 变更记录
+interface IStepItem {
+ id: number;
+ name: string;
+ detail?: string;
+}
+export const dataStep: IStepItem[] = [
+ { id: 1, name: '申请提交', detail: '已于12月21日提交' },
+ { id: 2, name: '电子发票', detail: '预计1~3个工作日' },
+ { id: 3, name: '发票已邮寄', detail: '电子发票开出后7个工作日内联系' },
+ { id: 4, name: '完成', detail: '' },
+];
+export const stepCurrent = 2;
+
+// Mock Data of 产品采购明细
+export const total = 36;
+const listTables: any = [];
+for (let i = 0; i < total; i++) {
+ listTables.push({
+ id: i,
+ number: 'S20201228115950963',
+ name: 'Macbook ',
+ tag: '电子产品',
+ code: 'p_tmp_60a637cd0d ',
+ amount: 52,
+ department: '财务部',
+ createtime: '2021-12-30 10:43:56',
+ });
+}
+export const dataBuyList = listTables;
diff --git a/admin/src/pages/Detail/Advanced/index.module.less b/admin/src/pages/Detail/Advanced/index.module.less
new file mode 100644
index 0000000..090de63
--- /dev/null
+++ b/admin/src/pages/Detail/Advanced/index.module.less
@@ -0,0 +1,3 @@
+.cardBox {
+ margin-top: 15px;
+}
diff --git a/admin/src/pages/Detail/Advanced/index.tsx b/admin/src/pages/Detail/Advanced/index.tsx
new file mode 100644
index 0000000..5a8ed4b
--- /dev/null
+++ b/admin/src/pages/Detail/Advanced/index.tsx
@@ -0,0 +1,14 @@
+import React, { memo } from 'react';
+import Base from './components/Base';
+import ProgressComp from './components/Progress';
+import Product from './components/Product';
+import Detail from './components/Detail';
+
+export default memo(() => (
+
+));
diff --git a/admin/src/pages/Detail/Base/consts.ts b/admin/src/pages/Detail/Base/consts.ts
new file mode 100644
index 0000000..f652f7e
--- /dev/null
+++ b/admin/src/pages/Detail/Base/consts.ts
@@ -0,0 +1,22 @@
+export const dataInfo = [
+ { id: 1, name: '合同名称', value: '总部办公用品采购项目' },
+ { id: 2, name: '合同状态', value: '履行中', type: 'status' },
+ { id: 3, name: '合同编号', value: 'BH00010' },
+ { id: 4, name: '合同类型', value: '主合同' },
+ { id: 5, name: '合同收付类型', value: '付款' },
+ { id: 6, name: '合同金额', value: '5,000,000元' },
+ { id: 7, name: '甲方', value: '腾讯科技(深圳)有限公司' },
+ { id: 8, name: '乙方', value: '欧尚' },
+ { id: 9, name: '合同签订日期', value: '2020-12-20' },
+ { id: 10, name: '合同生效日期', value: '2021-01-20' },
+ { id: 11, name: '合同结束日期', value: '2022-12-20' },
+ { id: 12, name: '合同附件', value: '总部办公用品采购项目合同.pdf', type: 'link' },
+ { id: 13, name: '备注', value: '--' },
+ { id: 14, name: '创建时间', value: '2020-12-22 10:00:00' },
+];
+
+export const dataStep = [
+ { id: 1, name: '上传合同附件', detail: '这里是提示文字' },
+ { id: 2, name: '修改合同金额', detail: '这里是提示文字' },
+ { id: 3, name: '新建合同', detail: '2020-12-01 15:00:00 管理员-李川操作' },
+];
diff --git a/admin/src/pages/Detail/Base/index.module.less b/admin/src/pages/Detail/Base/index.module.less
new file mode 100644
index 0000000..aaeb89a
--- /dev/null
+++ b/admin/src/pages/Detail/Base/index.module.less
@@ -0,0 +1,57 @@
+@Wrap: infoBox;
+
+.@{Wrap} {
+ column-count: 2;
+ &Item {
+ padding: 12px 0;
+ display: flex;
+ color: var(--td-text-color-primary);
+ h1 {
+ font-weight: 400;
+ font-size: 14px;
+ color: var(--td-text-color-secondary);
+ text-align: left;
+ line-height: 22px;
+ margin: 0;
+ }
+ span {
+ margin-left: 24px;
+ font-size: 14px;
+ line-height: 1.5;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ }
+ i {
+ display: inline-block;
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background: var(--td-success-color-5);
+ margin-right: 5px;
+ }
+ .inProgress {
+ color: var(--td-success-color-5);
+ }
+ .pdf {
+ color: var(--td-brand-color);
+ &:hover {
+ cursor: pointer;
+ }
+ }
+ }
+}
+
+.logBox {
+ margin-top: 15px;
+}
+
+@media (min-width: 992px) {
+ .@{Wrap} {
+ &Item {
+ h1 {
+ width: 200px;
+ }
+ }
+ }
+}
diff --git a/admin/src/pages/Detail/Base/index.tsx b/admin/src/pages/Detail/Base/index.tsx
new file mode 100644
index 0000000..df945d7
--- /dev/null
+++ b/admin/src/pages/Detail/Base/index.tsx
@@ -0,0 +1,39 @@
+import React, { memo } from 'react';
+import { Steps, Card } from 'tdesign-react';
+import classnames from 'classnames';
+import { dataInfo, dataStep } from './consts';
+import Style from './index.module.less';
+
+const { StepItem } = Steps;
+
+export default memo(() => (
+
+
+
+ {dataInfo.map((item) => (
+
+
{item.name}
+
+ {item.type === 'status' && }
+ {item.value}
+
+
+ ))}
+
+
+
+
+
+ {dataStep.map((item) => (
+
+ ))}
+
+
+
+
+));
diff --git a/admin/src/pages/Detail/Deploy/chart.ts b/admin/src/pages/Detail/Deploy/chart.ts
new file mode 100644
index 0000000..908cc82
--- /dev/null
+++ b/admin/src/pages/Detail/Deploy/chart.ts
@@ -0,0 +1,158 @@
+import type { EChartOption } from 'echarts';
+import { getTimeArray, getRandomInt } from 'utils/chart';
+
+export function getLineOptions(dateTime: any = []): EChartOption {
+ let dateArray: Array = ['00:00', '02:00', '04:00', '06:00'];
+ if (dateTime.length > 0) {
+ const divideNum = 7;
+ dateArray = getTimeArray(dateTime, divideNum);
+ }
+
+ return {
+ tooltip: {
+ trigger: 'item',
+ },
+ grid: {
+ top: '10px',
+ left: '0',
+ right: '20px',
+ bottom: '36px',
+ containLabel: true,
+ },
+ xAxis: {
+ type: 'category',
+ data: dateArray,
+ boundaryGap: false,
+ axisLine: {
+ lineStyle: {
+ color: '#E3E6EB',
+ width: 1,
+ },
+ },
+ },
+ yAxis: {
+ type: 'value',
+ },
+ legend: {
+ data: ['本月', '上月'],
+ icon: 'circle',
+ bottom: '0',
+ itemGap: 48,
+ itemHeight: 8,
+ itemWidth: 8,
+ },
+ series: [
+ {
+ name: '上月',
+ data: [
+ getRandomInt(),
+ getRandomInt(),
+ getRandomInt(),
+ getRandomInt(),
+ getRandomInt(),
+ getRandomInt(),
+ getRandomInt(),
+ ],
+ type: 'line',
+ smooth: true,
+ showSymbol: true,
+ symbol: 'circle',
+ symbolSize: 8,
+ areaStyle: {
+ color: '#0053D92F',
+ },
+ },
+ {
+ name: '本月',
+ data: [
+ getRandomInt(),
+ getRandomInt(),
+ getRandomInt(),
+ getRandomInt(),
+ getRandomInt(),
+ getRandomInt(),
+ getRandomInt(),
+ ],
+ type: 'line',
+ smooth: true,
+ showSymbol: true,
+ symbol: 'circle',
+ symbolSize: 8,
+ },
+ ],
+ };
+}
+
+export const lastYearList: Array = [100, 120, 140, 160, 180, 200, 210];
+
+export function getBarOptions(isMonth = false): EChartOption {
+ let lastYearListCopy = lastYearList.concat([]);
+ let thisYearListCopy = lastYearList.concat([]);
+
+ if (isMonth) {
+ lastYearListCopy = lastYearListCopy.reverse();
+ thisYearListCopy = thisYearListCopy.reverse();
+ }
+
+ return {
+ tooltip: {
+ trigger: 'item',
+ },
+ grid: {
+ top: '10px',
+ left: '0',
+ right: '0',
+ bottom: '36px',
+ containLabel: true,
+ },
+ xAxis: [
+ {
+ type: 'category',
+ data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
+ axisTick: {
+ alignWithLabel: true,
+ },
+ axisLine: {
+ lineStyle: {
+ color: '#E3E6EB',
+ width: 1,
+ },
+ },
+ },
+ ],
+ yAxis: [
+ {
+ type: 'value',
+ axisLabel: {
+ color: 'rgba(0, 0, 0, 0.4)',
+ },
+ },
+ ],
+ legend: {
+ data: ['去年', '今年'],
+ bottom: '0',
+ icon: 'rect',
+ itemGap: 48,
+ itemHeight: 4,
+ itemWidth: 12,
+ // itemStyle: {},
+ },
+ series: [
+ {
+ name: '去年',
+ type: 'bar',
+ barWidth: '30%',
+ data: lastYearListCopy,
+ itemStyle: {
+ color: '#BCC4D0',
+ },
+ },
+ {
+ name: '今年',
+ type: 'bar',
+ barWidth: '30%',
+ data: thisYearListCopy,
+ },
+ ],
+ };
+}
diff --git a/admin/src/pages/Detail/Deploy/components/BottomTable.module.less b/admin/src/pages/Detail/Deploy/components/BottomTable.module.less
new file mode 100644
index 0000000..ad01ace
--- /dev/null
+++ b/admin/src/pages/Detail/Deploy/components/BottomTable.module.less
@@ -0,0 +1,10 @@
+.operationLink {
+ color: var(--td-brand-color);
+ text-decoration: none;
+ margin-right: 24px;
+ cursor: pointer;
+ transition: color 0.2s cubic-bezier(0.38, 0, 0.24, 1);
+ &:last-child {
+ margin-right: 0;
+ }
+}
diff --git a/admin/src/pages/Detail/Deploy/components/BottomTable.tsx b/admin/src/pages/Detail/Deploy/components/BottomTable.tsx
new file mode 100644
index 0000000..c384a73
--- /dev/null
+++ b/admin/src/pages/Detail/Deploy/components/BottomTable.tsx
@@ -0,0 +1,72 @@
+import React, { useEffect, useState } from 'react';
+import { Table, Card } from 'tdesign-react';
+import { TableSort, TdPrimaryTableProps } from 'tdesign-react/es/table';
+import request from 'utils/request';
+import { TABLE_COLUMNS } from '../constant';
+import ManagementPopup from './ManagementPopup';
+import Style from './BottomTable.module.less';
+
+const BottomTable = () => {
+ const [sort, setSort] = useState({ sortBy: 'name', descending: true });
+ const [visible, setVisible] = useState(false);
+ const [{ tableData }, setTableData] = useState({ tableData: [] });
+ const pagination = {
+ pageSize: 10,
+ total: tableData.length,
+ pageSizeOptions: [],
+ };
+
+ useEffect(() => {
+ request.get('/api/get-project-list').then((res: any) => {
+ if (res.code === 0) {
+ const { list = [] } = res.data;
+ setTableData({ tableData: list });
+ }
+ });
+ }, []);
+
+ const removeRow = (rowIndex: number) => {
+ console.log(' rowIndex = ', rowIndex);
+ console.log(' tableData = ', tableData);
+
+ tableData.splice(rowIndex, 1);
+ setTableData({ tableData });
+ };
+
+ const getTableColumns = (columns: TdPrimaryTableProps['columns']): TdPrimaryTableProps['columns'] => {
+ if (columns) {
+ columns[4].cell = (context) => {
+ const { rowIndex } = context;
+ return (
+ <>
+ setVisible(!visible)}>
+ 管理
+
+ removeRow(rowIndex)}>
+ 删除
+
+ >
+ );
+ };
+ }
+ return columns;
+ };
+
+ return (
+ <>
+
+ setSort(newSort)}
+ />
+
+ {visible && }
+ >
+ );
+};
+
+export default React.memo(BottomTable);
diff --git a/admin/src/pages/Detail/Deploy/components/DynamicLineChart.tsx b/admin/src/pages/Detail/Deploy/components/DynamicLineChart.tsx
new file mode 100644
index 0000000..3406e37
--- /dev/null
+++ b/admin/src/pages/Detail/Deploy/components/DynamicLineChart.tsx
@@ -0,0 +1,30 @@
+import React, { useEffect, useState } from 'react';
+import { EChartOption } from 'echarts';
+import ReactEcharts from 'echarts-for-react';
+import useDynamicChart from 'hooks/useDynamicChart';
+import { getLineOptions } from '../chart';
+
+const DynamicLineChart = () => {
+ const [lineOptions, setLineOptions] = useState(getLineOptions());
+ const dynamicLineChartOptions = useDynamicChart(lineOptions, {
+ placeholderColor: ['legend.textStyle.color', 'xAxis.axisLabel.color', 'yAxis.axisLabel.color'],
+ });
+
+ useEffect(() => {
+ const timer = setInterval(() => setLineOptions(getLineOptions()), 3000);
+ return () => {
+ clearInterval(timer);
+ };
+ }, []);
+
+ return (
+
+ );
+};
+
+export default React.memo(DynamicLineChart);
diff --git a/admin/src/pages/Detail/Deploy/components/ManagementPopup.module.less b/admin/src/pages/Detail/Deploy/components/ManagementPopup.module.less
new file mode 100644
index 0000000..4e97bae
--- /dev/null
+++ b/admin/src/pages/Detail/Deploy/components/ManagementPopup.module.less
@@ -0,0 +1,30 @@
+.popupBox {
+ max-height: 620px;
+}
+
+.popupItem {
+ padding: 12px 0;
+ display: flex;
+ height: 46px;
+ align-items: center;
+ box-sizing: border-box;
+ > h1 {
+ margin: 0;
+ width: 84px;
+ font-size: 14px;
+ color: var(--td-text-color-secondary);
+ text-align: left;
+ line-height: 22px;
+ }
+ > p {
+ margin: 0 0 0 24px;
+ }
+}
+
+.popupItem_blue {
+ color: var(--td-brand-color-8);
+}
+
+.popupItem_green {
+ color: var(--td-success-color-5);
+}
diff --git a/admin/src/pages/Detail/Deploy/components/ManagementPopup.tsx b/admin/src/pages/Detail/Deploy/components/ManagementPopup.tsx
new file mode 100644
index 0000000..6cbbaad
--- /dev/null
+++ b/admin/src/pages/Detail/Deploy/components/ManagementPopup.tsx
@@ -0,0 +1,44 @@
+import React, { useState } from 'react';
+import { Dialog } from 'tdesign-react';
+import classnames from 'classnames';
+import { BASE_INFO_DATA } from '../constant';
+import Style from './ManagementPopup.module.less';
+
+interface IProps {
+ visible: boolean;
+}
+
+const ManagementPopup = ({ visible }: IProps): React.ReactElement => {
+ const [isShow, setVisible] = useState(visible);
+ const handleConfirm = () => setVisible(!isShow);
+
+ return (
+
+
+
+ {BASE_INFO_DATA.map((item, index) => (
+
+
{item.name}
+
+ {item.value}
+
+
+ ))}
+
+
+
+ );
+};
+
+export default React.memo(ManagementPopup);
diff --git a/admin/src/pages/Detail/Deploy/components/TopChart.module.less b/admin/src/pages/Detail/Deploy/components/TopChart.module.less
new file mode 100644
index 0000000..bb16ba3
--- /dev/null
+++ b/admin/src/pages/Detail/Deploy/components/TopChart.module.less
@@ -0,0 +1,8 @@
+.panel {
+ margin-bottom: 16px;
+}
+.deployPanelLeft {
+ border-radius: 3px;
+ min-width: 230px;
+ width: 100%;
+}
diff --git a/admin/src/pages/Detail/Deploy/components/TopChart.tsx b/admin/src/pages/Detail/Deploy/components/TopChart.tsx
new file mode 100644
index 0000000..1e0f4cd
--- /dev/null
+++ b/admin/src/pages/Detail/Deploy/components/TopChart.tsx
@@ -0,0 +1,51 @@
+import React, { useState } from 'react';
+import { Col, Radio, Row, Card } from 'tdesign-react';
+import { EChartOption } from 'echarts';
+import ReactEcharts from 'echarts-for-react';
+import { getBarOptions } from '../chart';
+import useDynamicChart from 'hooks/useDynamicChart';
+import DynamicLineChart from './DynamicLineChart';
+import Style from './TopChart.module.less';
+
+const TopChart = () => {
+ const [barOptions, setBarOptions] = useState(getBarOptions());
+
+ const tabChange = (isMonth: boolean) => {
+ setBarOptions(getBarOptions(isMonth));
+ };
+ const dynamicBarChartOptions = useDynamicChart(barOptions, {
+ placeholderColor: ['legend.textStyle.color', 'xAxis.0.axisLabel.color', 'yAxis.0.axisLabel.color'],
+ });
+ return (
+
+
+
+
+
+
+
+
+
+ tabChange(val === 'month')}>
+ 本周
+ 本月
+
+ }
+ bordered={false}
+ >
+
+
+
+
+ );
+};
+
+export default React.memo(TopChart);
diff --git a/admin/src/pages/Detail/Deploy/constant.ts b/admin/src/pages/Detail/Deploy/constant.ts
new file mode 100644
index 0000000..3d3299c
--- /dev/null
+++ b/admin/src/pages/Detail/Deploy/constant.ts
@@ -0,0 +1,197 @@
+import type { TdPrimaryTableProps, SorterFun, TableRowData } from 'tdesign-react/es/table';
+
+export interface TableModel {
+ name: string;
+ adminName: string;
+ updateTime: string;
+ telephone: string | number;
+ op?: undefined | string | number;
+}
+
+const nameSorter: SorterFun = (a, b) => {
+ const result = a.name.charCodeAt(0) - b.name.charCodeAt(0);
+ if (result > 0) return 1;
+ if (result < 0) return -1;
+ return 0;
+};
+
+export const TABLE_COLUMNS: TdPrimaryTableProps['columns'] = [
+ {
+ width: '448',
+ ellipsis: true,
+ colKey: 'name',
+ title: '项目名称',
+ sortType: 'all',
+ sorter: nameSorter,
+ },
+ {
+ width: '224',
+ ellipsis: true,
+ colKey: 'adminName',
+ title: '管理员',
+ },
+ {
+ width: '224',
+ ellipsis: true,
+ colKey: 'telephone',
+ title: '联系方式',
+ },
+ {
+ width: '224',
+ className: 'test',
+ ellipsis: true,
+ colKey: 'updateTime',
+ title: '创建时间',
+ sortType: 'all',
+ sorter: true,
+ },
+ {
+ align: 'left',
+ width: '200',
+ className: 'test2',
+ ellipsis: true,
+ colKey: 'op',
+ fixed: 'right',
+ title: '操作',
+ },
+];
+
+export const POPUP_DATA = {
+ formData: {
+ name: '',
+ warning: '',
+ success: '',
+ failB: '',
+ warningB: '',
+ loading: '',
+ add: '',
+ help: '',
+ },
+ options: [
+ {
+ label: '资源初始化后',
+ value: 'beijing',
+ },
+ {
+ label: '上海',
+ value: 'shanghai',
+ },
+ ],
+ options1: [
+ {
+ label: '资源初始化后',
+ value: 'guangzhou',
+ },
+ {
+ label: '深圳',
+ value: 'shenzhen',
+ },
+ {
+ label: '东莞',
+ value: 'dongguang',
+ },
+ ],
+ options2: [
+ {
+ label: '资源初始化后',
+ value: 'nanjing',
+ },
+ {
+ label: '苏州',
+ value: '苏州',
+ },
+ {
+ label: '无锡',
+ value: 'wuxi',
+ },
+ ],
+ tSelectOptions: [
+ {
+ label: 'Sanzhang',
+ value: '1',
+ },
+ {
+ label: 'ls',
+ value: '2',
+ },
+ {
+ label: 'James',
+ value: '3',
+ },
+ ],
+};
+
+export const BASE_INFO_DATA = [
+ {
+ name: '集群名',
+ value: 'helloworld',
+ },
+ {
+ name: '集群ID',
+ value: 'cls - 2ntelvxw',
+ type: {
+ key: 'color',
+ value: 'blue',
+ },
+ },
+ {
+ name: '状态',
+ value: '运行中',
+ type: {
+ key: 'color',
+ value: 'green',
+ },
+ },
+ {
+ name: 'K8S版本',
+ value: '1.7.8',
+ },
+ {
+ name: '配置',
+ value: '6.73 核 10.30 GB',
+ },
+ {
+ name: '所在地域',
+ value: '广州',
+ },
+ {
+ name: '新增资源所属项目',
+ value: '默认项目',
+ },
+ {
+ name: '节点数量',
+ value: '4 个',
+ },
+ {
+ name: '节点网络',
+ value: 'vpc - 5frmkm1x',
+ type: {
+ key: 'color',
+ value: 'blue',
+ },
+ },
+ {
+ name: '容器网络',
+ value: '172.16.0.0 / 16',
+ },
+ {
+ name: '集群凭证',
+ value: '显示凭证',
+ type: {
+ key: 'color',
+ value: 'blue',
+ },
+ },
+ {
+ name: '创建/更新',
+ value: '2018-05-31 22:11:44 2018-05-31 22:11:44',
+ type: {
+ key: 'contractAnnex',
+ value: 'pdf',
+ },
+ },
+ {
+ name: '描述',
+ value: 'istio_test',
+ },
+];
diff --git a/admin/src/pages/Detail/Deploy/index.tsx b/admin/src/pages/Detail/Deploy/index.tsx
new file mode 100644
index 0000000..71de10d
--- /dev/null
+++ b/admin/src/pages/Detail/Deploy/index.tsx
@@ -0,0 +1,12 @@
+import React, { memo } from 'react';
+import TopChart from './components/TopChart';
+import BottomTable from './components/BottomTable';
+
+const Deploy = () => (
+
+
+
+
+);
+
+export default memo(Deploy);
diff --git a/admin/src/pages/Detail/Secondary/consts.ts b/admin/src/pages/Detail/Secondary/consts.ts
new file mode 100644
index 0000000..01a3a3d
--- /dev/null
+++ b/admin/src/pages/Detail/Secondary/consts.ts
@@ -0,0 +1,60 @@
+export type TStatus = 1 | 2; // 1: 未读 2:已读
+export interface IItem {
+ id: number;
+ content: string;
+ tag: string;
+ createTime: string;
+ status: TStatus;
+ type: number;
+}
+
+export const dataItemList: IItem[] = [
+ {
+ id: 1,
+ content: '腾讯大厦一楼改造施工项目 已通过审核!',
+ tag: '合同动态',
+ createTime: '2021-01-01 08:00',
+ status: 1,
+ type: 1,
+ },
+ {
+ id: 2,
+ content: '三季度生产原材料采购项目 开票成功!',
+ tag: '票务动态',
+ createTime: '2021-01-01 08:00',
+ status: 2,
+ type: 2,
+ },
+ {
+ id: 3,
+ content: '2021-01-01 10:00的【国家电网线下签约】会议即将开始,请提前10分钟前往 会议室1 进行签到!',
+ tag: '会议通知',
+ createTime: '2021-01-01 08:00',
+ status: 1,
+ type: 3,
+ },
+ {
+ id: 4,
+ content: '一季度生产原材料采购项目 开票成功!',
+ tag: '票务动态',
+ createTime: '2021-01-01 08:00',
+ status: 1,
+ type: 2,
+ },
+ {
+ id: 5,
+ content: '二季度生产原材料采购项目 开票成功!',
+ tag: '票务动态',
+ createTime: '2021-01-01 08:00',
+ status: 1,
+ type: 2,
+ },
+ {
+ id: 6,
+ content: '三季度生产原材料采购项目 开票成功!',
+ tag: '票务动态',
+ createTime: '2021-01-01 08:00',
+ status: 1,
+ type: 2,
+ },
+];
diff --git a/admin/src/pages/Detail/Secondary/index.module.less b/admin/src/pages/Detail/Secondary/index.module.less
new file mode 100644
index 0000000..97e7703
--- /dev/null
+++ b/admin/src/pages/Detail/Secondary/index.module.less
@@ -0,0 +1,54 @@
+.secondaryNotification {
+ background-color: var(--td-bg-color-container);
+ border-radius: 3px;
+ padding: 24px 32px;
+ .msgList {
+ height: 70vh;
+ .listItem {
+ cursor: pointer;
+ padding: 13px 24px 13px 0;
+ &:hover {
+ background-color: var(--td-bg-color-container-hover);
+ }
+ &:hover .createTime {
+ display: none;
+ }
+ .action {
+ display: none;
+ }
+ &:hover .action {
+ display: block;
+ }
+ &:after {
+ content: "";
+ position: absolute;
+ left: 0;
+ bottom: 0;
+ width: 100%;
+ height: 1px;
+ background: var(--td-border-level-1-color);
+ }
+ }
+ .content {
+ margin: 0;
+ font-size: 14px;
+ color: var(--td-text-color-placeholder);
+ text-align: left;
+ line-height: 22px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+ .tag {
+ margin-right: 8px;
+ }
+ .noData {
+ text-align: center;
+ height: 28px;
+ line-height: 28px;
+ }
+ .unread {
+ color: var(--td-brand-color-8);
+ }
+ }
+}
diff --git a/admin/src/pages/Detail/Secondary/index.tsx b/admin/src/pages/Detail/Secondary/index.tsx
new file mode 100644
index 0000000..72a48a4
--- /dev/null
+++ b/admin/src/pages/Detail/Secondary/index.tsx
@@ -0,0 +1,113 @@
+import React, { memo, useState } from 'react';
+import { Tabs, List, Tag, Row, Col, Popup } from 'tdesign-react';
+import { AddRectangleIcon, DeleteIcon, ChatIcon } from 'tdesign-icons-react';
+import classnames from 'classnames';
+import { dataItemList, IItem, TStatus } from './consts';
+import Style from './index.module.less';
+
+const { TabPanel } = Tabs;
+const { ListItem } = List;
+
+const typeMap: {
+ [key: number]: 'default' | 'primary' | 'warning' | 'danger' | 'success';
+} = {
+ 1: 'danger',
+ 2: 'primary',
+ 3: 'warning',
+};
+
+interface IMsgListProps {
+ list: IItem[];
+ onDelete: Function;
+ onUpdate: Function;
+}
+
+const MsgList = memo((props: IMsgListProps) => {
+ const { list, onUpdate, onDelete } = props;
+ return (
+
+ {list?.map((item) => (
+
+ {item.createTime}
+
+
+
+ {item.status === 1 && (
+
+ onUpdate(item, 2)} />
+
+ )}
+ {item.status === 2 && (
+
+ onUpdate(item, 1)} />
+
+ )}
+
+
+
+ onDelete(item)} />
+
+
+
+
+
+ }
+ >
+
+
+ {item.tag}
+
+ {item.content}
+
+
+ ))}
+ {list.length === 0 && 暂无数据
}
+
+ );
+});
+
+export default memo(() => {
+ const [list, setList] = useState(dataItemList);
+
+ const deleteItem = (item: IItem) => {
+ setList((value) => value.filter((val) => val.id !== item.id));
+ };
+
+ const updateStatus = (item: IItem, status: TStatus) => {
+ setList((value) => {
+ value.forEach((val) => {
+ if (val.id === item.id) {
+ val.status = status;
+ }
+ return val;
+ });
+ return [...value];
+ });
+ };
+
+ return (
+
+
+
+
+
+
+
+ item.status === 1)} onDelete={deleteItem} onUpdate={updateStatus} />
+
+
+ item.status === 2)} onDelete={deleteItem} onUpdate={updateStatus} />
+
+
+
+
+ );
+});
diff --git a/admin/src/pages/Form/Base/index.module.less b/admin/src/pages/Form/Base/index.module.less
new file mode 100644
index 0000000..6114ae9
--- /dev/null
+++ b/admin/src/pages/Form/Base/index.module.less
@@ -0,0 +1,23 @@
+.formContainer {
+ width: 676px;
+ margin: 0 auto;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background-color: var(--td-bg-color-container);
+ padding: 30px 0 134px;
+}
+
+.titleText {
+ font-size: 20px;
+ margin: 64px 0 32px;
+ color: var(--td-text-color-primary);
+}
+
+.dateCol {
+ :global {
+ .t-form__controls-content {
+ display: inline;
+ }
+ }
+}
diff --git a/admin/src/pages/Form/Base/index.tsx b/admin/src/pages/Form/Base/index.tsx
new file mode 100644
index 0000000..f7ec65d
--- /dev/null
+++ b/admin/src/pages/Form/Base/index.tsx
@@ -0,0 +1,194 @@
+import React, { memo, useRef } from 'react';
+import {
+ Form,
+ Row,
+ Col,
+ Input,
+ Radio,
+ Button,
+ DatePicker,
+ Select,
+ Textarea,
+ Avatar,
+ Upload,
+ MessagePlugin,
+} from 'tdesign-react';
+import classnames from 'classnames';
+import { SubmitContext, FormInstanceFunctions } from 'tdesign-react/es/form/type';
+import CommonStyle from 'styles/common.module.less';
+import Style from './index.module.less';
+
+const { FormItem } = Form;
+const { Option } = Select;
+const { Group } = Avatar;
+
+const INITIAL_DATA = {
+ name: '',
+ type: '',
+ payment: '',
+ partyA: '',
+ partyB: '',
+ signDate: '',
+ effectiveDate: '',
+ endDate: '',
+ remark: '',
+ notary: '',
+ file: [],
+};
+
+export default memo(() => {
+ const formRef = useRef();
+
+ const onSubmit = (e: SubmitContext) => {
+ if (e.validateResult === true) {
+ console.log('form 值', formRef.current?.getFieldsValue?.(true));
+ MessagePlugin.info('提交成功');
+ }
+ };
+
+ const handleFail = ({ file }: { file: any }) => {
+ console.error(`文件 ${file.name} 上传失败`);
+ };
+
+ return (
+
+ );
+});
diff --git a/admin/src/pages/Form/Step/components/StepFour.tsx b/admin/src/pages/Form/Step/components/StepFour.tsx
new file mode 100644
index 0000000..93d4277
--- /dev/null
+++ b/admin/src/pages/Form/Step/components/StepFour.tsx
@@ -0,0 +1,44 @@
+import React, { memo } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { CheckCircleFilledIcon } from 'tdesign-icons-react';
+import { Button } from 'tdesign-react';
+import Style from './index.module.less';
+
+export default memo((props: { callback: Function }) => {
+ const navigate = useNavigate();
+ const { callback } = props;
+
+ const onClickAgain = () => {
+ callback('first');
+ };
+
+ const onCheck = () => {
+ const url = '/detail/advanced';
+ navigate(url);
+ };
+
+ return (
+
+
+
+
完成开票申请
+
预计1~3个工作日会将电子发票发至邮箱,发票邮寄请耐心等待
+
+
+ 再次申请
+
+
+ 查看进度
+
+
+
+
+ );
+});
diff --git a/admin/src/pages/Form/Step/components/StepOne.tsx b/admin/src/pages/Form/Step/components/StepOne.tsx
new file mode 100644
index 0000000..2c1e61e
--- /dev/null
+++ b/admin/src/pages/Form/Step/components/StepOne.tsx
@@ -0,0 +1,85 @@
+import React, { memo } from 'react';
+import { Alert, Button, Form, Select } from 'tdesign-react';
+import Style from '../index.module.less';
+
+const { FormItem } = Form;
+const { Option } = Select;
+
+const message = [
+ '1、申请开票后,电子发票在1~3个工作日内开具;增值税专用发票(纸质)如资质审核通过,将在电子发票开具后10个工作日内为您寄出;',
+ '2、开票金额为您实际支付金额;',
+ '3、如有疑问请直接联系:13300001111。',
+];
+
+const names = [
+ {
+ label: '合同A',
+ value: 'A',
+ },
+ {
+ label: '合同B',
+ value: 'B',
+ },
+ {
+ label: '合同C',
+ value: 'C',
+ },
+];
+
+const types = [
+ {
+ label: '类型A',
+ value: 'A',
+ },
+ {
+ label: '类型B',
+ value: 'B',
+ },
+ {
+ label: '类型C',
+ value: 'C',
+ },
+];
+
+export default memo((props: { callback: Function }) => {
+ const { callback } = props;
+ const next = () => {
+ callback('next');
+ };
+
+ return (
+ <>
+
+
+
+
+ {names.map((item) => {
+ const { label, value } = item;
+ return ;
+ })}
+
+
+
+
+
+ {types.map((item) => {
+ const { label, value } = item;
+ return ;
+ })}
+
+
+
+
+ --
+
+
+ next()}>
+ 提交申请
+
+
+
+ >
+ );
+});
diff --git a/admin/src/pages/Form/Step/components/StepThree.tsx b/admin/src/pages/Form/Step/components/StepThree.tsx
new file mode 100644
index 0000000..f7572f0
--- /dev/null
+++ b/admin/src/pages/Form/Step/components/StepThree.tsx
@@ -0,0 +1,91 @@
+import React, { memo } from 'react';
+import { Button, Form, Select, Input, Textarea } from 'tdesign-react';
+
+const { FormItem } = Form;
+const { Option } = Select;
+
+const addressOptions = [
+ {
+ label: '广东省深圳市南山区',
+ value: '0',
+ },
+ {
+ label: '北京市海淀区',
+ value: '1',
+ },
+ {
+ label: '四川省成都市高新区',
+ value: '2',
+ },
+ {
+ label: '广东省广州市天河区',
+ value: '3',
+ },
+ {
+ label: '陕西省西安市高新区',
+ value: '4',
+ },
+];
+
+export default memo((props: { current: number; callback: Function; steps: any[] }) => {
+ const { current, callback, steps } = props;
+
+ const next = () => {
+ callback('next');
+ };
+
+ const prev = () => {
+ callback('back');
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {addressOptions.map((item: { label: string; value: string }) => {
+ const { label, value } = item;
+ return ;
+ })}
+
+
+
+
+
+
+
+
+ {current < steps.length - 1 && (
+ next()}>
+ 下一步
+
+ )}
+
+ {current > 0 && (
+ prev()}>
+ 上一步
+
+ )}
+
+
+ );
+});
diff --git a/admin/src/pages/Form/Step/components/StepTwo.tsx b/admin/src/pages/Form/Step/components/StepTwo.tsx
new file mode 100644
index 0000000..b5f35ba
--- /dev/null
+++ b/admin/src/pages/Form/Step/components/StepTwo.tsx
@@ -0,0 +1,66 @@
+import React, { memo } from 'react';
+import { Button, Form, Input } from 'tdesign-react';
+
+const { FormItem } = Form;
+
+export default memo((props: { current: number; callback: Function; steps: any[] }) => {
+ const { current, callback, steps = [] } = props;
+
+ const next = () => {
+ callback('next');
+ };
+
+ const prev = () => {
+ callback('back');
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {current < steps.length - 1 && (
+ next()}>
+ 下一步
+
+ )}
+
+ {current > 0 && (
+ prev()}>
+ 上一步
+
+ )}
+
+
+ );
+});
diff --git a/admin/src/pages/Form/Step/components/index.module.less b/admin/src/pages/Form/Step/components/index.module.less
new file mode 100644
index 0000000..f4bbc9e
--- /dev/null
+++ b/admin/src/pages/Form/Step/components/index.module.less
@@ -0,0 +1,15 @@
+.stepFourWrapper {
+ text-align: center;
+}
+
+.icon {
+ font-size: 52px;
+ color: green;
+}
+
+.title {
+ margin-top: 20px;
+ font-size: 20px;
+ // font-weight: 500;
+ color: var(--td-text-color-secondary);
+}
\ No newline at end of file
diff --git a/admin/src/pages/Form/Step/components/index.ts b/admin/src/pages/Form/Step/components/index.ts
new file mode 100644
index 0000000..b890016
--- /dev/null
+++ b/admin/src/pages/Form/Step/components/index.ts
@@ -0,0 +1,4 @@
+export { default as StepOne } from './StepOne';
+export { default as StepTwo } from './StepTwo';
+export { default as StepThree } from './StepThree';
+export { default as StepFour } from './StepFour';
diff --git a/admin/src/pages/Form/Step/index.module.less b/admin/src/pages/Form/Step/index.module.less
new file mode 100644
index 0000000..f7bca06
--- /dev/null
+++ b/admin/src/pages/Form/Step/index.module.less
@@ -0,0 +1,3 @@
+.alertBox {
+ padding: 24px 0;
+}
\ No newline at end of file
diff --git a/admin/src/pages/Form/Step/index.tsx b/admin/src/pages/Form/Step/index.tsx
new file mode 100644
index 0000000..f444a8b
--- /dev/null
+++ b/admin/src/pages/Form/Step/index.tsx
@@ -0,0 +1,79 @@
+import React, { memo } from 'react';
+import classnames from 'classnames';
+import { Steps } from 'tdesign-react';
+import { StepOne, StepTwo, StepThree, StepFour } from './components';
+import CommonStyle from 'styles/common.module.less';
+
+const { StepItem: Step } = Steps;
+interface IStep {
+ title: string;
+ content: string;
+ component: any;
+}
+
+const steps: IStep[] = [
+ {
+ title: '申请提交',
+ content: '申请提交已于12月21日提交',
+ component: StepOne,
+ },
+ {
+ title: '电子发票',
+ content: '预计1~3个工作日',
+ component: StepTwo,
+ },
+ {
+ title: '发票已邮寄',
+ content: '电子发票开出后7个工作日内联系',
+ component: StepThree,
+ },
+ {
+ title: '完成',
+ content: '',
+ component: StepFour,
+ },
+];
+
+export default memo(() => {
+ const [current, setCurrent] = React.useState(0);
+ const Comp = steps[current].component;
+
+ const next = () => {
+ setCurrent(current + 1);
+ };
+
+ const prev = () => {
+ setCurrent(current - 1);
+ };
+
+ const handleSteps = (value: string) => {
+ switch (value) {
+ case 'back':
+ prev();
+ break;
+ case 'next':
+ next();
+ break;
+ case 'first':
+ setCurrent(0);
+ break;
+ default:
+ break;
+ }
+ };
+
+ return (
+
+ <>
+
+ {steps.map((item) => (
+
+ ))}
+
+
+
+
+ >
+
+ );
+});
diff --git a/admin/src/pages/List/Base/index.module.less b/admin/src/pages/List/Base/index.module.less
new file mode 100644
index 0000000..f1f4b90
--- /dev/null
+++ b/admin/src/pages/List/Base/index.module.less
@@ -0,0 +1,4 @@
+.toolBar {
+ color: #666;
+ margin-bottom: 20px;
+}
diff --git a/admin/src/pages/List/Base/index.tsx b/admin/src/pages/List/Base/index.tsx
new file mode 100644
index 0000000..346bd36
--- /dev/null
+++ b/admin/src/pages/List/Base/index.tsx
@@ -0,0 +1,211 @@
+import React, { useState, memo, useEffect } from 'react';
+import { Table, Tag, Row, Col, Button, Input } from 'tdesign-react';
+import { SearchIcon } from 'tdesign-icons-react';
+import classnames from 'classnames';
+import { useAppDispatch, useAppSelector } from 'modules/store';
+import { selectListBase, getList, clearPageState } from 'modules/list/base';
+import CommonStyle from 'styles/common.module.less';
+import style from './index.module.less';
+import { TrendIcon, ETrend } from 'components/Board';
+
+export const PaymentTypeMap: {
+ [key: number]: React.ReactElement;
+} = {
+ 0: ,
+ 1: ,
+};
+
+export const StatusMap: {
+ [key: number]: React.ReactElement;
+} = {
+ 1: (
+
+ 待审核
+
+ ),
+ 2: (
+
+ 待履行
+
+ ),
+ 3: (
+
+ 履行中
+
+ ),
+ 4: (
+
+ 已完成
+
+ ),
+ 5: (
+
+ 审核失败
+
+ ),
+};
+
+export const ContractTypeMap: {
+ [key: number]: string;
+} = {
+ 0: '审核失败',
+ 1: '待审核',
+ 2: '待履行',
+};
+
+export default memo(() => {
+ const dispatch = useAppDispatch();
+ const pageState = useAppSelector(selectListBase);
+ const [selectedRowKeys, setSelectedRowKeys] = useState<(string | number)[]>([1, 2]);
+
+ const { loading, contractList, current, pageSize, total } = pageState;
+
+ useEffect(() => {
+ dispatch(
+ getList({
+ pageSize: pageState.pageSize,
+ current: pageState.current,
+ }),
+ );
+ return () => {
+ console.log('clear');
+ dispatch(clearPageState());
+ };
+ }, []);
+
+ function onSelectChange(value: (string | number)[]) {
+ setSelectedRowKeys(value);
+ }
+ return (
+
+
+
+
+
+ 新建合同
+
+
+ 导出合同
+
+
+ 已选 {selectedRowKeys?.length || 0} 项
+
+
+
+
+ } placeholder='请输入你需要搜索的型号' />
+
+
+
+
+
+ 管理
+
+
+ 删除
+
+ >
+ );
+ },
+ },
+ ]}
+ loading={loading}
+ data={contractList}
+ rowKey='index'
+ selectedRowKeys={selectedRowKeys}
+ verticalAlign='top'
+ hover
+ onSelectChange={onSelectChange}
+ pagination={{
+ pageSize,
+ total,
+ current,
+ showJumper: true,
+ onCurrentChange(current, pageInfo) {
+ dispatch(
+ getList({
+ pageSize: pageInfo.pageSize,
+ current: pageInfo.current,
+ }),
+ );
+ },
+ onPageSizeChange(size) {
+ dispatch(
+ getList({
+ pageSize: size,
+ current: 1,
+ }),
+ );
+ },
+ }}
+ />
+
+ );
+});
diff --git a/admin/src/pages/List/Card/components/ProductCard.module.less b/admin/src/pages/List/Card/components/ProductCard.module.less
new file mode 100644
index 0000000..d0c8fa8
--- /dev/null
+++ b/admin/src/pages/List/Card/components/ProductCard.module.less
@@ -0,0 +1,28 @@
+.panel {
+ cursor: pointer;
+}
+
+.name {
+ margin-bottom: 8px;
+ font-size: 16px;
+ font-weight: 400;
+ color: var(--td-text-color-primary);
+}
+
+.description {
+ color: var(--td-text-color-secondary);
+ font-size: 12px;
+ line-height: 20px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ height: 40px;
+}
+
+.footer {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
diff --git a/admin/src/pages/List/Card/components/ProductCard.tsx b/admin/src/pages/List/Card/components/ProductCard.tsx
new file mode 100644
index 0000000..becb452
--- /dev/null
+++ b/admin/src/pages/List/Card/components/ProductCard.tsx
@@ -0,0 +1,68 @@
+import React from 'react';
+import { Card, Avatar, Tag, Dropdown, Button } from 'tdesign-react';
+import { UserAvatarIcon, CalendarIcon, LaptopIcon, ShopIcon, ServiceIcon, Icon } from 'tdesign-icons-react';
+import { IProduct } from 'services/product';
+import Style from './ProductCard.module.less';
+
+const { Group: AvatarGroup } = Avatar;
+const icons = [UserAvatarIcon, CalendarIcon, LaptopIcon, ShopIcon, ServiceIcon];
+
+const CardIcon = React.memo(() => {
+ const random = Math.floor(Math.random() * icons.length);
+ const Icon = icons[random];
+ return ;
+});
+
+const ProductCard = ({ product }: { product: IProduct }) => {
+ const disabled = !product.isSetup;
+ return (
+
+ 已停用
+
+ ) : (
+ 已启用
+ )
+ }
+ avatar={
+
+
+
+ }
+ footer={
+
+
+ {String.fromCharCode(64 + product.type || 0)}
+ +
+
+
+
+
+
+
+
+ }
+ >
+ {product?.name}
+ {product?.description}
+
+ );
+};
+
+export default React.memo(ProductCard);
diff --git a/admin/src/pages/List/Card/index.module.less b/admin/src/pages/List/Card/index.module.less
new file mode 100644
index 0000000..02194e6
--- /dev/null
+++ b/admin/src/pages/List/Card/index.module.less
@@ -0,0 +1,21 @@
+.toolBar {
+ display: flex;
+ justify-content: space-between;
+ .search {
+ width: 342px;
+ }
+}
+.cardList {
+ margin-top: 14px;
+ margin-bottom: 24px;
+}
+
+.pagination {
+ padding: 16px;
+}
+
+.loading {
+ display: flex;
+ justify-content: center;
+ margin: 20px 0;
+}
diff --git a/admin/src/pages/List/Card/index.tsx b/admin/src/pages/List/Card/index.tsx
new file mode 100644
index 0000000..c275fea
--- /dev/null
+++ b/admin/src/pages/List/Card/index.tsx
@@ -0,0 +1,84 @@
+import React, { useEffect } from 'react';
+import { Row, Col, Button, Input, Pagination, Loading } from 'tdesign-react';
+import { SearchIcon } from 'tdesign-icons-react';
+import { useAppDispatch, useAppSelector } from 'modules/store';
+import { selectListCard, clearPageState, getList, switchPageLoading } from 'modules/list/card';
+import ProductCard from './components/ProductCard';
+import { PageInfo } from 'tdesign-react/es/pagination/type';
+import Style from './index.module.less';
+
+const CardList = () => {
+ const dispatch = useAppDispatch();
+ const pageState = useAppSelector(selectListCard);
+
+ const pageInit = async () => {
+ await dispatch(
+ getList({
+ pageSize: pageState.pageSize,
+ current: 1,
+ }),
+ );
+ await dispatch(switchPageLoading(false));
+ };
+
+ useEffect(() => {
+ pageInit();
+ return () => {
+ clearPageState();
+ };
+ }, []);
+
+ const onChange = async ({ current, pageSize }: PageInfo) => {
+ await dispatch(
+ getList({
+ pageSize,
+ current,
+ }),
+ );
+ };
+
+ const handlePageSizeChange = async (pageSize: number, { current }: PageInfo) => {
+ await dispatch(
+ getList({
+ pageSize,
+ current,
+ }),
+ );
+ };
+
+ return (
+
+
+ 新建产品
+ } placeholder='请输入你需要搜索的内容' />
+
+ {pageState.pageLoading ? (
+
+
+
+ ) : (
+ <>
+
+
+ {pageState?.productList?.map((product, index) => (
+
+
+
+ ))}
+
+
+
+ >
+ )}
+
+ );
+};
+
+export default React.memo(CardList);
diff --git a/admin/src/pages/List/Select/components/SearchForm.tsx b/admin/src/pages/List/Select/components/SearchForm.tsx
new file mode 100644
index 0000000..08eeef6
--- /dev/null
+++ b/admin/src/pages/List/Select/components/SearchForm.tsx
@@ -0,0 +1,78 @@
+import React, { useRef, memo } from 'react';
+import { Row, Col, Form, Input, Button, MessagePlugin, Select } from 'tdesign-react';
+import { CONTRACT_STATUS_OPTIONS, CONTRACT_TYPE_OPTIONS } from '../consts';
+import { FormInstanceFunctions, SubmitContext } from 'tdesign-react/es/form/type';
+
+const { FormItem } = Form;
+
+export type FormValueType = {
+ name?: string;
+ status?: string;
+ number?: string;
+ time?: string;
+ type?: string;
+};
+
+export type SearchFormProps = {
+ onCancel: () => void;
+ onSubmit: (values: FormValueType) => Promise;
+};
+
+const SearchForm: React.FC = (props) => {
+ const formRef = useRef();
+ const onSubmit = (e: SubmitContext) => {
+ if (e.validateResult === true) {
+ MessagePlugin.info('提交成功');
+ }
+ const queryValue = formRef?.current?.getFieldsValue?.(true);
+ console.log('form 数据', queryValue);
+ };
+
+ const onReset = () => {
+ props.onCancel();
+ MessagePlugin.info('重置成功');
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 查询
+
+
+ 重置
+
+
+
+
+
+ );
+};
+
+export default memo(SearchForm);
diff --git a/admin/src/pages/List/Select/consts.ts b/admin/src/pages/List/Select/consts.ts
new file mode 100644
index 0000000..feb2fe3
--- /dev/null
+++ b/admin/src/pages/List/Select/consts.ts
@@ -0,0 +1,34 @@
+interface IOption {
+ value: number | string;
+ label: string;
+}
+
+// 合同状态枚举
+export const CONTRACT_STATUS = {
+ FAIL: 0,
+ AUDIT_PENDING: 1,
+ EXEC_PENDING: 2,
+ EXECUTING: 3,
+ FINISH: 4,
+};
+
+export const CONTRACT_STATUS_OPTIONS: Array = [
+ { value: CONTRACT_STATUS.FAIL, label: '审核失败' },
+ { value: CONTRACT_STATUS.AUDIT_PENDING, label: '待审核' },
+ { value: CONTRACT_STATUS.EXEC_PENDING, label: '待履行' },
+ { value: CONTRACT_STATUS.EXECUTING, label: '审核成功' },
+ { value: CONTRACT_STATUS.FINISH, label: '已完成' },
+];
+
+// 合同类型枚举
+export const CONTRACT_TYPES = {
+ MAIN: 0,
+ SUB: 1,
+ SUPPLEMENT: 2,
+};
+
+export const CONTRACT_TYPE_OPTIONS: Array = [
+ { value: CONTRACT_TYPES.MAIN, label: '主合同' },
+ { value: CONTRACT_TYPES.SUB, label: '子合同' },
+ { value: CONTRACT_TYPES.SUPPLEMENT, label: '补充合同' },
+];
diff --git a/admin/src/pages/List/Select/index.module.less b/admin/src/pages/List/Select/index.module.less
new file mode 100644
index 0000000..94fef82
--- /dev/null
+++ b/admin/src/pages/List/Select/index.module.less
@@ -0,0 +1,12 @@
+.list-common-table-query {
+ margin-bottom: 30px;
+}
+
+.table-container {
+ margin-top: 30px;
+}
+&-btn {
+ margin-right: 10px;
+ color: #29a4fb;
+ cursor: pointer;
+}
diff --git a/admin/src/pages/List/Select/index.tsx b/admin/src/pages/List/Select/index.tsx
new file mode 100644
index 0000000..a705fa6
--- /dev/null
+++ b/admin/src/pages/List/Select/index.tsx
@@ -0,0 +1,179 @@
+import React, { useState, memo, useEffect } from 'react';
+import { Table, Dialog, Button, Row } from 'tdesign-react';
+import { useAppDispatch, useAppSelector } from 'modules/store';
+import { selectListSelect, getList, clearPageState } from 'modules/list/select';
+import SearchForm from './components/SearchForm';
+import { StatusMap, ContractTypeMap, PaymentTypeMap } from '../Base';
+
+import './index.module.less';
+import classnames from 'classnames';
+import CommonStyle from '../../../styles/common.module.less';
+
+export const SelectTable = () => {
+ const dispatch = useAppDispatch();
+ const pageState = useAppSelector(selectListSelect);
+ const [selectedRowKeys, setSelectedRowKeys] = useState<(string | number)[]>([0, 1]);
+ const [visible, setVisible] = useState(false);
+ const { loading, contractList, current, pageSize, total } = pageState;
+
+ useEffect(() => {
+ dispatch(
+ getList({
+ pageSize: pageState.pageSize,
+ current: pageState.current,
+ }),
+ );
+ return () => {
+ dispatch(clearPageState());
+ };
+ }, []);
+
+ function onSelectChange(value: (string | number)[]) {
+ setSelectedRowKeys(value);
+ }
+
+ function rehandleClickOp(record: any) {
+ console.log(record);
+ }
+
+ function handleClickDelete(record: any) {
+ console.log(record);
+ setVisible(true);
+ }
+
+ function handleClose() {
+ setVisible(false);
+ }
+
+ return (
+ <>
+
+ {
+ console.log(value);
+ }}
+ onCancel={() => {}}
+ />
+
+
+ {
+ rehandleClickOp(record);
+ }}
+ >
+ 管理
+
+ {
+ handleClickDelete(record);
+ }}
+ >
+ 删除
+
+ >
+ );
+ },
+ },
+ ]}
+ rowKey='index'
+ selectedRowKeys={selectedRowKeys}
+ hover
+ onSelectChange={onSelectChange}
+ pagination={{
+ pageSize,
+ total,
+ current,
+ showJumper: true,
+ onCurrentChange(current, pageInfo) {
+ dispatch(
+ getList({
+ pageSize: pageInfo.pageSize,
+ current: pageInfo.current,
+ }),
+ );
+ },
+ onPageSizeChange(size) {
+ dispatch(
+ getList({
+ pageSize: size,
+ current: 1,
+ }),
+ );
+ },
+ }}
+ />
+
+ 删除后的所有合同信息将被清空,且无法恢复
+
+ >
+ );
+};
+
+const selectPage: React.FC = () => (
+
+
+
+);
+
+export default memo(selectPage);
diff --git a/admin/src/pages/List/Tree/consts.ts b/admin/src/pages/List/Tree/consts.ts
new file mode 100644
index 0000000..7add260
--- /dev/null
+++ b/admin/src/pages/List/Tree/consts.ts
@@ -0,0 +1,86 @@
+export const treeList = [
+ {
+ label: '深圳总部',
+ value: 0,
+ children: [
+ {
+ label: '总办',
+ value: '0-0',
+ },
+ {
+ label: '市场部',
+ value: '0-1',
+ children: [
+ {
+ label: '采购1组',
+ value: '0-1-0',
+ },
+ {
+ label: '采购2组',
+ value: '0-1-1',
+ },
+ ],
+ },
+ {
+ label: '技术部',
+ value: '0-2',
+ },
+ ],
+ },
+ {
+ label: '北京总部',
+ value: 1,
+ children: [
+ {
+ label: '总办',
+ value: '1-0',
+ },
+ {
+ label: '市场部',
+ value: '1-1',
+ children: [
+ {
+ label: '采购1组',
+ value: '1-1-0',
+ },
+ {
+ label: '采购2组',
+ value: '1-1-1',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ label: '上海总部',
+ value: 2,
+ children: [
+ {
+ label: '市场部',
+ value: '2-0',
+ },
+ {
+ label: '财务部',
+ value: '2-1',
+ children: [
+ {
+ label: '财务1组',
+ value: '2-1-0',
+ },
+ {
+ label: '财务2组',
+ value: '2-1-1',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ label: '湖南',
+ value: 3,
+ },
+ {
+ label: '湖北',
+ value: 4,
+ },
+];
diff --git a/admin/src/pages/List/Tree/index.module.less b/admin/src/pages/List/Tree/index.module.less
new file mode 100644
index 0000000..671c323
--- /dev/null
+++ b/admin/src/pages/List/Tree/index.module.less
@@ -0,0 +1,18 @@
+.content {
+ display: flex;
+}
+
+.treeContent {
+ width: 200px;
+ padding: 30px 32px;
+ border-right: 1px solid var(--td-border-level-1-color);
+}
+
+.search {
+ width: 150px;
+}
+
+.tableContent {
+ padding: 30px 32px;
+ width: calc(100% - 200px);
+}
diff --git a/admin/src/pages/List/Tree/index.tsx b/admin/src/pages/List/Tree/index.tsx
new file mode 100644
index 0000000..e92b90f
--- /dev/null
+++ b/admin/src/pages/List/Tree/index.tsx
@@ -0,0 +1,22 @@
+import React from 'react';
+import { Input, Tree } from 'tdesign-react';
+import { SearchIcon } from 'tdesign-icons-react';
+import classnames from 'classnames';
+import { SelectTable } from '../Select';
+import { treeList } from './consts';
+import CommonStyle from 'styles/common.module.less';
+import Style from './index.module.less';
+
+const TreeTable: React.FC = () => (
+
+
+ } placeholder='请输入关键词' />
+
+
+
+
+
+
+);
+
+export default TreeTable;
diff --git a/admin/src/pages/Login/components/Header/index.module.less b/admin/src/pages/Login/components/Header/index.module.less
new file mode 100644
index 0000000..be10d78
--- /dev/null
+++ b/admin/src/pages/Login/components/Header/index.module.less
@@ -0,0 +1,37 @@
+.loginHeader {
+ height: 64px;
+ padding: 0 24px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ backdrop-filter: blur(5px);
+ color: var(--td-text-color-primary);
+}
+
+.logo {
+ width: 188px;
+ height: 64px;
+}
+
+.operationsContainer {
+ display: flex;
+ align-items: center;
+ .t-button {
+ margin-left: 16px;
+ }
+}
+
+.operationsButton {
+ margin-left: 16px;
+}
+
+.icon {
+ height: 20px;
+ width: 20px;
+ padding: 6px;
+ box-sizing: content-box;
+
+ &:hover {
+ cursor: pointer;
+ }
+}
diff --git a/admin/src/pages/Login/components/Header/index.tsx b/admin/src/pages/Login/components/Header/index.tsx
new file mode 100644
index 0000000..1a17f9d
--- /dev/null
+++ b/admin/src/pages/Login/components/Header/index.tsx
@@ -0,0 +1,61 @@
+import React from 'react';
+import { Button } from 'tdesign-react';
+import { LogoGithubIcon, HelpCircleIcon, SettingIcon } from 'tdesign-icons-react';
+import { useAppDispatch } from 'modules/store';
+import { toggleSetting } from 'modules/global';
+
+import LogoFullIcon from 'assets/svg/assets-logo-full.svg?component';
+import Style from './index.module.less';
+
+export default function Header() {
+ const dispatch = useAppDispatch();
+
+ const navToGitHub = () => {
+ window.open('https://github.com/tencent/tdesign-react-starter');
+ };
+
+ const navToHelper = () => {
+ window.open('http://tdesign.tencent.com/starter/docs/react/get-started');
+ };
+
+ const toggleSettingPanel = () => {
+ dispatch(toggleSetting());
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/admin/src/pages/Login/components/Login/index.module.less b/admin/src/pages/Login/components/Login/index.module.less
new file mode 100644
index 0000000..5a69af9
--- /dev/null
+++ b/admin/src/pages/Login/components/Login/index.module.less
@@ -0,0 +1,102 @@
+.itemContainer {
+ width: 400px;
+ margin-top: 48px;
+}
+
+.checkContainer {
+ display: flex;
+ align-items: center;
+ font-size: 14px;
+ color: var(--td-text-color-secondary);
+
+ &.rememberPwd {
+ margin-bottom: 16px;
+ justify-content: space-between;
+ }
+
+ :global {
+ .t-checkbox__label {
+ color: var(--td-text-color-secondary);
+ }
+
+ span {
+ color: var(--td-brand-color);
+
+ &:hover {
+ cursor: pointer;
+ }
+ }
+ }
+}
+
+.tipContainer {
+ width: 192px;
+ margin-bottom: 16px;
+ font-size: 14px;
+ display: flex;
+ justify-content: space-between;
+
+ :global {
+ .tip {
+ color: var(--td-text-color-primary);
+ }
+
+ .refresh {
+ display: flex;
+ align-items: center;
+ color: var(--td-brand-color);
+
+ .t-icon {
+ font-size: 14px;
+ margin-left: 4px;
+ }
+
+ &:hover {
+ cursor: pointer;
+ }
+ }
+ }
+}
+
+.checkContainerTip {
+ float: right;
+ font-size: 14px;
+ color: var(--td-brand-color);
+}
+
+.verificationBtn {
+ flex-shrink: 0;
+ width: 102px;
+ height: 40px;
+ margin-left: 11px;
+}
+
+.btnContainer {
+ margin-top: 48px;
+}
+
+.switchContainer {
+ margin-top: 24px;
+
+ :global {
+ .tip {
+ font-size: 14px;
+ color: var(--td-brand-color);
+ cursor: pointer;
+ display: inline-flex;
+ align-items: center;
+ margin-right: 14px;
+
+ &:not(:last-child) {
+ &::after {
+ content: '';
+ display: block;
+ width: 1px;
+ height: 12px;
+ background: var(--td-gray-color-3);
+ margin-left: 14px;
+ }
+ }
+ }
+ }
+}
diff --git a/admin/src/pages/Login/components/Login/index.tsx b/admin/src/pages/Login/components/Login/index.tsx
new file mode 100644
index 0000000..9527e15
--- /dev/null
+++ b/admin/src/pages/Login/components/Login/index.tsx
@@ -0,0 +1,148 @@
+import React, { useRef, useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+import {
+ Button,
+ Checkbox,
+ Form,
+ type FormInstanceFunctions,
+ Input,
+ MessagePlugin,
+ QRCode,
+ type SubmitContext,
+} from 'tdesign-react';
+import { BrowseIcon, BrowseOffIcon, LockOnIcon, RefreshIcon, UserIcon } from 'tdesign-icons-react';
+import classnames from 'classnames';
+import { useAppDispatch } from 'modules/store';
+import { login } from 'modules/user';
+import useCountdown from '../../hooks/useCountDown';
+
+import Style from './index.module.less';
+
+const { FormItem } = Form;
+
+export type ELoginType = 'password' | 'phone' | 'qrcode';
+
+export default function Login() {
+ const [loginType, changeLoginType] = useState('password');
+ const [showPsw, toggleShowPsw] = useState(false);
+ const { countdown, setupCountdown } = useCountdown(60);
+ const formRef = useRef();
+ const navigate = useNavigate();
+ const dispatch = useAppDispatch();
+
+ const onSubmit = async (e: SubmitContext) => {
+ if (e.validateResult === true) {
+ try {
+ const formValue = formRef.current?.getFieldsValue?.(true) || {};
+ await dispatch(login(formValue));
+
+ MessagePlugin.success('登录成功');
+
+ navigate('/dashboard/base');
+ } catch (e) {
+ console.log(e);
+ MessagePlugin.error('登录失败');
+ }
+ }
+ };
+
+ const switchType = (val: ELoginType) => {
+ formRef.current?.reset?.();
+ changeLoginType(val);
+ };
+
+ return (
+
+ );
+}
diff --git a/admin/src/pages/Login/components/Register/index.module.less b/admin/src/pages/Login/components/Register/index.module.less
new file mode 100644
index 0000000..f3e5380
--- /dev/null
+++ b/admin/src/pages/Login/components/Register/index.module.less
@@ -0,0 +1,36 @@
+.itemContainer {
+ width: 400px;
+ margin-top: 48px;
+}
+
+.verificationBtn {
+ flex-shrink: 0;
+ width: 102px;
+ height: 40px;
+ margin-left: 11px;
+}
+
+.switchContainer {
+ margin-top: 24px;
+}
+
+.switchTip {
+ font-size: 14px;
+ color: var(--td-brand-color);
+ cursor: pointer;
+ display: inline-flex;
+ align-items: center;
+ margin-right: 14px;
+}
+
+.checkContainer {
+ font-size: 14px;
+ color: var(--td-text-color-secondary);
+ :global {
+ .tip {
+ float: right;
+ font-size: 14px;
+ color: var(--td-brand-color);
+ }
+ }
+}
diff --git a/admin/src/pages/Login/components/Register/index.tsx b/admin/src/pages/Login/components/Register/index.tsx
new file mode 100644
index 0000000..c656bea
--- /dev/null
+++ b/admin/src/pages/Login/components/Register/index.tsx
@@ -0,0 +1,107 @@
+import React, { useState, useRef } from 'react';
+import classnames from 'classnames';
+import { Form, MessagePlugin, Input, Checkbox, Button, FormInstanceFunctions, SubmitContext } from 'tdesign-react';
+import { LockOnIcon, UserIcon, MailIcon, BrowseOffIcon, BrowseIcon } from 'tdesign-icons-react';
+import useCountdown from '../../hooks/useCountDown';
+
+import Style from './index.module.less';
+
+const { FormItem } = Form;
+
+export type ERegisterType = 'phone' | 'email';
+
+export default function Register() {
+ const [registerType, changeRegisterType] = useState('phone');
+ const [showPsw, toggleShowPsw] = useState(false);
+ const { countdown, setupCountdown } = useCountdown(60);
+ const formRef = useRef();
+
+ const onSubmit = (e: SubmitContext) => {
+ if (e.validateResult === true) {
+ const { checked } = formRef.current?.getFieldsValue?.(['checked']) as { checked: boolean };
+ if (!checked) {
+ MessagePlugin.error('请同意 TDesign 服务协议和 TDesign 隐私声明');
+ return;
+ }
+ MessagePlugin.success('注册成功');
+ }
+ };
+
+ const switchType = (val: ERegisterType) => {
+ formRef.current?.reset?.();
+ changeRegisterType(val);
+ };
+
+ return (
+
+ );
+}
diff --git a/admin/src/pages/Login/hooks/useCountDown.ts b/admin/src/pages/Login/hooks/useCountDown.ts
new file mode 100644
index 0000000..3b806d5
--- /dev/null
+++ b/admin/src/pages/Login/hooks/useCountDown.ts
@@ -0,0 +1,32 @@
+import { useState, useEffect, useRef } from 'react';
+
+const useCountdown = (duration: number) => {
+ const [countdown, setCountdown] = useState(0);
+ const [isSetup, toggleSetup] = useState(false);
+ const timer = useRef();
+
+ useEffect(() => {
+ if (isSetup) {
+ setCountdown(duration);
+ timer.current = setInterval(() => {
+ setCountdown((current) => current - 1);
+ }, 1000);
+ } else clearInterval(timer.current as NodeJS.Timeout);
+ }, [isSetup]);
+
+ useEffect(() => {
+ if (countdown === 0) {
+ toggleSetup(false);
+ }
+ }, [countdown]);
+
+ useEffect(() => () => clearInterval(timer.current as NodeJS.Timeout), []);
+
+ function setupCountdown() {
+ toggleSetup(true);
+ }
+
+ return { countdown, setupCountdown };
+};
+
+export default useCountdown;
diff --git a/admin/src/pages/Login/index.module.less b/admin/src/pages/Login/index.module.less
new file mode 100644
index 0000000..9c65ea5
--- /dev/null
+++ b/admin/src/pages/Login/index.module.less
@@ -0,0 +1,67 @@
+.loginWrapper {
+ height: 100vh;
+ display: flex;
+ flex-direction: column;
+ background-size: cover;
+ background-position: 100%;
+ position: relative;
+ &.dark {
+ background-color: var(--td-bg-color-page);
+ background-image: url('assets/image/assets-login-bg-black.png');
+ }
+ &.light {
+ background-color: white;
+ background-image: url('assets/image/assets-login-bg-white.png');
+ }
+}
+
+.loginContainer {
+ position: absolute;
+ top: 22%;
+ left: 5%;
+ min-height: 500px;
+ line-height: 22px;
+}
+
+.title {
+ font-size: 36px;
+ line-height: 44px;
+ color: var(--td-text-color-primary);
+ margin-top: 4px;
+ margin-bottom: 0;
+}
+
+.subTitle {
+ margin-top: 16px;
+}
+
+.tip {
+ display: inline-block;
+ margin-right: 8px;
+ font-size: 14px;
+ margin-top: 0;
+ margin-bottom: 0;
+
+ &.registerTip {
+ color: var(--td-text-color-secondary);
+ }
+
+ &.loginTip {
+ color: var(--td-text-color-primary);
+ cursor: pointer;
+ }
+}
+
+.copyright {
+ font-size: 14px;
+ position: absolute;
+ left: 5%;
+ bottom: 64px;
+ color: var(--td-text-color-secondary);
+}
+
+@media screen and (max-height: 762px) {
+ .copyright {
+ display: none;
+ }
+}
diff --git a/admin/src/pages/Login/index.tsx b/admin/src/pages/Login/index.tsx
new file mode 100644
index 0000000..9df335d
--- /dev/null
+++ b/admin/src/pages/Login/index.tsx
@@ -0,0 +1,44 @@
+import React, { memo, useState } from 'react';
+import classNames from 'classnames';
+import Login from './components/Login';
+import Register from './components/Register';
+import LoginHeader from './components/Header';
+import { useAppSelector } from 'modules/store';
+import { selectGlobal } from 'modules/global';
+
+import Style from './index.module.less';
+
+export default memo(() => {
+ const [type, setType] = useState('login');
+ const globalState = useAppSelector(selectGlobal);
+ const { theme } = globalState;
+ const handleSwitchLoginType = () => {
+ setType(type === 'register' ? 'login' : 'register');
+ };
+
+ return (
+
+
+
+
+
登录到
+
元境幻生管理台
+
+
+ {type === 'register' ? '已有账号?' : '没有账号吗?'}
+
+
+ {type === 'register' ? '登录' : '注册新账号'}
+
+
+
+ {type === 'login' ?
:
}
+
+
+ Copyright @ 2021-{new Date().getFullYear()} Tencent. All Rights Reserved
+
+
+ );
+});
diff --git a/admin/src/pages/Result/403/index.tsx b/admin/src/pages/Result/403/index.tsx
new file mode 100644
index 0000000..158c96c
--- /dev/null
+++ b/admin/src/pages/Result/403/index.tsx
@@ -0,0 +1,6 @@
+import React from 'react';
+import ErrorPage from 'components/ErrorPage';
+
+const UnAuthorized = () => ;
+
+export default React.memo(UnAuthorized);
diff --git a/admin/src/pages/Result/404/index.tsx b/admin/src/pages/Result/404/index.tsx
new file mode 100644
index 0000000..a43631d
--- /dev/null
+++ b/admin/src/pages/Result/404/index.tsx
@@ -0,0 +1,6 @@
+import React from 'react';
+import ErrorPage from 'components/ErrorPage';
+
+const NotFound = () => ;
+
+export default React.memo(NotFound);
diff --git a/admin/src/pages/Result/500/index.tsx b/admin/src/pages/Result/500/index.tsx
new file mode 100644
index 0000000..881b089
--- /dev/null
+++ b/admin/src/pages/Result/500/index.tsx
@@ -0,0 +1,6 @@
+import React from 'react';
+import ErrorPage from 'components/ErrorPage';
+
+const ServerError = () => ;
+
+export default React.memo(ServerError);
diff --git a/admin/src/pages/Result/BrowserIncompatible/index.module.less b/admin/src/pages/Result/BrowserIncompatible/index.module.less
new file mode 100644
index 0000000..c5babe7
--- /dev/null
+++ b/admin/src/pages/Result/BrowserIncompatible/index.module.less
@@ -0,0 +1,69 @@
+.Content {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ height: 75vh;
+ padding: 24px;
+ min-height: 400px;
+ color: var(--td-brand-color);
+
+ .title {
+ font-weight: 500;
+ font-size: 20px;
+ line-height: 28px;
+ margin-top: 8px;
+ color: var(--td-text-color-primary);
+ }
+ .description {
+ margin: 8px 0 32px;
+ font-size: 14px;
+ line-height: 22px;
+ color: var(--td-text-color-secondary);
+ }
+
+ .rightButton {
+ margin-left: 8px;
+ }
+
+ .resultSlotContainer {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: space-between;
+ color: var(--td-text-color-secondary);
+
+ .recommendContainer {
+ position: absolute;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ top: 175px;
+ padding: 24px 48px;
+ width: 640px;
+ background: var(--td-bg-color-container);
+ box-shadow: 0 1px 2px var(--td-shadow-1);
+ border-radius: 3px;
+
+ .recommendBrowser {
+ display: flex;
+
+ img {
+ width: 36.67px;
+ height: 36.67px;
+ }
+ }
+ .recommendBrowser > div {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: space-between;
+ height: 70px;
+ }
+ .recommendBrowser > div + div {
+ margin-left: 40px;
+ }
+ }
+ }
+}
diff --git a/admin/src/pages/Result/BrowserIncompatible/index.tsx b/admin/src/pages/Result/BrowserIncompatible/index.tsx
new file mode 100644
index 0000000..363e980
--- /dev/null
+++ b/admin/src/pages/Result/BrowserIncompatible/index.tsx
@@ -0,0 +1,32 @@
+import React, { memo } from 'react';
+import { Button } from 'tdesign-react';
+
+import BrowserIncompatibleIcon from 'assets/svg/assets-result-browser-incompatible.svg?component';
+import style from './index.module.less';
+
+const BrowserIncompatible = () => (
+
+
+
浏览器版本低
+
抱歉,您正在使用的浏览器版本过低,无法打开当前网页。
+
+
+
返回首页
+
+
元境幻生管理台推荐以下主流浏览器
+
+
+
+
Chrome
+
+
+
+
QQ Browser
+
+
+
+
+
+);
+
+export default memo(BrowserIncompatible);
diff --git a/admin/src/pages/Result/Fail/index.module.less b/admin/src/pages/Result/Fail/index.module.less
new file mode 100644
index 0000000..2f76aed
--- /dev/null
+++ b/admin/src/pages/Result/Fail/index.module.less
@@ -0,0 +1,30 @@
+.Content {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ height: 75vh;
+
+ .icon {
+ font-size: 72px;
+ color: var(--td-text-color-secondary);
+ }
+
+ .title {
+ font-weight: 500;
+ font-size: 20px;
+ line-height: 28px;
+ margin-top: 28px;
+ color: var(--td-text-color-primary);
+ }
+ .description {
+ margin: 8px 0 32px;
+ font-size: 14px;
+ line-height: 22px;
+ color: var(--td-text-color-secondary);
+ }
+
+ .rightButton {
+ margin-left: 8px;
+ }
+}
diff --git a/admin/src/pages/Result/Fail/index.tsx b/admin/src/pages/Result/Fail/index.tsx
new file mode 100644
index 0000000..f9666b2
--- /dev/null
+++ b/admin/src/pages/Result/Fail/index.tsx
@@ -0,0 +1,21 @@
+import React, { memo } from 'react';
+import { Button } from 'tdesign-react';
+import { ErrorCircleIcon } from 'tdesign-icons-react';
+
+import style from './index.module.less';
+
+const Fail = () => (
+
+
+
创建失败
+
抱歉,您的项目创建失败,企业微信联系检查创建者权限,或返回修改。
+
+ 返回修改
+
+ 返回首页
+
+
+
+);
+
+export default memo(Fail);
diff --git a/admin/src/pages/Result/Maintenance/index.module.less b/admin/src/pages/Result/Maintenance/index.module.less
new file mode 100644
index 0000000..1d9da3e
--- /dev/null
+++ b/admin/src/pages/Result/Maintenance/index.module.less
@@ -0,0 +1,37 @@
+.Content {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ height: 75vh;
+ padding: 24px;
+ min-height: 400px;
+ color: var(--td-brand-color);
+
+ .title {
+ font-weight: 500;
+ font-size: 20px;
+ line-height: 28px;
+ margin-top: 8px;
+ color: var(--td-text-color-primary);
+ }
+ .description {
+ margin: 8px 0 32px;
+ font-size: 14px;
+ line-height: 22px;
+ color: var(--td-text-color-secondary);
+ }
+
+ .rightButton {
+ margin-left: 8px;
+ }
+
+ .resultSlotContainer {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: space-between;
+ color: var(--td-text-color-secondary);
+ }
+}
diff --git a/admin/src/pages/Result/Maintenance/index.tsx b/admin/src/pages/Result/Maintenance/index.tsx
new file mode 100644
index 0000000..cb95dd6
--- /dev/null
+++ b/admin/src/pages/Result/Maintenance/index.tsx
@@ -0,0 +1,19 @@
+import React, { memo } from 'react';
+import { Button } from 'tdesign-react';
+
+import MaintenanceIcon from 'assets/svg/assets-result-maintenance.svg?component';
+import style from './index.module.less';
+
+const BrowserIncompatible = () => (
+
+
+
系统维护中
+
系统维护中,请稍后再试。
+
+
+ 返回首页
+
+
+);
+
+export default memo(BrowserIncompatible);
diff --git a/admin/src/pages/Result/NetworkError/index.tsx b/admin/src/pages/Result/NetworkError/index.tsx
new file mode 100644
index 0000000..601f0a7
--- /dev/null
+++ b/admin/src/pages/Result/NetworkError/index.tsx
@@ -0,0 +1,21 @@
+import React, { memo } from 'react';
+import { Button } from 'tdesign-react';
+
+import NetworkErrorIcon from 'assets/svg/assets-result-network-error.svg?component';
+import style from '../index.module.less';
+
+const NetworkError = () => (
+
+
+
网络异常
+
网络异常,请稍后再试
+
+ 重新加载
+
+ 返回首页
+
+
+
+);
+
+export default memo(NetworkError);
diff --git a/admin/src/pages/Result/Success/index.module.less b/admin/src/pages/Result/Success/index.module.less
new file mode 100644
index 0000000..e595065
--- /dev/null
+++ b/admin/src/pages/Result/Success/index.module.less
@@ -0,0 +1,30 @@
+.Content {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ height: 75vh;
+
+ .icon {
+ font-size: 72px;
+ color: var(--td-success-color);
+ }
+
+ .title {
+ font-weight: 500;
+ font-size: 20px;
+ line-height: 28px;
+ margin-top: 28px;
+ color: var(--td-text-color-primary);
+ }
+ .description {
+ margin: 8px 0 32px;
+ font-size: 14px;
+ line-height: 22px;
+ color: var(--td-text-color-secondary);
+ }
+
+ .rightButton {
+ margin-left: 8px;
+ }
+}
diff --git a/admin/src/pages/Result/Success/index.tsx b/admin/src/pages/Result/Success/index.tsx
new file mode 100644
index 0000000..354baa8
--- /dev/null
+++ b/admin/src/pages/Result/Success/index.tsx
@@ -0,0 +1,21 @@
+import React, { memo } from 'react';
+import { Button } from 'tdesign-react';
+import { CheckCircleIcon } from 'tdesign-icons-react';
+
+import style from './index.module.less';
+
+const Success = () => (
+
+
+
项目已创建成功
+
可以联系负责人分发应用
+
+ 返回首页
+
+ 查看进度
+
+
+
+);
+
+export default memo(Success);
diff --git a/admin/src/pages/Result/index.module.less b/admin/src/pages/Result/index.module.less
new file mode 100644
index 0000000..303dc10
--- /dev/null
+++ b/admin/src/pages/Result/index.module.less
@@ -0,0 +1,28 @@
+.Content {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ height: 75vh;
+ padding: 24px;
+ min-height: 400px;
+ color: var(--td-brand-color);
+
+ .title {
+ font-weight: 500;
+ font-size: 20px;
+ line-height: 28px;
+ margin-top: 8px;
+ color: var(--td-text-color-primary);
+ }
+ .description {
+ margin: 8px 0 32px;
+ font-size: 14px;
+ line-height: 22px;
+ color: var(--td-text-color-secondary);
+ }
+
+ .rightButton {
+ margin-left: 8px;
+ }
+}
diff --git a/admin/src/pages/User/chart.ts b/admin/src/pages/User/chart.ts
new file mode 100644
index 0000000..a96e692
--- /dev/null
+++ b/admin/src/pages/User/chart.ts
@@ -0,0 +1,49 @@
+export const visitData = {
+ tooltip: {
+ trigger: 'axis',
+ },
+ legend: {
+ data: ['杯子', '茶叶', '蜂蜜', '面粉'],
+ },
+ grid: {
+ left: '3%',
+ right: '4%',
+ bottom: '3%',
+ containLabel: true,
+ },
+ toolbox: {
+ feature: {
+ saveAsImage: {},
+ },
+ },
+ xAxis: {
+ type: 'category',
+ boundaryGap: false,
+ data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
+ },
+ yAxis: {
+ type: 'value',
+ },
+ series: [
+ {
+ name: '杯子',
+ type: 'line',
+ data: [21, 99, 56, 66, 55, 7, 83],
+ },
+ {
+ name: '茶叶',
+ type: 'line',
+ data: [84, 30, 70, 14, 19, 75, 73],
+ },
+ {
+ name: '蜂蜜',
+ type: 'line',
+ data: [57, 3, 25, 13, 49, 80, 11],
+ },
+ {
+ name: '面粉',
+ type: 'line',
+ data: [8, 85, 2, 77, 10, 65, 90],
+ },
+ ],
+};
diff --git a/admin/src/pages/User/consts.ts b/admin/src/pages/User/consts.ts
new file mode 100644
index 0000000..f450f45
--- /dev/null
+++ b/admin/src/pages/User/consts.ts
@@ -0,0 +1,27 @@
+// 团队成员数据
+export const TEAMS = [
+ {
+ id: 1,
+ name: 'Lovellzhong 钟某某',
+ position: '直客销售 港澳拓展组员工',
+ avatar: 'https://tdesign.gtimg.com/mobile/demos/avatar1.png',
+ },
+ {
+ id: 2,
+ name: 'Jiajingwang 彭某某',
+ position: '前端开发 前台研发组员工',
+ avatar: 'https://tdesign.gtimg.com/mobile/demos/avatar2.png',
+ },
+ {
+ id: 3,
+ name: 'Cruisezhang 林某某',
+ position: '技术产品 产品组员工',
+ avatar: 'https://tdesign.gtimg.com/mobile/demos/avatar3.png',
+ },
+ {
+ id: 4,
+ name: 'Lovellzhang 商某某',
+ position: '产品运营 港澳拓展组员工',
+ avatar: 'https://tdesign.gtimg.com/mobile/demos/avatar4.png',
+ },
+];
diff --git a/admin/src/pages/User/index.module.less b/admin/src/pages/User/index.module.less
new file mode 100644
index 0000000..12711a0
--- /dev/null
+++ b/admin/src/pages/User/index.module.less
@@ -0,0 +1,97 @@
+// 欢迎语
+.welcome {
+ .name {
+ font-size: 20px;
+ color: var(--td-text-color-primary);
+ }
+
+ .regular {
+ margin-right: 15px;
+ font-size: 14px;
+ }
+
+ .logo {
+ width: 180px;
+ }
+}
+
+// 基本信息
+.userinfo {
+ margin-top: 16px;
+ .label {
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ line-height: 24px;
+ margin: 20px 0 6px;
+ font-size: 14px;
+ color: var(--td-text-color-placeholder);
+ }
+
+ .value {
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ line-height: 40px;
+ font-size: 14px;
+ color: var(--td-text-color-primary);
+ }
+}
+
+// 统计数据
+.statistics {
+ margin-top: 16px;
+ padding-top: 20px;
+}
+
+// 职位信息
+.postmsg {
+ background: var(--td-brand-color);
+ color: #fff;
+ .avatar {
+ width: 90px;
+ height: 90px;
+ font-size: 45px;
+ position: relative;
+ display: inline-flex;
+ background: var(--td-brand-color-2);
+ color: var(--td-text-color-brand);
+ overflow: hidden;
+ white-space: nowrap;
+ vertical-align: middle;
+ justify-content: center;
+ align-items: center;
+ box-sizing: content-box;
+ border-radius: 50%;
+ }
+
+ .name {
+ line-height: 37px;
+ font-size: 20px;
+ margin-top: 36px;
+ }
+
+ .position {
+ line-height: 24px;
+ font-size: 14px;
+ margin-top: 8px;
+ }
+}
+
+// 团队
+.teams {
+ margin-top: 16px;
+ :global {
+ .t-list-item {
+ padding: 15px 0;
+ }
+ }
+}
+
+// 服务产品
+.product {
+ margin-top: 16px;
+ .logo {
+ width: 48px;
+ }
+}
diff --git a/admin/src/pages/User/index.tsx b/admin/src/pages/User/index.tsx
new file mode 100644
index 0000000..21bcaad
--- /dev/null
+++ b/admin/src/pages/User/index.tsx
@@ -0,0 +1,151 @@
+import React, { memo } from 'react';
+import { Row, Col, Button, List, Card } from 'tdesign-react';
+import { IconFont } from 'tdesign-icons-react';
+import { BrowserRouterProps } from 'react-router-dom';
+import ReactEcharts from 'echarts-for-react';
+import { TEAMS } from './consts';
+import { visitData } from './chart';
+import ProductA from 'assets/svg/assets-product-1.svg?component';
+import ProductB from 'assets/svg/assets-product-2.svg?component';
+import ProductC from 'assets/svg/assets-product-3.svg?component';
+import ProductD from 'assets/svg/assets-product-4.svg?component';
+import useDynamicChart from 'hooks/useDynamicChart';
+
+import styles from './index.module.less';
+
+const { ListItem, ListItemMeta } = List;
+
+const User: React.FC = () => {
+ const chartData = useDynamicChart(visitData, {
+ placeholderColor: ['legend.textStyle.color', 'xAxis.axisLabel.color', 'yAxis.axisLabel.color'],
+ });
+ return (
+
+
+
+
+
+
+ Hi,Image 下午好,今天是你加入鹅厂的第 100 天~
+
+
+
+
+
+
+
+
+
+ }
+ header
+ >
+
+
+ 手机
+ +86 13923734567
+
+
+ 座机
+ 734567
+
+
+ 办公室邮箱
+ Account@qq.com
+
+
+ 座位
+ T32F 012
+
+
+
+
+ 管理主体
+ 腾讯集团
+
+
+ 直属上级
+ Michael Wang
+
+
+ 职称
+ 高级 UI 设计师
+
+
+ 入职时间
+ 2021-07-01
+
+
+
+
+ 所属团队
+ 腾讯/腾讯公司/某事业群/某产品部/某运营中心/商户服务组
+
+
+
+
+
+
+
+
+
+
+ T
+
+ My Account
+ XXG 港澳业务拓展组员工 直客销售
+
+
+
+
+ }
+ >
+
+ {TEAMS.map((item) => (
+
+
+
+ ))}
+
+
+
+
+
+ }
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default memo(User);
diff --git a/admin/src/router/index.ts b/admin/src/router/index.ts
new file mode 100644
index 0000000..a0a6481
--- /dev/null
+++ b/admin/src/router/index.ts
@@ -0,0 +1,55 @@
+import React, { lazy } from 'react';
+import { BrowserRouterProps } from 'react-router-dom';
+import dashboard from './modules/dashboard';
+import list from './modules/list';
+import form from './modules/form';
+import detail from './modules/detail';
+import result from './modules/result';
+import user from './modules/user';
+import login from './modules/login';
+import otherRoutes from './modules/others';
+
+export interface IRouter {
+ path: string;
+ redirect?: string;
+ Component?: React.FC | (() => any);
+ /**
+ * 当前路由是否全屏显示
+ */
+ isFullPage?: boolean;
+ /**
+ * meta未赋值 路由不显示到菜单中
+ */
+ meta?: {
+ title?: string;
+ Icon?: React.FC;
+ /**
+ * 侧边栏隐藏该路由
+ */
+ hidden?: boolean;
+ /**
+ * 单层路由
+ */
+ single?: boolean;
+ };
+ children?: IRouter[];
+}
+
+const routes: IRouter[] = [
+ {
+ path: '/login',
+ Component: lazy(() => import('pages/Login')),
+ isFullPage: true,
+ meta: {
+ hidden: true,
+ },
+ },
+ {
+ path: '/',
+ redirect: '/dashboard/base',
+ },
+];
+
+const allRoutes = [...routes, ...dashboard, ...list, ...form, ...detail, ...result, ...user, ...login, ...otherRoutes];
+
+export default allRoutes;
diff --git a/admin/src/router/modules/dashboard.ts b/admin/src/router/modules/dashboard.ts
new file mode 100644
index 0000000..3def893
--- /dev/null
+++ b/admin/src/router/modules/dashboard.ts
@@ -0,0 +1,31 @@
+import { lazy } from 'react';
+import { DashboardIcon } from 'tdesign-icons-react';
+import { IRouter } from '../index';
+
+const dashboard: IRouter[] = [
+ {
+ path: '/dashboard',
+ meta: {
+ title: '统计报表',
+ Icon: DashboardIcon,
+ },
+ children: [
+ {
+ path: 'base',
+ Component: lazy(() => import('pages/Dashboard/Base')),
+ meta: {
+ title: '概览仪表盘',
+ },
+ },
+ {
+ path: 'detail',
+ Component: lazy(() => import('pages/Dashboard/Detail')),
+ meta: {
+ title: '统计报表',
+ },
+ },
+ ],
+ },
+];
+
+export default dashboard;
diff --git a/admin/src/router/modules/detail.ts b/admin/src/router/modules/detail.ts
new file mode 100644
index 0000000..0747bdc
--- /dev/null
+++ b/admin/src/router/modules/detail.ts
@@ -0,0 +1,39 @@
+import { lazy } from 'react';
+import { LayersIcon } from 'tdesign-icons-react';
+import { IRouter } from '../index';
+
+const result: IRouter[] = [
+ {
+ path: '/detail',
+ meta: {
+ title: '详情页',
+ Icon: LayersIcon,
+ },
+ children: [
+ {
+ path: 'base',
+ Component: lazy(() => import('pages/Detail/Base')),
+ meta: {
+ title: '基础详情页',
+ },
+ },
+ {
+ path: 'advanced',
+ Component: lazy(() => import('pages/Detail/Advanced')),
+ meta: { title: '多卡片详情页' },
+ },
+ {
+ path: 'deploy',
+ Component: lazy(() => import('pages/Detail/Deploy')),
+ meta: { title: '数据详情页' },
+ },
+ {
+ path: 'secondary',
+ Component: lazy(() => import('pages/Detail/Secondary')),
+ meta: { title: '二级详情页' },
+ },
+ ],
+ },
+];
+
+export default result;
diff --git a/admin/src/router/modules/form.ts b/admin/src/router/modules/form.ts
new file mode 100644
index 0000000..ecb15bd
--- /dev/null
+++ b/admin/src/router/modules/form.ts
@@ -0,0 +1,29 @@
+import { lazy } from 'react';
+import { QueueIcon } from 'tdesign-icons-react';
+import { IRouter } from '../index';
+
+const result: IRouter[] = [
+ {
+ path: '/form',
+ meta: {
+ title: '表单类',
+ Icon: QueueIcon,
+ },
+ children: [
+ {
+ path: 'base',
+ Component: lazy(() => import('pages/Form/Base')),
+ meta: {
+ title: '基础表单页',
+ },
+ },
+ {
+ path: 'step',
+ Component: lazy(() => import('pages/Form/Step')),
+ meta: { title: '分步表单页' },
+ },
+ ],
+ },
+];
+
+export default result;
diff --git a/admin/src/router/modules/list.ts b/admin/src/router/modules/list.ts
new file mode 100644
index 0000000..d2de15d
--- /dev/null
+++ b/admin/src/router/modules/list.ts
@@ -0,0 +1,41 @@
+import { lazy } from 'react';
+import { ViewModuleIcon } from 'tdesign-icons-react';
+import { IRouter } from '../index';
+
+const result: IRouter[] = [
+ {
+ path: '/list',
+ meta: {
+ title: '列表页',
+ Icon: ViewModuleIcon,
+ },
+ children: [
+ {
+ path: 'base',
+ Component: lazy(() => import('pages/List/Base')),
+ meta: {
+ title: '基础列表页',
+ },
+ },
+ {
+ path: 'card',
+ Component: lazy(() => import('pages/List/Card')),
+ meta: {
+ title: '卡片列表页',
+ },
+ },
+ {
+ path: 'select',
+ Component: lazy(() => import('pages/List/Select')),
+ meta: { title: '筛选列表页' },
+ },
+ {
+ path: 'tree',
+ Component: lazy(() => import('pages/List/Tree')),
+ meta: { title: '树状筛选列表页' },
+ },
+ ],
+ },
+];
+
+export default result;
diff --git a/admin/src/router/modules/login.ts b/admin/src/router/modules/login.ts
new file mode 100644
index 0000000..767cc55
--- /dev/null
+++ b/admin/src/router/modules/login.ts
@@ -0,0 +1,25 @@
+import { lazy } from 'react';
+import { LogoutIcon } from 'tdesign-icons-react';
+import { IRouter } from '../index';
+
+const result: IRouter[] = [
+ {
+ path: '/login',
+ meta: {
+ title: '登录页',
+ Icon: LogoutIcon,
+ },
+ children: [
+ {
+ path: 'index',
+ Component: lazy(() => import('pages/Login')),
+ isFullPage: true,
+ meta: {
+ title: '登录中心',
+ },
+ },
+ ],
+ },
+];
+
+export default result;
diff --git a/admin/src/router/modules/others.ts b/admin/src/router/modules/others.ts
new file mode 100644
index 0000000..ecf8398
--- /dev/null
+++ b/admin/src/router/modules/others.ts
@@ -0,0 +1,19 @@
+import { lazy } from 'react';
+import { IRouter } from '../index';
+
+const otherRoutes: IRouter[] = [
+ {
+ path: '/403',
+ Component: lazy(() => import('pages/Result/403')),
+ },
+ {
+ path: '/500',
+ Component: lazy(() => import('pages/Result/500')),
+ },
+ {
+ path: '*',
+ Component: lazy(() => import('pages/Result/404')),
+ },
+];
+
+export default otherRoutes;
diff --git a/admin/src/router/modules/result.ts b/admin/src/router/modules/result.ts
new file mode 100644
index 0000000..6c2a813
--- /dev/null
+++ b/admin/src/router/modules/result.ts
@@ -0,0 +1,73 @@
+import { lazy } from 'react';
+import { CheckCircleIcon } from 'tdesign-icons-react';
+import { IRouter } from '../index';
+
+const result: IRouter[] = [
+ {
+ path: '/result',
+ meta: {
+ title: '结果页',
+ Icon: CheckCircleIcon,
+ },
+ children: [
+ {
+ path: 'success',
+ Component: lazy(() => import('pages/Result/Success')),
+ meta: {
+ title: '成功页',
+ },
+ },
+ {
+ path: 'fail',
+ Component: lazy(() => import('pages/Result/Fail')),
+ meta: {
+ title: '失败页',
+ },
+ },
+ {
+ path: 'network-error',
+ Component: lazy(() => import('pages/Result/NetworkError')),
+ meta: {
+ title: '网络异常',
+ },
+ },
+ {
+ path: '403',
+ Component: lazy(() => import('pages/Result/403')),
+ meta: {
+ title: '无权限',
+ },
+ },
+ {
+ path: '404',
+ Component: lazy(() => import('pages/Result/404')),
+ meta: {
+ title: '访问页面不存在页',
+ },
+ },
+ {
+ path: '500',
+ Component: lazy(() => import('pages/Result/500')),
+ meta: {
+ title: '服务器出错页',
+ },
+ },
+ {
+ path: 'browser-incompatible',
+ Component: lazy(() => import('pages/Result/BrowserIncompatible')),
+ meta: {
+ title: '浏览器不兼容页',
+ },
+ },
+ {
+ path: 'maintenance',
+ Component: lazy(() => import('pages/Result/Maintenance')),
+ meta: {
+ title: '系统维护页',
+ },
+ },
+ ],
+ },
+];
+
+export default result;
diff --git a/admin/src/router/modules/user.ts b/admin/src/router/modules/user.ts
new file mode 100644
index 0000000..95f6588
--- /dev/null
+++ b/admin/src/router/modules/user.ts
@@ -0,0 +1,24 @@
+import { lazy } from 'react';
+import { UserCircleIcon } from 'tdesign-icons-react';
+import { IRouter } from '../index';
+
+const result: IRouter[] = [
+ {
+ path: '/user',
+ meta: {
+ title: '个人页',
+ Icon: UserCircleIcon,
+ },
+ children: [
+ {
+ path: 'index',
+ Component: lazy(() => import('pages/User')),
+ meta: {
+ title: '个人中心',
+ },
+ },
+ ],
+ },
+];
+
+export default result;
diff --git a/admin/src/services/contract.ts b/admin/src/services/contract.ts
new file mode 100644
index 0000000..eccb97d
--- /dev/null
+++ b/admin/src/services/contract.ts
@@ -0,0 +1,35 @@
+import request from 'utils/request';
+
+export interface IContract {
+ adminName: string;
+ amount: string;
+ contractType: number;
+ index: number;
+ name: string;
+ no: string;
+ paymentType: 1 | 2;
+ status: number;
+ updateTime: string;
+}
+
+interface IResult {
+ list: IContract[];
+}
+
+interface IParams {
+ pageSize: number;
+ current: number;
+}
+
+export const getContractList = async (params: IParams) => {
+ const result = await request.get('/api/get-list');
+
+ // 模拟接口分页
+ let list = result?.data?.list || [];
+ const total = list.length;
+ list = list.splice(params.pageSize * (params.current - 1), params.pageSize);
+ return {
+ list,
+ total,
+ };
+};
diff --git a/admin/src/services/product.ts b/admin/src/services/product.ts
new file mode 100644
index 0000000..4b4c4c1
--- /dev/null
+++ b/admin/src/services/product.ts
@@ -0,0 +1,32 @@
+import request from 'utils/request';
+
+export interface IProduct {
+ banner: string;
+ description: string;
+ index: number;
+ isSetup: boolean;
+ name: string;
+ type: number;
+}
+
+interface IResult {
+ list: IProduct[];
+}
+
+interface IParams {
+ pageSize: number;
+ current: number;
+}
+
+export const getProductList = async (params: IParams) => {
+ const result = await request.get('api/get-card-list');
+
+ // 模拟接口分页
+ let list = result?.data?.list || [];
+ const total = list.length;
+ list = list.splice(params.pageSize * (params.current - 1), params.pageSize);
+ return {
+ list,
+ total,
+ };
+};
diff --git a/admin/src/styles/common.module.less b/admin/src/styles/common.module.less
new file mode 100644
index 0000000..140ff85
--- /dev/null
+++ b/admin/src/styles/common.module.less
@@ -0,0 +1,9 @@
+.pageWithPadding {
+ padding: 30px 32px;
+}
+
+.pageWithColor {
+ background: var(--td-bg-color-container);
+ box-shadow: 0 2px 3px rgba(0, 0, 0, 0.1);
+ border-radius: 3px;
+}
diff --git a/admin/src/styles/index.less b/admin/src/styles/index.less
new file mode 100644
index 0000000..08df376
--- /dev/null
+++ b/admin/src/styles/index.less
@@ -0,0 +1,17 @@
+body {
+ margin: 0;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans',
+ 'Droid Sans', 'Helvetica Neue', sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+html,
+body,
+#app {
+ height: 100%;
+}
+
+code {
+ font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
+}
diff --git a/admin/src/types/index.d.ts b/admin/src/types/index.d.ts
new file mode 100644
index 0000000..b6a872b
--- /dev/null
+++ b/admin/src/types/index.d.ts
@@ -0,0 +1,4 @@
+export enum ETheme {
+ light = 'light',
+ dark = 'dark',
+}
diff --git a/admin/src/utils/chart.ts b/admin/src/utils/chart.ts
new file mode 100644
index 0000000..c8949b6
--- /dev/null
+++ b/admin/src/utils/chart.ts
@@ -0,0 +1,38 @@
+// 获取 chart 的 mock 数据
+import dayjs, { Dayjs } from 'dayjs';
+
+const RECENT_7_DAYS: [Dayjs, Dayjs] = [dayjs().subtract(7, 'day'), dayjs().subtract(1, 'day')];
+export const ONE_WEEK_LIST = ['周一', '周二', '周三', '周四', '周五', '周六', '周日'];
+
+export const getRandomInt = (num = 100): number => {
+ const resultNum = Number((Math.random() * num).toFixed(0));
+ return resultNum <= 1 ? 1 : resultNum;
+};
+
+type ChartValue = number | string;
+
+export function getTimeArray(dateTime: string[] = [], divideNum = 10, format = 'MM-DD'): string[] {
+ const timeArray = [];
+ if (dateTime.length === 0) {
+ dateTime.push(...RECENT_7_DAYS.map((item) => item.format(format)));
+ }
+ for (let i = 0; i < divideNum; i++) {
+ const dateAbsTime: number = (new Date(dateTime[1]).getTime() - new Date(dateTime[0]).getTime()) / divideNum;
+ const timeNode: number = new Date(dateTime[0]).getTime() + dateAbsTime * i;
+ timeArray.push(dayjs(timeNode).format(format));
+ }
+
+ return timeArray;
+}
+
+export const getChartDataSet = (dateTime: Array = [], divideNum = 10): ChartValue[][] => {
+ const timeArray = getTimeArray(dateTime, divideNum);
+ const inArray = [];
+ const outArray = [];
+ for (let index = 0; index < divideNum; index++) {
+ inArray.push(getRandomInt().toString());
+ outArray.push(getRandomInt().toString());
+ }
+
+ return [timeArray, inArray, outArray];
+};
diff --git a/admin/src/utils/color.ts b/admin/src/utils/color.ts
new file mode 100644
index 0000000..95b98c3
--- /dev/null
+++ b/admin/src/utils/color.ts
@@ -0,0 +1,99 @@
+import { Color } from 'tvision-color';
+import { defaultColor, darkColor, CHART_COLORS } from 'configs/color';
+import { ETheme } from 'types/index.d';
+
+/**
+ * 依据主题颜色获取 ColorList
+ * @param theme
+ * @param themeColor
+ */
+function getColorFromThemeColor(theme: string, themeColor: string): Array {
+ let themeColorList = [];
+ const isDarkMode = theme === ETheme.dark;
+ const colorLowerCase = themeColor.toLocaleLowerCase();
+
+ if (defaultColor.includes(colorLowerCase)) {
+ const colorIdx = defaultColor.indexOf(colorLowerCase);
+ const defaultGradients = !isDarkMode ? defaultColor : darkColor;
+ const spliceThemeList = defaultGradients.slice(0, colorIdx);
+ themeColorList = defaultGradients.slice(colorIdx, defaultGradients.length).concat(spliceThemeList);
+ } else {
+ themeColorList = Color.getRandomPalette({
+ color: themeColor,
+ colorGamut: 'bright',
+ number: 8,
+ });
+ }
+
+ return themeColorList;
+}
+
+/**
+ *
+ * @param theme 当前主题
+ * @param themeColor 当前主题色
+ */
+export function getChartColor(theme: ETheme, themeColor: string) {
+ const colorList = getColorFromThemeColor(theme, themeColor);
+ // 图表颜色
+ const chartColors = CHART_COLORS[theme];
+ return { ...chartColors, colorList };
+}
+
+export function generateColorMap(
+ theme: string,
+ colorPalette: Array,
+ mode: 'light' | 'dark',
+ brandColorIdx: number,
+) {
+ const isDarkMode = mode === 'dark';
+
+ if (isDarkMode) {
+ // eslint-disable-next-line no-use-before-define
+ colorPalette.reverse().map((color) => {
+ const [h, s, l] = Color.colorTransform(color, 'hex', 'hsl');
+ return Color.colorTransform([h, Number(s) - 4, l], 'hsl', 'hex');
+ });
+ // eslint-disable-next-line no-param-reassign
+ brandColorIdx = 10 - brandColorIdx;
+ colorPalette[0] = `${colorPalette[brandColorIdx]}20`;
+ }
+
+ const colorMap = {
+ '--td-brand-color': colorPalette[brandColorIdx], // 主题色
+ '--td-brand-color-1': colorPalette[0], // light
+ '--td-brand-color-2': colorPalette[1], // focus
+ '--td-brand-color-3': colorPalette[2], // disabled
+ '--td-brand-color-4': colorPalette[3],
+ '--td-brand-color-5': colorPalette[4],
+ '--td-brand-color-6': colorPalette[5],
+ '--td-brand-color-7': brandColorIdx > 0 ? colorPalette[brandColorIdx - 1] : theme, // hover
+ '--td-brand-color-8': colorPalette[brandColorIdx], // 主题色
+ '--td-brand-color-9': brandColorIdx > 8 ? theme : colorPalette[brandColorIdx + 1], // click
+ '--td-brand-color-10': colorPalette[9],
+ };
+ return colorMap;
+}
+
+export function insertThemeStylesheet(theme: string, colorMap: Record, mode: 'light' | 'dark') {
+ const isDarkMode = mode === 'dark';
+ const root = !isDarkMode ? `:root[theme-color='${theme}']` : `:root[theme-color='${theme}'][theme-mode='dark']`;
+
+ const styleSheet = document.createElement('style');
+ styleSheet.type = 'text/css';
+ styleSheet.innerText = `${root}{
+ --td-brand-color: ${colorMap['--td-brand-color']};
+ --td-brand-color-1: ${colorMap['--td-brand-color-1']};
+ --td-brand-color-2: ${colorMap['--td-brand-color-2']};
+ --td-brand-color-3: ${colorMap['--td-brand-color-3']};
+ --td-brand-color-4: ${colorMap['--td-brand-color-4']};
+ --td-brand-color-5: ${colorMap['--td-brand-color-5']};
+ --td-brand-color-6: ${colorMap['--td-brand-color-6']};
+ --td-brand-color-7: ${colorMap['--td-brand-color-7']};
+ --td-brand-color-8: ${colorMap['--td-brand-color-8']};
+ --td-brand-color-9: ${colorMap['--td-brand-color-9']};
+ --td-brand-color-10: ${colorMap['--td-brand-color-10']};
+ }`;
+
+ document.head.appendChild(styleSheet);
+}
diff --git a/admin/src/utils/path.ts b/admin/src/utils/path.ts
new file mode 100644
index 0000000..b0f56ed
--- /dev/null
+++ b/admin/src/utils/path.ts
@@ -0,0 +1,12 @@
+/**
+ * 解析当前菜单对应的路由
+ * @param path1
+ * @param path2
+ */
+export const resolve = (path1 = '', path2 = '') => {
+ let separator = '/';
+ if (path1.endsWith('/') || path2.startsWith('/')) {
+ separator = '';
+ }
+ return `${path1}${separator}${path2}`;
+};
diff --git a/admin/src/utils/request.ts b/admin/src/utils/request.ts
new file mode 100644
index 0000000..6088250
--- /dev/null
+++ b/admin/src/utils/request.ts
@@ -0,0 +1,31 @@
+import axios from 'axios';
+import proxy from '../configs/host';
+
+const env = import.meta.env.MODE || 'development';
+const API_HOST = proxy[env].API;
+
+const SUCCESS_CODE = 0;
+const TIMEOUT = 5000;
+
+export const instance = axios.create({
+ baseURL: API_HOST,
+ timeout: TIMEOUT,
+ withCredentials: true,
+});
+
+instance.interceptors.response.use(
+ // eslint-disable-next-line consistent-return
+ (response) => {
+ if (response.status === 200) {
+ const { data } = response;
+ if (data.code === SUCCESS_CODE) {
+ return data;
+ }
+ return Promise.reject(data);
+ }
+ return Promise.reject(response?.data);
+ },
+ (e) => Promise.reject(e),
+);
+
+export default instance;
diff --git a/admin/tsconfig.json b/admin/tsconfig.json
new file mode 100644
index 0000000..417e08f
--- /dev/null
+++ b/admin/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "compilerOptions": {
+ "baseUrl": "src",
+ "target": "esnext",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "noFallthroughCasesInSwitch": true,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx"
+ },
+ "include": ["src"]
+}
diff --git a/admin/vite.config.js b/admin/vite.config.js
new file mode 100644
index 0000000..eb8931c
--- /dev/null
+++ b/admin/vite.config.js
@@ -0,0 +1,70 @@
+import path from 'path';
+import { loadEnv } from 'vite';
+import { viteMockServe } from 'vite-plugin-mock';
+import react from '@vitejs/plugin-react';
+import svgr from '@honkhonk/vite-plugin-svgr';
+
+const CWD = process.cwd();
+
+export default (params) => {
+ const { mode } = params;
+ const { VITE_BASE_URL } = loadEnv(mode, CWD);
+ const baseUrl = VITE_BASE_URL?.trim() || '/admin/';
+
+ return {
+ base: baseUrl,
+ resolve: {
+ alias: {
+ assets: path.resolve(__dirname, './src/assets'),
+ components: path.resolve(__dirname, './src/components'),
+ configs: path.resolve(__dirname, './src/configs'),
+ layouts: path.resolve(__dirname, './src/layouts'),
+ modules: path.resolve(__dirname, './src/modules'),
+ pages: path.resolve(__dirname, './src/pages'),
+ styles: path.resolve(__dirname, './src/styles'),
+ utils: path.resolve(__dirname, './src/utils'),
+ services: path.resolve(__dirname, './src/services'),
+ router: path.resolve(__dirname, './src/router'),
+ hooks: path.resolve(__dirname, './src/hooks'),
+ types: path.resolve(__dirname, './src/types'),
+ },
+ },
+
+ css: {
+ preprocessorOptions: {
+ less: {
+ modifyVars: {
+ // 如需自定义组件其他 token, 在此处配置
+ },
+ },
+ },
+ },
+
+ plugins: [
+ svgr(),
+ react(),
+ mode === 'mock' &&
+ viteMockServe({
+ mockPath: './mock',
+ localEnabled: true,
+ }),
+ ],
+
+ build: {
+ cssCodeSplit: false,
+ },
+
+ server: {
+ host: '0.0.0.0',
+ port: 3003,
+ proxy: {
+ '/api': {
+ // 用于开发环境下的转发请求
+ // 更多请参考:https://vitejs.dev/config/#server-proxy
+ target: 'https://service-exndqyuk-1257786608.gz.apigw.tencentcs.com',
+ changeOrigin: true,
+ },
+ },
+ },
+ };
+};
diff --git a/canvas-agent/.gitignore b/canvas-agent/.gitignore
deleted file mode 100644
index de4d1f0..0000000
--- a/canvas-agent/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-dist
-node_modules
diff --git a/canvas-agent/README.md b/canvas-agent/README.md
deleted file mode 100644
index 32527e4..0000000
--- a/canvas-agent/README.md
+++ /dev/null
@@ -1,157 +0,0 @@
-# Infinite Canvas Agent
-
-本地 Canvas Agent 用来连接画布网页和用户电脑上的 Codex / Claude Code。本地开发时优先连接 `http://localhost:3000`,不需要先使用线上站点。
-
-## 启动
-
-```bash
-npx -y @basketikun/canvas-agent
-```
-
-需要排查连接、线程、Codex app-server 或工具调用问题时,可开启 Debug 模式:
-
-```bash
-npx -y @basketikun/canvas-agent --debug
-```
-
-Debug 日志会以 `[DEBUG][HH:mm:ss]` 等传统格式输出到终端,并按启动日期保存到 `~/.infinite-canvas/logs/canvas-agent-YYYY-MM-DD.log`。终端日志带级别颜色,文件日志为纯文本;日志包含 HTTP、SSE、线程、turn、Codex app-server 和工具调用事件,token 与图片 Data URL 会自动隐藏。
-
-本仓库开发时也可以直接运行:
-
-```bash
-cd canvas-agent
-npm install
-npm run build
-node dist/index.js
-```
-
-启动后会输出本机地址和 token:
-
-```txt
-Local URL: http://127.0.0.1:17371
-Connect token: xxxxxx
-```
-
-在画布右上角点击 `Agent`,填入地址和 token 后连接。
-
-Codex app 插件会读取启动输出里的 Local URL 和 Connect token,并直接打开画布网页地址;Canvas Agent 不负责生成画布打开 URL。
-
-Canvas Agent 默认只监听 `127.0.0.1`。网页第一次带正确 token 连接后,Canvas Agent 会记录该网页 Origin;之后其他 Origin 不能复用这个本地 Agent,除非用户清理 `~/.infinite-canvas/canvas-agent.json` 里的 `origins`。
-
-## 发布
-
-`canvas-agent` 使用自己的 `package.json` 版本号,不跟仓库根目录 `VERSION` 绑定。推送到 `main` 后,GitHub Actions 会检查 npm 上是否已经存在当前包版本;不存在时才发布 `@basketikun/canvas-agent`。
-
-发布前需要在 GitHub 仓库 Secrets 中配置 `NPM_TOKEN`。
-
-## Codex MCP
-
-如果希望 Codex 终端能直接操作画布,需要先把 Canvas Agent 注册成 Codex MCP。
-
-直接运行 `npx -y @basketikun/canvas-agent` 只启动本地 Agent 服务,不会安装 MCP,也不会增加 Codex 工具上下文。只有安装 Codex app 插件,或手动执行 `codex mcp add` 后,`infinite-canvas` 工具才会进入 Codex 上下文;由于工具较多,不使用时建议移除。
-
-通过插件安装时移除插件:
-
-```bash
-codex plugin remove infinite-canvas
-```
-
-手动添加 MCP 时移除 MCP:
-
-```bash
-codex mcp remove infinite-canvas
-```
-
-### Codex app 插件
-
-仓库内提供了 Codex app 插件:`plugins/infinite-canvas`。在 Codex app 中添加本仓库的 marketplace 后,可以安装 `Infinite Canvas` 插件;插件会注册同一个 `infinite-canvas` MCP,并带上画布操作说明。
-
-添加本地 marketplace 时建议使用仓库绝对路径,避免 Codex 从其他工作目录解析失败:
-
-```bash
-cd /path/to/infinite-canvas
-codex plugin marketplace add "$(pwd)"
-codex plugin add infinite-canvas@infinite-canvas-local
-```
-
-插件默认通过 npm 启动 MCP;这个命令只提供 MCP 工具,不会把 MCP 写入全局配置,也不会在退出时自动卸载:
-
-```bash
-npx -y @basketikun/canvas-agent mcp
-```
-
-使用时可以直接在 Codex 里说“打开 Infinite Canvas”,插件会启动本地 Agent,读取 Local URL 和 Connect token,然后在右侧打开 `https://canvas.best/` 并自动新建、连接画布;只有明确要求使用本地项目时才会启动本地前端。
-
-Canvas Agent 启动后,给 Codex 添加 MCP:
-
-```bash
-codex mcp add infinite-canvas -- npx -y @basketikun/canvas-agent mcp
-```
-
-本仓库开发时可以改成,实际使用建议替换为本机绝对路径:
-
-```bash
-codex mcp add infinite-canvas -- node /path/to/infinite-canvas/canvas-agent/dist/index.js mcp
-```
-
-Canvas Agent 源码使用 TypeScript 编写,MCP 协议层使用官方 `@modelcontextprotocol/sdk`,工具入参使用 `zod` 描述。
-
-如果希望终端里的 Codex 不被 MCP 审批卡住,可以在 `~/.codex/config.toml` 里给这个 MCP 设置自动放行:
-
-```toml
-[mcp_servers.infinite-canvas]
-command = "npx"
-args = ["-y", "@basketikun/canvas-agent", "mcp"]
-default_tools_approval_mode = "approve"
-```
-
-可用工具:
-
-- `canvas_get_state`
-- `canvas_get_selection`
-- `canvas_export_snapshot`
-- `canvas_apply_ops`
-- `canvas_create_text_node`
-- `canvas_create_image_prompt_flow`
-
-`canvas_apply_ops` 示例:
-
-```json
-{
- "ops": [
- {
- "type": "add_node",
- "nodeType": "text",
- "title": "标题",
- "position": { "x": 0, "y": 0 },
- "metadata": { "content": "文本内容" }
- }
- ]
-}
-```
-
-## 侧边栏 Codex
-
-本地面板会把提示词发送给 Canvas Agent。Canvas Agent 使用官方 `@openai/codex` CLI 的 `codex app-server --stdio` 启动并复用同一个 Codex thread,启动时会注入 `infinite-canvas` MCP 配置并自动放行 MCP 审批,真正执行画布修改前仍由网页侧边栏二次确认。
-
-侧边栏会展示 Codex 返回的 `thread.started`、`turn.started`、`item.*`、`turn.completed` 等结构化事件;Canvas Agent 会合并短时间内的回复、思考摘要和命令输出增量,网页使用同一条消息持续更新,并把任务进度、计划、搜索、文件修改与工具操作整理为中文过程时间线。
-
-侧边栏上传或粘贴的图片会先发到本机 Canvas Agent,再由 Canvas Agent 临时写入本机文件并作为 app-server `localImage` 输入传给 Codex;前端会提示附件体积,单次请求体限制为 30MB。
-
-## Claude Code
-
-Claude Code Adapter 代码暂时保留,但当前网页侧边栏只开放 Codex。后续开放 Claude 入口时,Canvas Agent 会调用本机 `claude -p --output-format stream-json` 并把流式 JSON 事件转发到侧边栏。
-
-如果希望 Claude Code 也能操作画布,需要给 Claude Code 添加同一个 MCP。建议用 user scope,避免 Canvas Agent 从不同目录启动时找不到配置:
-
-```bash
-claude mcp add --scope user --transport stdio infinite-canvas -- npx -y @basketikun/canvas-agent mcp
-```
-
-本仓库开发时可以改成:
-
-```bash
-claude mcp add --scope user --transport stdio infinite-canvas -- node /path/to/infinite-canvas/canvas-agent/dist/index.js mcp
-```
-
-Canvas Agent 调用 Claude Code 时会默认带上 `--allowedTools mcp__infinite-canvas__*`,画布写操作仍由网页侧边栏确认。
diff --git a/canvas-agent/agent-instructions.md b/canvas-agent/agent-instructions.md
deleted file mode 100644
index c2f1496..0000000
--- a/canvas-agent/agent-instructions.md
+++ /dev/null
@@ -1,13 +0,0 @@
-# Infinite Canvas Agent
-
-你正在帮助用户操作 Infinite Canvas 网站。
-
-- 用户要求操作画布时,默认目标就是网页当前已经打开的画布。需要了解内容时先使用 `canvas_get_state`;读取成功后直接在该画布执行任务,不要调用 `canvas_list_projects`,也不要用 `site_navigate` 重复进入画布。
-- 只有用户明确要求查看、选择或切换其他画布,或者 `canvas_get_state` 明确提示当前没有已连接画布时,才使用 `canvas_list_projects` 和 `site_navigate`。`site_navigate` 可跳转 `/`、`/canvas`、`/canvas/:id`、`/image`、`/video`、`/prompts`、`/assets`、`/config`。
-- 修改当前画布时根据任务使用已配置的 infinite-canvas MCP 工具;复杂批量改动使用 `canvas_apply_ops`。
-- 用户要求把上传附件放入画布或作为生成参考图时,必须先用 `canvas_create_attachment_nodes` 创建真实图片节点,再把节点 ID 传给生成流程,不要创建空图片占位节点。
-- 生图与视频工作台分别使用 `workbench_image_*`、`workbench_video_*` 工具;提示词和素材分别使用 `prompts_search`、`assets_*` 工具。
-- 用户要求生成图片、视频、音频或文本时,默认调用对应的 `canvas_generate_image`、`canvas_generate_video`、`canvas_generate_audio`、`canvas_generate_text`,通过当前画布的生成节点完成任务。
-- 只有用户明确要求使用“Codex 内置生图”“ImageGen 技能”或意思明确相同的能力时,才使用 Codex 自带的 `imagegen`;不要因为用户只说“生成图片”就自行改用内置生图。内置生图完成后,其结果会由 Canvas Agent 自动展示到对话并插入当前画布,无需再创建空节点或重复生成。
-- 只有用户明确说要在生图/视频工作台生成时,才使用 `workbench_image_*`、`workbench_video_*`。生成任务提交后应说明已经在画布或工作台开始生成,不要在实际没有结果时声称“已生成”。
-- 需要生成内容时直接调用对应生成工具,不要绑定特定业务场景,不要模拟鼠标点击,不要要求用户手动复制 JSON。
diff --git a/canvas-agent/bun.lock b/canvas-agent/bun.lock
deleted file mode 100644
index 65a058a..0000000
--- a/canvas-agent/bun.lock
+++ /dev/null
@@ -1,384 +0,0 @@
-{
- "lockfileVersion": 1,
- "configVersion": 1,
- "workspaces": {
- "": {
- "name": "@basketikun/canvas-agent",
- "dependencies": {
- "@modelcontextprotocol/sdk": "^1.12.1",
- "@openai/codex": "0.146.0",
- "express": "^5.1.0",
- "gray-matter": "^4.0.3",
- "strip-ansi": "7.2.0",
- "winston": "^3.19.0",
- "yaml": "^2.9.0",
- "zod": "^3.25.0",
- },
- "devDependencies": {
- "@types/express": "^5.0.0",
- "@types/node": "^22.0.0",
- "tsx": "^4.19.0",
- "typescript": "^5.8.0",
- },
- },
- },
- "packages": {
- "@colors/colors": ["@colors/colors@1.6.0", "", {}, "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA=="],
-
- "@dabh/diagnostics": ["@dabh/diagnostics@2.0.8", "", { "dependencies": { "@so-ric/colorspace": "^1.1.6", "enabled": "2.0.x", "kuler": "^2.0.0" } }, "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q=="],
-
- "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="],
-
- "@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.28.1.tgz", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="],
-
- "@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.1", "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", { "os": "android", "cpu": "arm64" }, "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg=="],
-
- "@esbuild/android-x64": ["@esbuild/android-x64@0.28.1", "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.28.1.tgz", { "os": "android", "cpu": "x64" }, "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng=="],
-
- "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.1", "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q=="],
-
- "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.1", "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ=="],
-
- "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.1", "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", { "os": "freebsd", "cpu": "arm64" }, "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw=="],
-
- "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.1", "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ=="],
-
- "@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.1", "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", { "os": "linux", "cpu": "arm" }, "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ=="],
-
- "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.1", "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g=="],
-
- "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.1", "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", { "os": "linux", "cpu": "ia32" }, "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w=="],
-
- "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.1", "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", { "os": "linux", "cpu": "none" }, "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg=="],
-
- "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.1", "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", { "os": "linux", "cpu": "none" }, "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ=="],
-
- "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.1", "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ=="],
-
- "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.1", "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", { "os": "linux", "cpu": "none" }, "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ=="],
-
- "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.1", "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", { "os": "linux", "cpu": "s390x" }, "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag=="],
-
- "@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.1", "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", { "os": "linux", "cpu": "x64" }, "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA=="],
-
- "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.1", "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", { "os": "none", "cpu": "arm64" }, "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw=="],
-
- "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.1", "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", { "os": "none", "cpu": "x64" }, "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg=="],
-
- "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.1", "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", { "os": "openbsd", "cpu": "arm64" }, "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q=="],
-
- "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.1", "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", { "os": "openbsd", "cpu": "x64" }, "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw=="],
-
- "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.1", "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", { "os": "none", "cpu": "arm64" }, "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg=="],
-
- "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.1", "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", { "os": "sunos", "cpu": "x64" }, "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ=="],
-
- "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.1", "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA=="],
-
- "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.1", "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", { "os": "win32", "cpu": "ia32" }, "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg=="],
-
- "@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="],
-
- "@hono/node-server": ["@hono/node-server@1.19.14", "https://registry.npmmirror.com/@hono/node-server/-/node-server-1.19.14.tgz", { "peerDependencies": { "hono": "^4" } }, "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw=="],
-
- "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.29.0", "https://registry.npmmirror.com/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ=="],
-
- "@openai/codex": ["@openai/codex@0.146.0", "", { "optionalDependencies": { "@openai/codex-darwin-arm64": "npm:@openai/codex@0.146.0-darwin-arm64", "@openai/codex-darwin-x64": "npm:@openai/codex@0.146.0-darwin-x64", "@openai/codex-linux-arm64": "npm:@openai/codex@0.146.0-linux-arm64", "@openai/codex-linux-x64": "npm:@openai/codex@0.146.0-linux-x64", "@openai/codex-win32-arm64": "npm:@openai/codex@0.146.0-win32-arm64", "@openai/codex-win32-x64": "npm:@openai/codex@0.146.0-win32-x64" }, "bin": { "codex": "bin/codex.js" } }, "sha512-yG3sPWNda/2YAIQIDq9MrrjoCTIQ7rxYM5IasrG3VBcuhCLTkgeg/JzqmJq1V98RE4MJ5jCxDXXQlOjrditFRw=="],
-
- "@openai/codex-darwin-arm64": ["@openai/codex@0.146.0-darwin-arm64", "", { "os": "darwin", "cpu": "arm64" }, "sha512-nb61yX4r5L6Z0dlC4o3u0GAK1YCd4TUvjaB382bajDoh84V+uv2hTBIVZ++fgXWV9yoeuNrNnNcn7GoTGOe2Tg=="],
-
- "@openai/codex-darwin-x64": ["@openai/codex@0.146.0-darwin-x64", "", { "os": "darwin", "cpu": "x64" }, "sha512-hTQR5jy/ObfTf1MDnuJCZJAe+SljKE8DDwQWN6lDFgjsPhMQz852U2tILt8Ei+G5GkQSzemHYKl2AYPwW0Y5xw=="],
-
- "@openai/codex-linux-arm64": ["@openai/codex@0.146.0-linux-arm64", "", { "os": "linux", "cpu": "arm64" }, "sha512-qiYDxkkEFnXG7joadJW6Q+XcgyDXCpGdpa9nk/c+i0gEomur1j7bHvx12NfWWCF/y8Tqri6ay+FLuC2MjdehtA=="],
-
- "@openai/codex-linux-x64": ["@openai/codex@0.146.0-linux-x64", "", { "os": "linux", "cpu": "x64" }, "sha512-fswvyGprAPCMiOEue/7MKMk7pCjh9kZIJfJX5i9atmfnmGYbYCcUhZsEH9LEP0+0t5xyPqDbfNXY7NSxIVuXxA=="],
-
- "@openai/codex-win32-arm64": ["@openai/codex@0.146.0-win32-arm64", "", { "os": "win32", "cpu": "arm64" }, "sha512-EW6zdjDe+SLX2Iw+xymJ5+Pz2+DGexdstfFHXh4Ub+TfJsQPiMjGfZfNaoWgdJ2FsqSIzVKu2+G0KCMGYz2W8g=="],
-
- "@openai/codex-win32-x64": ["@openai/codex@0.146.0-win32-x64", "", { "os": "win32", "cpu": "x64" }, "sha512-b3lxMYeR0+IhstNo4JjX1P9cPc1xwVcCVkPd1lD1wpWPJ0SBhpIkPczwbu3ZRkJcdyl342+rgyf4DUrbZLdrGA=="],
-
- "@so-ric/colorspace": ["@so-ric/colorspace@1.1.6", "", { "dependencies": { "color": "^5.0.2", "text-hex": "1.0.x" } }, "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw=="],
-
- "@types/body-parser": ["@types/body-parser@1.19.6", "https://registry.npmmirror.com/@types/body-parser/-/body-parser-1.19.6.tgz", { "dependencies": { "@types/connect": "*", "@types/node": "*" } }, "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g=="],
-
- "@types/connect": ["@types/connect@3.4.38", "https://registry.npmmirror.com/@types/connect/-/connect-3.4.38.tgz", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="],
-
- "@types/express": ["@types/express@5.0.6", "https://registry.npmmirror.com/@types/express/-/express-5.0.6.tgz", { "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", "@types/serve-static": "^2" } }, "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA=="],
-
- "@types/express-serve-static-core": ["@types/express-serve-static-core@5.1.1", "https://registry.npmmirror.com/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz", { "dependencies": { "@types/node": "*", "@types/qs": "*", "@types/range-parser": "*", "@types/send": "*" } }, "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A=="],
-
- "@types/http-errors": ["@types/http-errors@2.0.5", "https://registry.npmmirror.com/@types/http-errors/-/http-errors-2.0.5.tgz", {}, "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg=="],
-
- "@types/node": ["@types/node@22.19.21", "https://registry.npmmirror.com/@types/node/-/node-22.19.21.tgz", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-VMeFBSCKQKmm2swI2kW51SFusDqekC6q9trBCvJ/JliDchFSuoYYKN7yVNjPthP1HKZcx3U1gI/wTcEBjEFKTA=="],
-
- "@types/qs": ["@types/qs@6.15.1", "https://registry.npmmirror.com/@types/qs/-/qs-6.15.1.tgz", {}, "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw=="],
-
- "@types/range-parser": ["@types/range-parser@1.2.7", "https://registry.npmmirror.com/@types/range-parser/-/range-parser-1.2.7.tgz", {}, "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="],
-
- "@types/send": ["@types/send@1.2.1", "https://registry.npmmirror.com/@types/send/-/send-1.2.1.tgz", { "dependencies": { "@types/node": "*" } }, "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ=="],
-
- "@types/serve-static": ["@types/serve-static@2.2.0", "https://registry.npmmirror.com/@types/serve-static/-/serve-static-2.2.0.tgz", { "dependencies": { "@types/http-errors": "*", "@types/node": "*" } }, "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ=="],
-
- "@types/triple-beam": ["@types/triple-beam@1.3.5", "", {}, "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw=="],
-
- "accepts": ["accepts@2.0.0", "https://registry.npmmirror.com/accepts/-/accepts-2.0.0.tgz", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
-
- "ajv": ["ajv@8.20.0", "https://registry.npmmirror.com/ajv/-/ajv-8.20.0.tgz", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="],
-
- "ajv-formats": ["ajv-formats@3.0.1", "https://registry.npmmirror.com/ajv-formats/-/ajv-formats-3.0.1.tgz", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="],
-
- "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
-
- "argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
-
- "async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="],
-
- "body-parser": ["body-parser@2.2.2", "https://registry.npmmirror.com/body-parser/-/body-parser-2.2.2.tgz", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="],
-
- "bytes": ["bytes@3.1.2", "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
-
- "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
-
- "call-bound": ["call-bound@1.0.4", "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
-
- "color": ["color@5.0.3", "", { "dependencies": { "color-convert": "^3.1.3", "color-string": "^2.1.3" } }, "sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA=="],
-
- "color-convert": ["color-convert@3.1.3", "", { "dependencies": { "color-name": "^2.0.0" } }, "sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg=="],
-
- "color-name": ["color-name@2.1.1", "", {}, "sha512-p2FdgwVx1a9yWBHP2wI0VgShkDpgN4kZISkxdNipGBJWpa5G6b04OINlVWCyJj0JmfvcPrgqt95E9k8yvaOJFg=="],
-
- "color-string": ["color-string@2.1.4", "", { "dependencies": { "color-name": "^2.0.0" } }, "sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg=="],
-
- "content-disposition": ["content-disposition@1.1.0", "https://registry.npmmirror.com/content-disposition/-/content-disposition-1.1.0.tgz", {}, "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g=="],
-
- "content-type": ["content-type@1.0.5", "https://registry.npmmirror.com/content-type/-/content-type-1.0.5.tgz", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
-
- "cookie": ["cookie@0.7.2", "https://registry.npmmirror.com/cookie/-/cookie-0.7.2.tgz", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
-
- "cookie-signature": ["cookie-signature@1.2.2", "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.2.2.tgz", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],
-
- "cors": ["cors@2.8.6", "https://registry.npmmirror.com/cors/-/cors-2.8.6.tgz", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw=="],
-
- "cross-spawn": ["cross-spawn@7.0.6", "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
-
- "debug": ["debug@4.4.3", "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
-
- "depd": ["depd@2.0.0", "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
-
- "dunder-proto": ["dunder-proto@1.0.1", "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
-
- "ee-first": ["ee-first@1.1.1", "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
-
- "enabled": ["enabled@2.0.0", "", {}, "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ=="],
-
- "encodeurl": ["encodeurl@2.0.0", "https://registry.npmmirror.com/encodeurl/-/encodeurl-2.0.0.tgz", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
-
- "es-define-property": ["es-define-property@1.0.1", "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
-
- "es-errors": ["es-errors@1.3.0", "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
-
- "es-object-atoms": ["es-object-atoms@1.1.2", "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.2.tgz", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw=="],
-
- "esbuild": ["esbuild@0.28.1", "https://registry.npmmirror.com/esbuild/-/esbuild-0.28.1.tgz", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="],
-
- "escape-html": ["escape-html@1.0.3", "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
-
- "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="],
-
- "etag": ["etag@1.8.1", "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
-
- "eventsource": ["eventsource@3.0.7", "https://registry.npmmirror.com/eventsource/-/eventsource-3.0.7.tgz", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="],
-
- "eventsource-parser": ["eventsource-parser@3.1.0", "https://registry.npmmirror.com/eventsource-parser/-/eventsource-parser-3.1.0.tgz", {}, "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg=="],
-
- "express": ["express@5.2.1", "https://registry.npmmirror.com/express/-/express-5.2.1.tgz", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="],
-
- "express-rate-limit": ["express-rate-limit@8.5.2", "https://registry.npmmirror.com/express-rate-limit/-/express-rate-limit-8.5.2.tgz", { "dependencies": { "ip-address": "^10.2.0" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A=="],
-
- "extend-shallow": ["extend-shallow@2.0.1", "", { "dependencies": { "is-extendable": "^0.1.0" } }, "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug=="],
-
- "fast-deep-equal": ["fast-deep-equal@3.1.3", "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
-
- "fast-uri": ["fast-uri@3.1.2", "https://registry.npmmirror.com/fast-uri/-/fast-uri-3.1.2.tgz", {}, "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ=="],
-
- "fecha": ["fecha@4.2.3", "", {}, "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw=="],
-
- "finalhandler": ["finalhandler@2.1.1", "https://registry.npmmirror.com/finalhandler/-/finalhandler-2.1.1.tgz", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="],
-
- "fn.name": ["fn.name@1.1.0", "", {}, "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw=="],
-
- "forwarded": ["forwarded@0.2.0", "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
-
- "fresh": ["fresh@2.0.0", "https://registry.npmmirror.com/fresh/-/fresh-2.0.0.tgz", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
-
- "fsevents": ["fsevents@2.3.3", "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
-
- "function-bind": ["function-bind@1.1.2", "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
-
- "get-intrinsic": ["get-intrinsic@1.3.0", "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
-
- "get-proto": ["get-proto@1.0.1", "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
-
- "gopd": ["gopd@1.2.0", "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
-
- "gray-matter": ["gray-matter@4.0.3", "", { "dependencies": { "js-yaml": "^3.13.1", "kind-of": "^6.0.2", "section-matter": "^1.0.0", "strip-bom-string": "^1.0.0" } }, "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q=="],
-
- "has-symbols": ["has-symbols@1.1.0", "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
-
- "hasown": ["hasown@2.0.4", "https://registry.npmmirror.com/hasown/-/hasown-2.0.4.tgz", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="],
-
- "hono": ["hono@4.12.25", "https://registry.npmmirror.com/hono/-/hono-4.12.25.tgz", {}, "sha512-2NFaIyNVgJmBs/ecmtGzlmluTFs5cHEWGTdu0t1HBwYzoGXOL5nUQBRMXsXWla5i4KkG//QMzVP88m1+I3fdAQ=="],
-
- "http-errors": ["http-errors@2.0.1", "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.1.tgz", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="],
-
- "iconv-lite": ["iconv-lite@0.7.2", "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.7.2.tgz", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
-
- "inherits": ["inherits@2.0.4", "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
-
- "ip-address": ["ip-address@10.2.0", "https://registry.npmmirror.com/ip-address/-/ip-address-10.2.0.tgz", {}, "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA=="],
-
- "ipaddr.js": ["ipaddr.js@1.9.1", "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
-
- "is-extendable": ["is-extendable@0.1.1", "", {}, "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw=="],
-
- "is-promise": ["is-promise@4.0.0", "https://registry.npmmirror.com/is-promise/-/is-promise-4.0.0.tgz", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="],
-
- "is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
-
- "isexe": ["isexe@2.0.0", "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
-
- "jose": ["jose@6.2.3", "https://registry.npmmirror.com/jose/-/jose-6.2.3.tgz", {}, "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw=="],
-
- "js-yaml": ["js-yaml@3.15.1", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag=="],
-
- "json-schema-traverse": ["json-schema-traverse@1.0.0", "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
-
- "json-schema-typed": ["json-schema-typed@8.0.2", "https://registry.npmmirror.com/json-schema-typed/-/json-schema-typed-8.0.2.tgz", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="],
-
- "kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="],
-
- "kuler": ["kuler@2.0.0", "", {}, "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A=="],
-
- "logform": ["logform@2.7.0", "", { "dependencies": { "@colors/colors": "1.6.0", "@types/triple-beam": "^1.3.2", "fecha": "^4.2.0", "ms": "^2.1.1", "safe-stable-stringify": "^2.3.1", "triple-beam": "^1.3.0" } }, "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ=="],
-
- "math-intrinsics": ["math-intrinsics@1.1.0", "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
-
- "media-typer": ["media-typer@1.1.0", "https://registry.npmmirror.com/media-typer/-/media-typer-1.1.0.tgz", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
-
- "merge-descriptors": ["merge-descriptors@2.0.0", "https://registry.npmmirror.com/merge-descriptors/-/merge-descriptors-2.0.0.tgz", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],
-
- "mime-db": ["mime-db@1.54.0", "https://registry.npmmirror.com/mime-db/-/mime-db-1.54.0.tgz", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
-
- "mime-types": ["mime-types@3.0.2", "https://registry.npmmirror.com/mime-types/-/mime-types-3.0.2.tgz", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="],
-
- "ms": ["ms@2.1.3", "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
-
- "negotiator": ["negotiator@1.0.0", "https://registry.npmmirror.com/negotiator/-/negotiator-1.0.0.tgz", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
-
- "object-assign": ["object-assign@4.1.1", "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
-
- "object-inspect": ["object-inspect@1.13.4", "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.4.tgz", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
-
- "on-finished": ["on-finished@2.4.1", "https://registry.npmmirror.com/on-finished/-/on-finished-2.4.1.tgz", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
-
- "once": ["once@1.4.0", "https://registry.npmmirror.com/once/-/once-1.4.0.tgz", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
-
- "one-time": ["one-time@1.0.0", "", { "dependencies": { "fn.name": "1.x.x" } }, "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g=="],
-
- "parseurl": ["parseurl@1.3.3", "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
-
- "path-key": ["path-key@3.1.1", "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
-
- "path-to-regexp": ["path-to-regexp@8.4.2", "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-8.4.2.tgz", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="],
-
- "pkce-challenge": ["pkce-challenge@5.0.1", "https://registry.npmmirror.com/pkce-challenge/-/pkce-challenge-5.0.1.tgz", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="],
-
- "proxy-addr": ["proxy-addr@2.0.7", "https://registry.npmmirror.com/proxy-addr/-/proxy-addr-2.0.7.tgz", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
-
- "qs": ["qs@6.15.2", "https://registry.npmmirror.com/qs/-/qs-6.15.2.tgz", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw=="],
-
- "range-parser": ["range-parser@1.2.1", "https://registry.npmmirror.com/range-parser/-/range-parser-1.2.1.tgz", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
-
- "raw-body": ["raw-body@3.0.2", "https://registry.npmmirror.com/raw-body/-/raw-body-3.0.2.tgz", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="],
-
- "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
-
- "require-from-string": ["require-from-string@2.0.2", "https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
-
- "router": ["router@2.2.0", "https://registry.npmmirror.com/router/-/router-2.2.0.tgz", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="],
-
- "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
-
- "safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="],
-
- "safer-buffer": ["safer-buffer@2.1.2", "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
-
- "section-matter": ["section-matter@1.0.0", "", { "dependencies": { "extend-shallow": "^2.0.1", "kind-of": "^6.0.0" } }, "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA=="],
-
- "send": ["send@1.2.1", "https://registry.npmmirror.com/send/-/send-1.2.1.tgz", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="],
-
- "serve-static": ["serve-static@2.2.1", "https://registry.npmmirror.com/serve-static/-/serve-static-2.2.1.tgz", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="],
-
- "setprototypeof": ["setprototypeof@1.2.0", "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
-
- "shebang-command": ["shebang-command@2.0.0", "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
-
- "shebang-regex": ["shebang-regex@3.0.0", "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
-
- "side-channel": ["side-channel@1.1.1", "https://registry.npmmirror.com/side-channel/-/side-channel-1.1.1.tgz", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4", "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ=="],
-
- "side-channel-list": ["side-channel-list@1.0.1", "https://registry.npmmirror.com/side-channel-list/-/side-channel-list-1.0.1.tgz", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4" } }, "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w=="],
-
- "side-channel-map": ["side-channel-map@1.0.1", "https://registry.npmmirror.com/side-channel-map/-/side-channel-map-1.0.1.tgz", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="],
-
- "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "https://registry.npmmirror.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
-
- "sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="],
-
- "stack-trace": ["stack-trace@0.0.10", "", {}, "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg=="],
-
- "statuses": ["statuses@2.0.2", "https://registry.npmmirror.com/statuses/-/statuses-2.0.2.tgz", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
-
- "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
-
- "strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
-
- "strip-bom-string": ["strip-bom-string@1.0.0", "", {}, "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g=="],
-
- "text-hex": ["text-hex@1.0.0", "", {}, "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg=="],
-
- "toidentifier": ["toidentifier@1.0.1", "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
-
- "triple-beam": ["triple-beam@1.4.1", "", {}, "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg=="],
-
- "tsx": ["tsx@4.22.4", "https://registry.npmmirror.com/tsx/-/tsx-4.22.4.tgz", { "dependencies": { "esbuild": "~0.28.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg=="],
-
- "type-is": ["type-is@2.1.0", "https://registry.npmmirror.com/type-is/-/type-is-2.1.0.tgz", { "dependencies": { "content-type": "^2.0.0", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA=="],
-
- "typescript": ["typescript@5.9.3", "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
-
- "undici-types": ["undici-types@6.21.0", "https://registry.npmmirror.com/undici-types/-/undici-types-6.21.0.tgz", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
-
- "unpipe": ["unpipe@1.0.0", "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
-
- "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
-
- "vary": ["vary@1.1.2", "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
-
- "which": ["which@2.0.2", "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
-
- "winston": ["winston@3.19.0", "", { "dependencies": { "@colors/colors": "^1.6.0", "@dabh/diagnostics": "^2.0.8", "async": "^3.2.3", "is-stream": "^2.0.0", "logform": "^2.7.0", "one-time": "^1.0.0", "readable-stream": "^3.4.0", "safe-stable-stringify": "^2.3.1", "stack-trace": "0.0.x", "triple-beam": "^1.3.0", "winston-transport": "^4.9.0" } }, "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA=="],
-
- "winston-transport": ["winston-transport@4.9.0", "", { "dependencies": { "logform": "^2.7.0", "readable-stream": "^3.6.2", "triple-beam": "^1.3.0" } }, "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A=="],
-
- "wrappy": ["wrappy@1.0.2", "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
-
- "yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="],
-
- "zod": ["zod@3.25.76", "https://registry.npmmirror.com/zod/-/zod-3.25.76.tgz", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
-
- "zod-to-json-schema": ["zod-to-json-schema@3.25.2", "https://registry.npmmirror.com/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="],
-
- "type-is/content-type": ["content-type@2.0.0", "https://registry.npmmirror.com/content-type/-/content-type-2.0.0.tgz", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="],
- }
-}
diff --git a/canvas-agent/codex-server.md b/canvas-agent/codex-server.md
deleted file mode 100644
index caba0ee..0000000
--- a/canvas-agent/codex-server.md
+++ /dev/null
@@ -1,2175 +0,0 @@
-# Codex App Server
-
-> For the complete documentation index, see [llms.txt](https://learn.chatgpt.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to the page URL.
-
-Codex app-server is the interface Codex uses to power rich clients (for example, the Codex VS Code extension). Use it when you want a deep integration inside your own product: authentication, conversation history, approvals, and streamed agent events. The app-server implementation is open source in the Codex GitHub repository ([openai/codex/codex-rs/app-server](https://github.com/openai/codex/tree/main/codex-rs/app-server)). See the [Open Source](https://learn.chatgpt.com/docs/open-source) page for the full list of open-source Codex components.
-
-If you are automating jobs or running Codex in CI, use the
-[Codex SDK](https://learn.chatgpt.com/docs/codex-sdk) instead.
-
-## Connect the CLI terminal UI
-
-Remote terminal UI mode lets you run app-server on one machine and connect the
-Codex CLI terminal interface from another. Start a WebSocket listener:
-
-```bash
-codex app-server --listen ws://127.0.0.1:4500
-```
-
-Then connect the terminal UI:
-
-```bash
-codex --remote ws://127.0.0.1:4500
-```
-
-For a non-local connection, configure WebSocket authentication and put the
-connection behind TLS. Store the bearer token in an environment variable and
-pass its name instead of putting the token on the command line:
-
-```bash
-export CODEX_REMOTE_TOKEN="$(cat "$HOME/.codex/app-server-token")"
-codex --remote wss://remote-host:4500 \
- --remote-auth-token-env CODEX_REMOTE_TOKEN
-```
-
-The `--remote` option accepts `ws://`, `wss://`, `unix://`, and
-`unix://PATH` endpoints. Use plain WebSockets only for localhost or an SSH
-port-forwarded connection.
-
-## Protocol
-
-Like [MCP](https://modelcontextprotocol.io/), `codex app-server` supports bidirectional communication using JSON-RPC 2.0 messages (with the `"jsonrpc":"2.0"` header omitted on the wire).
-
-Supported transports:
-
-- `stdio` (`--listen stdio://`, default): newline-delimited JSON (JSONL).
-- `websocket` (`--listen ws://IP:PORT`, experimental and unsupported): one
- JSON-RPC message per WebSocket text frame.
-- Unix socket (`--listen unix://` or `--listen unix://PATH`): WebSocket
- connections over Codex's default app-server control socket or a custom Unix
- socket path, using the standard HTTP Upgrade handshake.
-- `off` (`--listen off`): don't expose a local transport.
-
-When you run with `--listen ws://IP:PORT`, the same listener also serves basic
-HTTP health probes:
-
-- `GET /readyz` returns `200 OK` once the listener accepts new connections.
-- `GET /healthz` returns `200 OK` when the request doesn't include an `Origin`
- header.
-- Requests with an `Origin` header are rejected with `403 Forbidden`.
-
-WebSocket transport is experimental and unsupported. Local listeners such as
-`ws://127.0.0.1:PORT` are appropriate for localhost and SSH port-forwarding
-workflows. Non-loopback WebSocket listeners currently allow unauthenticated
-connections by default during rollout, so configure WebSocket auth before
-exposing one remotely.
-
-Supported WebSocket auth flags:
-
-- `--ws-auth capability-token --ws-token-file /absolute/path`
-- `--ws-auth capability-token --ws-token-sha256 HEX`
-- `--ws-auth signed-bearer-token --ws-shared-secret-file /absolute/path`
-
-For signed bearer tokens, you can also set `--ws-issuer`, `--ws-audience`, and
-`--ws-max-clock-skew-seconds`. Clients present the credential as
-`Authorization: Bearer ` during the WebSocket handshake, and app-server
-enforces auth before JSON-RPC `initialize`.
-
-Prefer `--ws-token-file` over passing raw bearer tokens on the command line. Use
-`--ws-token-sha256` only when the client keeps the raw high-entropy token in a
-separate local secret store; the hash is only a verifier, and clients still need
-the original token.
-
-In WebSocket mode, app-server uses bounded queues. When request ingress is full,
-the server rejects new requests with JSON-RPC error code `-32001` and message
-`"Server overloaded; retry later."` Clients should retry with an exponentially
-increasing delay and jitter.
-
-## Message schema
-
-Requests include `method`, `params`, and `id`:
-
-```json
-{ "method": "thread/start", "id": 10, "params": { "model": "gpt-5.4" } }
-```
-
-Responses echo the `id` with either `result` or `error`:
-
-```json
-{ "id": 10, "result": { "thread": { "id": "thr_123" } } }
-```
-
-```json
-{ "id": 10, "error": { "code": 123, "message": "Something went wrong" } }
-```
-
-Notifications omit `id` and use only `method` and `params`:
-
-```json
-{ "method": "turn/started", "params": { "turn": { "id": "turn_456" } } }
-```
-
-You can generate a TypeScript schema or a JSON Schema bundle from the CLI. Each output is specific to the Codex version you ran, so the generated artifacts match that version exactly:
-
-```bash
-codex app-server generate-ts --out ./schemas
-codex app-server generate-json-schema --out ./schemas
-```
-
-## Getting started
-
-1. Start the server with `codex app-server` (default stdio transport),
- `codex app-server --listen ws://127.0.0.1:4500` (TCP WebSocket), or
- `codex app-server --listen unix://` (default Unix socket).
-2. Connect a client over the selected transport, then send `initialize` followed by the `initialized` notification.
-3. Start a thread and a turn, then keep reading notifications from the active transport stream.
-
-Example (Node.js / TypeScript):
-
-```ts
-
-
-
-const proc = spawn("codex", ["app-server"], {
- stdio: ["pipe", "pipe", "inherit"],
-});
-const rl = readline.createInterface({ input: proc.stdout });
-
-const send = (message: unknown) => {
- proc.stdin.write(`${JSON.stringify(message)}\n`);
-};
-
-let threadId: string | null = null;
-
-rl.on("line", (line) => {
- const msg = JSON.parse(line) as any;
- console.log("server:", msg);
-
- if (msg.id === 1 && msg.result?.thread?.id && !threadId) {
- threadId = msg.result.thread.id;
- send({
- method: "turn/start",
- id: 2,
- params: {
- threadId,
- input: [{ type: "text", text: "Summarize this repo." }],
- },
- });
- }
-});
-
-send({
- method: "initialize",
- id: 0,
- params: {
- clientInfo: {
- name: "my_product",
- title: "My Product",
- version: "0.1.0",
- },
- },
-});
-send({ method: "initialized", params: {} });
-send({ method: "thread/start", id: 1, params: { model: "gpt-5.4" } });
-```
-
-## Core primitives
-
-- **Thread**: A conversation between a user and the Codex agent. Threads contain turns.
-- **Turn**: A single user request and the agent work that follows. Turns contain items and stream incremental updates.
-- **Item**: A unit of input or output (user message, agent message, command runs, file change, tool call, and more).
-
-Use the thread APIs to create, list, or archive conversations. Drive a conversation with turn APIs and stream progress via turn notifications.
-
-## Lifecycle overview
-
-- **Initialize once per connection**: Immediately after opening a transport connection, send an `initialize` request with your client metadata, then emit `initialized`. The server rejects any request on that connection before this handshake.
-- **Start (or resume) a thread**: Call `thread/start` for a new conversation, `thread/resume` to continue an existing one, or `thread/fork` to branch history into a new thread id.
-- **Begin a turn**: Call `turn/start` with the target `threadId` and user input. Optional fields override model, personality, `cwd`, sandbox policy, and more.
-- **Steer an active turn**: Call `turn/steer` to append user input to the currently in-flight turn without creating a new turn.
-- **Stream events**: After `turn/start`, keep reading notifications on stdout: `thread/archived`, `thread/unarchived`, `item/started`, `item/completed`, `item/agentMessage/delta`, tool progress, and other updates.
-- **Finish the turn**: The server emits `turn/completed` with final status when the model finishes or after a `turn/interrupt` cancellation.
-
-## Initialization
-
-Clients must send a single `initialize` request per transport connection before invoking any other method on that connection, then acknowledge with an `initialized` notification. Requests sent before initialization receive a `Not initialized` error, and repeated `initialize` calls on the same connection return `Already initialized`.
-
-The server returns the user agent string it will present to upstream services plus `platformFamily` and `platformOs` values that describe the runtime target. Set `clientInfo` to identify your integration.
-
-`initialize.params.capabilities` also supports these client capabilities:
-
-- `optOutNotificationMethods` - exact notification method names to suppress for
- this connection. Matching is exact (no wildcards or prefixes); unknown names
- are accepted and ignored.
-- `requestAttestation` - opt into the server-initiated `attestation/generate`
- request. Desktop hosts that provide upstream attestation respond with an
- opaque `{ "token": "..." }` value.
-- `mcpServerOpenaiFormElicitation` - allow downstream MCP servers to send the
- OpenAI extended-form variant of `mcpServer/elicitation/request`.
-
-**Important**: Use `clientInfo.name` to identify your client for the OpenAI Compliance Logs Platform. If you are developing a new Codex integration intended for enterprise use, please contact OpenAI to get it added to a known clients list. For more context, see the [Codex logs reference](https://chatgpt.com/admin/api-reference#tag/Logs:-Codex).
-
-Example (from the Codex VS Code extension):
-
-```json
-{
- "method": "initialize",
- "id": 0,
- "params": {
- "clientInfo": {
- "name": "codex_vscode",
- "title": "Codex VS Code Extension",
- "version": "0.1.0"
- }
- }
-}
-```
-
-Example with notification opt-out:
-
-```json
-{
- "method": "initialize",
- "id": 1,
- "params": {
- "clientInfo": {
- "name": "my_client",
- "title": "My Client",
- "version": "0.1.0"
- },
- "capabilities": {
- "experimentalApi": true,
- "optOutNotificationMethods": ["thread/started", "item/agentMessage/delta"]
- }
- }
-}
-```
-
-## Experimental API opt-in
-
-Some app-server methods and fields are intentionally gated behind `experimentalApi` capability.
-
-- Omit `capabilities` (or set `experimentalApi` to `false`) to stay on the stable API surface, and the server rejects experimental methods/fields.
-- Set `capabilities.experimentalApi` to `true` to enable experimental methods and fields.
-
-```json
-{
- "method": "initialize",
- "id": 1,
- "params": {
- "clientInfo": {
- "name": "my_client",
- "title": "My Client",
- "version": "0.1.0"
- },
- "capabilities": {
- "experimentalApi": true
- }
- }
-}
-```
-
-If a client sends an experimental method or field without opting in, app-server rejects it with:
-
-` requires experimentalApi capability`
-
-## API overview
-
-- `thread/start` - create a new thread; emits `thread/started` and automatically subscribes you to turn/item events for that thread.
-- `thread/resume` - reopen an existing thread by id so later `turn/start` calls append to it.
-- `thread/fork` - fork a thread into a new thread id by copying stored history. Pass `lastTurnId` to copy history through that turn and omit later turns. Emits `thread/started` for the new thread; returned threads include `forkedFromId` when available.
-- `thread/read` - read a stored thread by id without resuming it; set `includeTurns` to return full turn history. Returned `thread` objects include runtime `status`.
-- `thread/list` - page through stored thread logs; supports cursor-based pagination plus `modelProviders`, `sourceKinds`, `archived`, `cwd`, `useStateDbOnly`, `searchTerm`, and experimental `parentThreadId` or `ancestorThreadId` filters. Returned `thread` objects include runtime `status`.
-- `thread/turns/list` - experimental; page through a stored thread's turn history without resuming it. `itemsView` controls whether turn items are omitted, summarized, or fully loaded.
-- `thread/items/list` - experimental; page through persisted thread items, optionally restricted to one `turnId`. The active thread store must support item pagination.
-- `thread/loaded/list` - list the thread ids currently loaded in memory.
-- `thread/name/set` - set or update a thread's user-facing name for a loaded thread or a persisted rollout; emits `thread/name/updated`.
-- `thread/goal/set` - set the goal for a thread; emits `thread/goal/updated`.
-- `thread/goal/get` - read the current goal for a thread.
-- `thread/goal/clear` - clear the goal for a thread; emits `thread/goal/cleared`.
-- `thread/metadata/update` - patch SQLite-backed stored thread metadata; currently supports persisted `gitInfo`.
-- `thread/archive` - move a thread's log file into the archived directory and attempt to archive spawned descendant thread logs that aren't already archived; returns `{}` on success and emits `thread/archived` for each archived thread.
-- `thread/delete` - permanently delete a persisted active or archived thread and any spawned descendant threads; returns `{}` on success and emits `thread/deleted` for each deleted thread.
-- `thread/unsubscribe` - unsubscribe this connection from thread turn/item events. If this was the last subscriber, the server unloads the thread after a no-subscriber inactivity grace period and emits `thread/closed`.
-- `thread/unarchive` - restore an archived thread rollout back into the active sessions directory; returns the restored `thread` and emits `thread/unarchived`.
-- `thread/status/changed` - notification emitted when a loaded thread's runtime `status` changes.
-- `thread/compact/start` - trigger conversation history compaction for a thread; returns `{}` immediately while progress streams via `turn/*` and `item/*` notifications.
-- `thread/shellCommand` - run a user-initiated shell command against a thread. This runs outside the sandbox with full access and doesn't inherit the thread sandbox policy.
-- `thread/backgroundTerminals/clean` - stop all running background terminals for a thread (experimental; requires `capabilities.experimentalApi`).
-- `thread/backgroundTerminals/list` - list running background terminals for a loaded thread (experimental; requires `capabilities.experimentalApi`).
-- `thread/backgroundTerminals/terminate` - terminate one running background terminal by app-server `processId` (experimental; requires `capabilities.experimentalApi`).
-- `thread/rollback` - deprecated; drop the last N turns from the in-memory context and persist a rollback marker; returns the updated `thread`.
-- `turn/start` - add user input to a thread and begin Codex generation; responds with the initial `turn` and streams events. For `collaborationMode`, `settings.developer_instructions: null` means "use built-in instructions for the selected mode."
-- `thread/inject_items` - append raw Responses API items to a loaded thread's model-visible history without starting a user turn.
-- `turn/steer` - append user input to the active in-flight turn for a thread; returns the accepted `turnId`.
-- `turn/interrupt` - request cancellation of an in-flight turn; success is `{}` and the turn ends with `status: "interrupted"`.
-- `review/start` - kick off the Codex reviewer for a thread; emits `enteredReviewMode` and `exitedReviewMode` items.
-- `command/exec` - run a single command under the server sandbox without starting a thread/turn.
-- `command/exec/write` - write `stdin` bytes to a running `command/exec` session or close `stdin`.
-- `command/exec/resize` - resize a running PTY-backed `command/exec` session.
-- `command/exec/terminate` - stop a running `command/exec` session.
-- `command/exec/outputDelta` (notify) - emitted for base64-encoded stdout/stderr chunks from a streaming `command/exec` session.
-- `process/spawn` - start an explicit process session outside Codex's sandbox (experimental; requires `capabilities.experimentalApi`).
-- `process/writeStdin` - write stdin bytes to a running `process/spawn` session or close stdin (experimental).
-- `process/resizePty` - resize a running PTY-backed process session (experimental).
-- `process/kill` - terminate a running process session (experimental).
-- `process/outputDelta` and `process/exited` (notify) - emitted for streaming process output and process exit status (experimental).
-- `model/list` - list available models (set `includeHidden: true` to include entries with `hidden: true`) with effort options, optional `upgrade`, and `inputModalities`.
-- `modelProvider/capabilities/read` - read provider capability bounds for model/provider combinations.
-- `experimentalFeature/list` - list feature flags with lifecycle stage metadata and cursor pagination.
-- `experimentalFeature/enablement/set` - patch in-memory runtime settings for supported feature keys such as `apps` and `plugins`.
-- `environment/info` - experimental; connect to a configured execution environment and return its shell plus default working directory.
-- `permissionProfile/list` - list beta permission profiles and whether effective requirements allow them, with cursor pagination.
-- `collaborationMode/list` - list collaboration mode presets (experimental, no pagination).
-- `skills/list` - list skills for one or more `cwd` values (supports `forceReload` and optional `perCwdExtraUserRoots`).
-- `skills/extraRoots/set` - replace the process-level extra roots used to discover standalone skills without persisting them.
-- `skills/changed` (notify) - emitted when watched local skill files change.
-- `hooks/list` - list discovered lifecycle hooks for one or more `cwd` values.
-- `marketplace/add` - add a remote plugin marketplace and persist it into the user's marketplace config.
-- `marketplace/remove` - remove a configured marketplace and its installed marketplace root when present.
-- `marketplace/upgrade` - refresh a configured Git marketplace, or all configured Git marketplaces when you omit the marketplace name.
-- `plugin/list` - under development; list discovered plugin marketplaces and plugin state, including install/auth policy metadata, marketplace load errors, featured plugin ids, and local, Git, package-registry, or remote plugin source metadata. Summaries can include remote `version`, local `localVersion`, structured light/dark icons, and `installPolicySource`, which can be `null`, `WORKSPACE_SETTING`, or `IMPLICIT_CANONICAL_APP` for current remote rows. Don't call this method from production clients yet.
-- `plugin/read` - under development; read one plugin by marketplace path or remote marketplace name and plugin name, including bundled skills, apps, MCP server names, and a remote plugin `shareUrl` when the remote catalog provides one. Don't call this method from production clients yet.
-- `plugin/install` - under development; install a plugin from a marketplace path or remote marketplace name. Don't call this method from production clients yet.
-- `plugin/uninstall` - under development; uninstall an installed plugin. Don't call this method from production clients yet.
-- `plugin/skill/read` - read remote plugin skill Markdown on demand by remote marketplace, plugin id, and skill name.
-- `app/list` - list available apps (connectors) with pagination plus accessibility/enabled metadata.
-- `skills/config/write` - enable or disable skills by path.
-- `mcpServer/oauth/login` - start an OAuth login for a configured MCP server; returns an authorization URL and emits `mcpServer/oauthLogin/completed` on completion.
-- `tool/requestUserInput` - prompt the user with 1-3 short questions for a tool call (experimental); questions can set `isOther` for a free-form option.
-- `mcpServer/elicitation/request` (server request) - ask the client for structured form input or confirmation of a URL flow requested by an MCP server.
-- `item/permissions/requestApproval` (server request) - ask the client to grant a subset of network or filesystem permissions requested by the built-in `request_permissions` tool.
-- `config/mcpServer/reload` - reload MCP server configuration from disk and queue a refresh for loaded threads.
-- `mcpServerStatus/list` - list MCP servers, tools, resources, and auth status (cursor + limit pagination). Use `detail: "full"` for full data or `detail: "toolsAndAuthOnly"` to omit resources.
-- `mcpServer/resource/read` - read a single MCP resource through an initialized MCP server.
-- `mcpServer/tool/call` - call a tool on a thread's configured MCP server.
-- `mcpServer/startupStatus/updated` (notify) - emitted when a configured MCP server's startup status changes for a loaded thread.
-- `windowsSandbox/setupStart` - start Windows sandbox setup for `elevated` or `unelevated` mode; returns quickly and later emits `windowsSandbox/setupCompleted`.
-- `feedback/upload` - submit a feedback report (classification + optional reason/logs + conversation id, plus optional `extraLogFiles` attachments).
-- `config/read` - fetch the effective configuration on disk after resolving configuration layering.
-- `externalAgentConfig/detect` - detect external-agent artifacts that can be migrated with `includeHome` and optional `cwds`; each detected item includes `cwd` (`null` for home).
-- `externalAgentConfig/import` - apply selected external-agent migration items by passing explicit `migrationItems` with `cwd` (`null` for home). Supported item types include config, skills, `AGENTS.md`, plugins, MCP server config, subagents, hooks, commands, and sessions; non-empty imports emit `externalAgentConfig/import/progress` and `externalAgentConfig/import/completed` as work finishes. Plugin and session imports can complete asynchronously.
-- `config/value/write` - write a single configuration key/value to the user's `config.toml` on disk.
-- `config/batchWrite` - apply configuration edits atomically to the user's `config.toml` on disk.
-- `configRequirements/read` - fetch requirements from `requirements.toml` and/or MDM, including allow-lists, pinned `featureRequirements`, and residency/network requirements (or `null` if you haven't set any up).
-- `fs/readFile`, `fs/writeFile`, `fs/createDirectory`, `fs/getMetadata`, `fs/readDirectory`, `fs/remove`, `fs/copy`, `fs/watch`, `fs/unwatch`, and `fs/changed` (notify) - operate on absolute filesystem paths through the app-server v2 filesystem API.
-
-Plugin summaries include a `source` union. Local plugins return
-`{ "type": "local", "path": ... }`, Git-backed marketplace entries return
-`{ "type": "git", "url": ..., "path": ..., "refName": ..., "sha": ... }`,
-package-registry entries return
-`{ "type": "npm", "package": ..., "version": ..., "registry": ... }`, and
-remote catalog entries return `{ "type": "remote" }`. For remote-only catalog
-entries, `PluginMarketplaceEntry.path` can be `null`; pass
-`remoteMarketplaceName` instead of `marketplacePath` when reading or installing
-those plugins.
-
-## Models
-
-### List models (`model/list`)
-
-Call `model/list` to discover available models and their capabilities before rendering model or personality selectors.
-
-```json
-{ "method": "model/list", "id": 6, "params": { "limit": 20, "includeHidden": false } }
-{ "id": 6, "result": {
- "data": [{
- "id": "gpt-5.4",
- "model": "gpt-5.4",
- "displayName": "GPT-5.4",
- "hidden": false,
- "defaultReasoningEffort": "medium",
- "supportedReasoningEfforts": [{
- "reasoningEffort": "low",
- "description": "Lower latency"
- }],
- "inputModalities": ["text", "image"],
- "supportsPersonality": true,
- "isDefault": true
- }],
- "nextCursor": null
-} }
-```
-
-Each model entry can include:
-
-- `supportedReasoningEfforts` - supported effort options for the model.
-- `defaultReasoningEffort` - suggested default effort for clients.
-- `upgrade` - optional recommended upgrade model id for migration prompts in clients.
-- `upgradeInfo` - optional upgrade metadata for migration prompts in clients.
-- `hidden` - whether the model is hidden from the default picker list.
-- `inputModalities` - supported input types for the model (for example `text`, `image`).
-- `supportsPersonality` - whether the model supports personality-specific instructions such as `/personality`.
-- `isDefault` - whether the model is the recommended default.
-
-By default, `model/list` returns picker-visible models only. Set `includeHidden: true` if you need the full list and want to filter on the client side using `hidden`.
-
-When `inputModalities` is missing (older model catalogs), treat it as `["text", "image"]` for backward compatibility.
-
-### List experimental features (`experimentalFeature/list`)
-
-Use this endpoint to discover feature flags with metadata and lifecycle stage:
-
-```json
-{ "method": "experimentalFeature/list", "id": 7, "params": { "limit": 20 } }
-{ "id": 7, "result": {
- "data": [{
- "name": "unified_exec",
- "stage": "beta",
- "displayName": "Unified exec",
- "description": "Use the unified PTY-backed execution tool.",
- "announcement": "Beta rollout for improved command execution reliability.",
- "enabled": false,
- "defaultEnabled": false
- }],
- "nextCursor": null
-} }
-```
-
-`stage` can be `beta`, `underDevelopment`, `stable`, `deprecated`, or `removed`. For non-beta flags, `displayName`, `description`, and `announcement` may be `null`.
-
-### Inspect an execution environment (experimental)
-
-Use `environment/info` to inspect a configured remote environment before
-starting work there. The method requires `capabilities.experimentalApi = true`.
-
-```json
-{ "method": "environment/info", "id": 8, "params": { "environmentId": "devbox" } }
-{ "id": 8, "result": {
- "shell": { "name": "zsh", "path": "/bin/zsh" },
- "cwd": "file:///workspace/project"
-} }
-```
-
-`cwd` can be `null`. When present, it's a canonical `file:` URI that uses the
-environment's native path syntax. Unknown environment IDs and connection or
-protocol failures return request errors.
-
-## Threads
-
-- `thread/read` reads a stored thread without subscribing to it; set `includeTurns` to include turns.
-- `thread/turns/list` is experimental and pages through a stored thread's turn history without
- resuming it. Use `itemsView` to choose whether turn items are omitted,
- summarized, or fully loaded.
-- `thread/items/list` is experimental and pages through persisted thread items, optionally restricted to one turn.
-- `thread/list` supports cursor pagination plus `modelProviders`, `sourceKinds`, `archived`, `cwd`, `useStateDbOnly`, `searchTerm`, and experimental `parentThreadId` or `ancestorThreadId` filtering.
-- `thread/loaded/list` returns the thread IDs currently in memory.
-- `thread/archive` moves the thread's persisted JSONL log into the archived directory and attempts to archive spawned descendant thread logs that aren't already archived.
-- `thread/delete` permanently deletes a persisted active or archived thread and its spawned descendant threads.
-- `thread/metadata/update` patches stored thread metadata, currently including persisted `gitInfo`.
-- `thread/unsubscribe` unsubscribes the current connection from a loaded thread and can trigger `thread/closed` after an inactivity grace period.
-- `thread/unarchive` restores an archived thread rollout back into the active sessions directory.
-- `thread/compact/start` triggers compaction and returns `{}` immediately.
-- `thread/rollback` is deprecated. It drops the last N turns from the in-memory context and records a rollback marker in the thread's persisted JSONL log.
-- `thread/inject_items` appends raw Responses API items to a loaded thread's model-visible history without starting a user turn.
-
-### Start or resume a thread
-
-Start a fresh thread when you need a new Codex conversation.
-
-```json
-{ "method": "thread/start", "id": 10, "params": {
- "model": "gpt-5.4",
- "cwd": "/Users/me/project",
- "approvalPolicy": "never",
- "sandbox": "workspaceWrite",
- "personality": "friendly",
- "serviceName": "my_app_server_client"
-} }
-{ "id": 10, "result": {
- "thread": {
- "id": "thr_123",
- "sessionId": "thr_123",
- "preview": "",
- "ephemeral": false,
- "modelProvider": "openai",
- "createdAt": 1730910000
- }
-} }
-{ "method": "thread/started", "params": { "thread": { "id": "thr_123" } } }
-```
-
-`serviceName` is optional. Set it when you want app-server to tag thread-level metrics with your integration's service name.
-
-`thread/start`, `thread/resume`, and `thread/fork` return
-`instructionSources`, an array of loaded instruction-file paths. Each path uses
-its source environment's native absolute syntax, including for remote
-environments.
-
-Experimental clients can set `historyMode` on `thread/start` to `"legacy"`
-(the default) or `"paginated"`. Paginated thread creation isn't supported yet
-and returns JSON-RPC error `-32601`. App-server can list and read summaries for
-existing paginated records, but full-history reads, turn pagination, and resume
-fail closed until paginated history is supported.
-
-Beta clients that opt into `capabilities.experimentalApi` can pass a named
-permission-profile id in `permissions` instead of the legacy `sandbox` field.
-Don't send `permissions` and `sandbox` together. Use
-`permissionProfile/list` with the project `cwd` to discover available profiles
-and whether managed requirements allow each one.
-
-`thread.sessionId` identifies the current live session tree root. Root threads
-use their own thread id as the session id; forked threads keep the session id
-of the root they came from. Clients should read the session id from
-`thread.sessionId` instead of deriving it from the thread id.
-
-To continue a stored session, call `thread/resume` with the `thread.id` you recorded earlier. The response shape matches `thread/start`. You can also pass the same configuration overrides supported by `thread/start`, such as `personality`:
-
-```json
-{ "method": "thread/resume", "id": 11, "params": {
- "threadId": "thr_123",
- "personality": "friendly"
-} }
-{ "id": 11, "result": { "thread": { "id": "thr_123", "name": "Bug bash notes", "ephemeral": false } } }
-```
-
-Resuming a thread doesn't update `thread.updatedAt` (or the rollout file's modified time) by itself. The timestamp updates when you start a turn.
-
-If you mark an enabled MCP server as `required` in config and that server fails to initialize, `thread/start` and `thread/resume` fail instead of continuing without it.
-
-`dynamicTools` on `thread/start` is an experimental field (requires `capabilities.experimentalApi = true`). Codex persists these dynamic tools in the thread rollout metadata and restores them on `thread/resume` when you don't supply new dynamic tools.
-
-If you resume with a different model than the one recorded in the rollout, Codex emits a warning and applies a one-time model-switch instruction on the next turn.
-
-### Manage a thread goal
-
-Use `thread/goal/set`, `thread/goal/get`, and `thread/goal/clear` to manage the
-same persisted goal state surfaced by `/goal` in the TUI.
-
-```json
-{ "method": "thread/goal/set", "id": 13, "params": {
- "threadId": "thr_123",
- "objective": "Finish the migration and keep tests green",
- "status": "active",
- "tokenBudget": 40000
-} }
-{ "id": 13, "result": { "goal": {
- "threadId": "thr_123",
- "objective": "Finish the migration and keep tests green",
- "status": "active",
- "tokenBudget": 40000,
- "tokensUsed": 0,
- "timeUsedSeconds": 0
-} } }
-{ "method": "thread/goal/updated", "params": {
- "threadId": "thr_123",
- "goal": {
- "threadId": "thr_123",
- "objective": "Finish the migration and keep tests green",
- "status": "active",
- "tokenBudget": 40000,
- "tokensUsed": 0,
- "timeUsedSeconds": 0
- }
-} }
-```
-
-Goal objectives must be non-empty and at most 4,000 characters. Supplying a new
-objective replaces the goal and resets usage accounting. Supplying the current
-non-terminal objective, or omitting `objective`, updates status or token budget
-while preserving usage history.
-
-To branch from a stored session, call `thread/fork` with the `thread.id`. This creates a new thread id and emits a `thread/started` notification for it. Pass
-`lastTurnId` to copy history through that turn, inclusive, and omit later
-turns:
-
-```json
-{ "method": "thread/fork", "id": 12, "params": { "threadId": "thr_123", "lastTurnId": "turn_456" } }
-{ "id": 12, "result": { "thread": { "id": "thr_456", "sessionId": "thr_123", "forkedFromId": "thr_123" } } }
-{ "method": "thread/started", "params": { "thread": { "id": "thr_456" } } }
-```
-
-App-server rejects an in-progress `lastTurnId`. If you omit the field while the
-source thread is mid-turn, the fork records an interruption marker instead of
-retaining an unmarked partial turn.
-
-When a user-facing thread title has been set, app-server hydrates `thread.name` on `thread/list`, `thread/read`, `thread/resume`, `thread/unarchive`, and `thread/rollback` responses. `thread/start` and `thread/fork` may omit `name` (or return `null`) until a title is set later.
-
-### Read a stored thread (without resuming)
-
-Use `thread/read` when you want stored thread data but don't want to resume the thread or subscribe to its events.
-
-- `includeTurns` - when `true`, the response includes the thread's turns; when `false` or omitted, you get the thread summary only.
-- Returned `thread` objects include runtime `status` (`notLoaded`, `idle`, `systemError`, or `active` with `activeFlags`).
-
-```json
-{ "method": "thread/read", "id": 19, "params": { "threadId": "thr_123", "includeTurns": true } }
-{ "id": 19, "result": { "thread": { "id": "thr_123", "name": "Bug bash notes", "ephemeral": false, "status": { "type": "notLoaded" }, "turns": [] } } }
-```
-
-Unlike `thread/resume`, `thread/read` doesn't load the thread into memory or emit `thread/started`.
-
-### List thread turns
-
-`thread/turns/list` is experimental. Use it to page a stored thread's turn history without resuming it. Results default to newest-first so clients can fetch older turns with `nextCursor`. The response also includes `backwardsCursor`; pass it as `cursor` with `sortDirection: "asc"` to fetch turns newer than the first item from the earlier page.
-
-`itemsView` controls how much turn-item data the response includes:
-
-- `notLoaded` omits items.
-- `summary` returns summarized item data and is the default when omitted.
-- `full` returns full item data.
-
-```json
-{ "method": "thread/turns/list", "id": 20, "params": {
- "threadId": "thr_123",
- "limit": 50,
- "sortDirection": "desc",
- "itemsView": "summary"
-} }
-{ "id": 20, "result": {
- "data": [],
- "nextCursor": "older-turns-cursor-or-null",
- "backwardsCursor": "newer-turns-cursor-or-null"
-} }
-```
-
-`thread/items/list` is also experimental. It pages persisted items without
-resuming the thread. Pass `turnId` to restrict results to one turn, or omit it
-to page items across the thread. The active thread store must support item
-pagination; otherwise, the server returns an unsupported-method error.
-
-### List threads (with pagination & filters)
-
-`thread/list` lets you render a history UI. Results default to newest-first by `createdAt`. Filters apply before pagination. Pass any combination of:
-
-- `cursor` - opaque string from a prior response; omit for the first page.
-- `limit` - server defaults to a reasonable page size if unset.
-- `sortKey` - `created_at` (default), `updated_at`, or `recency_at`.
-- `sortDirection` - `desc` (default) or `asc`.
-- `modelProviders` - restrict results to specific providers; unset, null, or an empty array includes all providers.
-- `sourceKinds` - restrict results to specific thread sources. When omitted or `[]`, the server defaults to interactive sources only: `cli` and `vscode`.
-- `archived` - when `true`, list archived threads only. When `false` or omitted, list non-archived threads (default).
-- `cwd` - restrict results to threads whose session current working directory exactly matches this path, or one of the paths in an array. Relative paths resolve from the app-server process working directory.
-- `useStateDbOnly` - when `true`, return state database results without scanning JSONL thread logs to repair metadata. Omit it or pass `false` for the default scan-and-repair behavior.
-- `searchTerm` - restrict results to threads whose extracted title contains this case-sensitive text fragment.
-- `parentThreadId` - restrict results to direct child threads of the given parent thread. This filter is experimental and requires `capabilities.experimentalApi = true`.
-- `ancestorThreadId` - restrict results to spawned descendants of the given thread at any depth. This filter is experimental and requires `capabilities.experimentalApi = true`; don't combine it with `parentThreadId`.
-
-`sourceKinds` accepts the following values:
-
-- `cli`
-- `vscode`
-- `exec`
-- `appServer`
-- `subAgent`
-- `subAgentReview`
-- `subAgentCompact`
-- `subAgentThreadSpawn`
-- `subAgentOther`
-- `unknown`
-
-Example:
-
-```json
-{ "method": "thread/list", "id": 20, "params": {
- "cursor": null,
- "limit": 25,
- "sortKey": "created_at"
-} }
-{ "id": 20, "result": {
- "data": [
- { "id": "thr_a", "preview": "Create a TUI", "ephemeral": false, "modelProvider": "openai", "createdAt": 1730831111, "updatedAt": 1730831111, "name": "TUI prototype", "status": { "type": "notLoaded" } },
- { "id": "thr_b", "preview": "Fix tests", "ephemeral": true, "modelProvider": "openai", "createdAt": 1730750000, "updatedAt": 1730750000, "status": { "type": "notLoaded" } }
- ],
- "nextCursor": "opaque-token-or-null"
-} }
-```
-
-When `nextCursor` is `null`, you have reached the final page.
-
-### Update stored thread metadata
-
-Use `thread/metadata/update` to patch stored thread metadata without resuming the thread. Today this supports persisted `gitInfo`; omitted fields are left unchanged, and explicit `null` clears a stored value.
-
-```json
-{ "method": "thread/metadata/update", "id": 21, "params": {
- "threadId": "thr_123",
- "gitInfo": { "branch": "feature/sidebar-pr" }
-} }
-{ "id": 21, "result": {
- "thread": {
- "id": "thr_123",
- "gitInfo": { "sha": null, "branch": "feature/sidebar-pr", "originUrl": null }
- }
-} }
-```
-
-### Track thread status changes
-
-`thread/status/changed` is emitted whenever a loaded thread's runtime status changes. The payload includes `threadId` and the new `status`.
-
-```json
-{
- "method": "thread/status/changed",
- "params": {
- "threadId": "thr_123",
- "status": { "type": "active", "activeFlags": ["waitingOnApproval"] }
- }
-}
-```
-
-### List loaded threads
-
-`thread/loaded/list` returns thread IDs currently loaded in memory.
-
-```json
-{ "method": "thread/loaded/list", "id": 21 }
-{ "id": 21, "result": { "data": ["thr_123", "thr_456"] } }
-```
-
-### Unsubscribe from a loaded thread
-
-`thread/unsubscribe` removes the current connection's subscription to a thread. The response status is one of:
-
-- `unsubscribed` when the connection was subscribed and is now removed.
-- `notSubscribed` when the connection wasn't subscribed to that thread.
-- `notLoaded` when the thread isn't loaded.
-
-If this was the last subscriber, the server keeps the thread loaded until it has no subscribers and no thread activity for 30 minutes. When the grace period expires, app-server unloads the thread and emits a `thread/status/changed` transition to `notLoaded` plus `thread/closed`.
-
-```json
-{ "method": "thread/unsubscribe", "id": 22, "params": { "threadId": "thr_123" } }
-{ "id": 22, "result": { "status": "unsubscribed" } }
-```
-
-If the thread later expires:
-
-```json
-{ "method": "thread/status/changed", "params": {
- "threadId": "thr_123",
- "status": { "type": "notLoaded" }
-} }
-{ "method": "thread/closed", "params": { "threadId": "thr_123" } }
-```
-
-### Archive a thread
-
-Use `thread/archive` to move the persisted thread log (stored as a JSONL file on disk) into the archived sessions directory. Archiving a thread also attempts to archive spawned descendant threads that aren't already archived.
-
-```json
-{ "method": "thread/archive", "id": 22, "params": { "threadId": "thr_b" } }
-{ "id": 22, "result": {} }
-{ "method": "thread/archived", "params": { "threadId": "thr_b" } }
-{ "method": "thread/archived", "params": { "threadId": "thr_child" } }
-```
-
-Archived threads won't appear in future calls to `thread/list` unless you pass `archived: true`. The server emits one `thread/archived` notification for each thread it actually archives; if a spawned descendant can't be archived, the request can still succeed without an archived notification for that descendant.
-
-### Delete a thread
-
-Use `thread/delete` to permanently delete a persisted active or archived thread
-and its spawned descendant threads. The server removes existing rollout files and
-associated metadata before returning success; missing rollout files are treated
-as already deleted. Ephemeral root threads can't be deleted.
-
-```json
-{ "method": "thread/delete", "id": 23, "params": { "threadId": "thr_b" } }
-{ "id": 23, "result": {} }
-{ "method": "thread/deleted", "params": { "threadId": "thr_b" } }
-{ "method": "thread/deleted", "params": { "threadId": "thr_child" } }
-```
-
-### Unarchive a thread
-
-Use `thread/unarchive` to move an archived thread rollout back into the active sessions directory.
-
-```json
-{ "method": "thread/unarchive", "id": 24, "params": { "threadId": "thr_b" } }
-{ "id": 24, "result": { "thread": { "id": "thr_b", "name": "Bug bash notes" } } }
-{ "method": "thread/unarchived", "params": { "threadId": "thr_b" } }
-```
-
-### Trigger thread compaction
-
-Use `thread/compact/start` to trigger manual history compaction for a thread. The request returns immediately with `{}`.
-
-App-server emits progress as standard `turn/*` and `item/*` notifications on the same `threadId`, including a `contextCompaction` item lifecycle (`item/started` then `item/completed`).
-
-```json
-{ "method": "thread/compact/start", "id": 25, "params": { "threadId": "thr_b" } }
-{ "id": 25, "result": {} }
-```
-
-### Run a thread shell command
-
-Use `thread/shellCommand` for user-initiated shell commands that belong to a thread. The request returns immediately with `{}` while progress streams through standard `turn/*` and `item/*` notifications.
-
-This API runs outside the sandbox with full access and doesn't inherit the thread sandbox policy. Clients should expose it only for explicit user-initiated commands.
-
-If the thread already has an active turn, the command runs as an auxiliary action on that turn and its formatted output is injected into the turn's message stream. If the thread is idle, app-server starts a standalone turn for the shell command.
-
-```json
-{ "method": "thread/shellCommand", "id": 26, "params": { "threadId": "thr_b", "command": "git status --short" } }
-{ "id": 26, "result": {} }
-```
-
-### Clean background terminals
-
-Use `thread/backgroundTerminals/clean` to stop all running background terminals associated with a thread. This method is experimental and requires `capabilities.experimentalApi = true`.
-
-```json
-{ "method": "thread/backgroundTerminals/clean", "id": 27, "params": { "threadId": "thr_b" } }
-{ "id": 27, "result": {} }
-```
-
-Use `thread/backgroundTerminals/list` to inspect running background terminals
-for a loaded thread. The request supports standard `cursor` and `limit`
-pagination, and the returned `processId` is the app-server process id. This
-method is experimental and requires `capabilities.experimentalApi = true`:
-
-```json
-{ "method": "thread/backgroundTerminals/list", "id": 28, "params": { "threadId": "thr_b" } }
-{ "id": 28, "result": { "data": [
- {
- "itemId": "item_456",
- "processId": "42",
- "command": "python3 -m http.server",
- "cwd": "/workspace",
- "osPid": null,
- "cpuPercent": null,
- "rssKb": null
- }
-], "nextCursor": null } }
-```
-
-Use `thread/backgroundTerminals/terminate` with that `processId` to stop one
-background terminal. This method is experimental and requires
-`capabilities.experimentalApi = true`:
-
-```json
-{ "method": "thread/backgroundTerminals/terminate", "id": 29, "params": { "threadId": "thr_b", "processId": "42" } }
-{ "id": 29, "result": { "terminated": true } }
-```
-
-### Roll back recent turns
-
-`thread/rollback` is deprecated and will be removed. It removes the last
-`numTurns` entries from the in-memory context and persists a rollback marker in
-the rollout log. The returned `thread` includes `turns` populated after the
-rollback.
-
-```json
-{ "method": "thread/rollback", "id": 30, "params": { "threadId": "thr_b", "numTurns": 1 } }
-{ "id": 30, "result": { "thread": { "id": "thr_b", "name": "Bug bash notes", "ephemeral": false } } }
-```
-
-## Turns
-
-The `input` field accepts a list of items:
-
-- `{ "type": "text", "text": "Explain this diff" }`
-- `{ "type": "image", "url": "https://.../design.png" }`
-- `{ "type": "localImage", "path": "/tmp/screenshot.png" }`
-
-You can override configuration settings per turn (model, effort, personality, `cwd`, sandbox policy, summary). When specified, these settings become the defaults for later turns on the same thread. `outputSchema` applies only to the current turn. For `sandboxPolicy.type = "externalSandbox"`, set `networkAccess` to `restricted` or `enabled`; for `workspaceWrite`, `networkAccess` remains a boolean.
-
-For `turn/start.collaborationMode`, `settings.developer_instructions: null` means "use built-in instructions for the selected mode" rather than clearing mode instructions.
-
-### Sandbox read access (`ReadOnlyAccess`)
-
-`sandboxPolicy` supports explicit read-access controls:
-
-- `readOnly`: optional `access` (`{ "type": "fullAccess" }` by default, or restricted roots).
-- `workspaceWrite`: optional `readOnlyAccess` (`{ "type": "fullAccess" }` by default, or restricted roots).
-
-Restricted read access shape:
-
-```json
-{
- "type": "restricted",
- "includePlatformDefaults": true,
- "readableRoots": ["/Users/me/shared-read-only"]
-}
-```
-
-On macOS, `includePlatformDefaults: true` appends a curated platform-default Seatbelt policy for restricted-read sessions. This improves tool compatibility without broadly allowing all of `/System`.
-
-Examples:
-
-```json
-{ "type": "readOnly", "access": { "type": "fullAccess" } }
-```
-
-```json
-{
- "type": "workspaceWrite",
- "writableRoots": ["/Users/me/project"],
- "readOnlyAccess": {
- "type": "restricted",
- "includePlatformDefaults": true,
- "readableRoots": ["/Users/me/shared-read-only"]
- },
- "networkAccess": false
-}
-```
-
-### Start a turn
-
-```json
-{ "method": "turn/start", "id": 30, "params": {
- "threadId": "thr_123",
- "input": [ { "type": "text", "text": "Run tests" } ],
- "cwd": "/Users/me/project",
- "approvalPolicy": "unlessTrusted",
- "sandboxPolicy": {
- "type": "workspaceWrite",
- "writableRoots": ["/Users/me/project"],
- "networkAccess": true
- },
- "model": "gpt-5.4",
- "effort": "medium",
- "summary": "concise",
- "personality": "friendly",
- "outputSchema": {
- "type": "object",
- "properties": { "answer": { "type": "string" } },
- "required": ["answer"],
- "additionalProperties": false
- }
-} }
-{ "id": 30, "result": { "turn": { "id": "turn_456", "status": "inProgress", "items": [], "error": null } } }
-```
-
-### Inject items into a thread
-
-Use `thread/inject_items` to append prebuilt Responses API items to a loaded thread's prompt history without starting a user turn. These items are persisted to the rollout and included in subsequent model requests.
-
-```json
-{ "method": "thread/inject_items", "id": 31, "params": {
- "threadId": "thr_123",
- "items": [
- {
- "type": "message",
- "role": "assistant",
- "content": [{ "type": "output_text", "text": "Previously computed context." }]
- }
- ]
-} }
-{ "id": 31, "result": {} }
-```
-
-### Steer an active turn
-
-Use `turn/steer` to append more user input to the active in-flight turn.
-
-- Include `expectedTurnId`; it must match the active turn id.
-- The request fails if there is no active turn on the thread.
-- `turn/steer` doesn't emit a new `turn/started` notification.
-- `turn/steer` doesn't accept turn-level overrides (`model`, `cwd`, `sandboxPolicy`, or `outputSchema`).
-
-```json
-{ "method": "turn/steer", "id": 32, "params": {
- "threadId": "thr_123",
- "input": [ { "type": "text", "text": "Actually focus on failing tests first." } ],
- "expectedTurnId": "turn_456"
-} }
-{ "id": 32, "result": { "turnId": "turn_456" } }
-```
-
-### Start a turn (invoke a skill)
-
-Invoke a skill explicitly by including `$` in the text input and adding a `skill` input item alongside it.
-
-```json
-{ "method": "turn/start", "id": 33, "params": {
- "threadId": "thr_123",
- "input": [
- { "type": "text", "text": "$skill-creator Add a new skill for triaging flaky CI and include step-by-step usage." },
- { "type": "skill", "name": "skill-creator", "path": "/Users/me/.codex/skills/skill-creator/SKILL.md" }
- ]
-} }
-{ "id": 33, "result": { "turn": { "id": "turn_457", "status": "inProgress", "items": [], "error": null } } }
-```
-
-### Interrupt a turn
-
-```json
-{ "method": "turn/interrupt", "id": 31, "params": { "threadId": "thr_123", "turnId": "turn_456" } }
-{ "id": 31, "result": {} }
-```
-
-On success, the turn finishes with `status: "interrupted"`.
-
-## Review
-
-`review/start` runs the Codex reviewer for a thread and streams review items. Targets include:
-
-- `uncommittedChanges`
-- `baseBranch` (diff against a branch)
-- `commit` (review a specific commit)
-- `custom` (free-form instructions)
-
-Use `delivery: "inline"` (default) to run the review on the existing thread, or `delivery: "detached"` to fork a new review thread.
-
-Example request/response:
-
-```json
-{ "method": "review/start", "id": 40, "params": {
- "threadId": "thr_123",
- "delivery": "inline",
- "target": { "type": "commit", "sha": "1234567deadbeef", "title": "Polish tui colors" }
-} }
-{ "id": 40, "result": {
- "turn": {
- "id": "turn_900",
- "status": "inProgress",
- "items": [
- { "type": "userMessage", "id": "turn_900", "content": [ { "type": "text", "text": "Review commit 1234567: Polish tui colors" } ] }
- ],
- "error": null
- },
- "reviewThreadId": "thr_123"
-} }
-```
-
-For a detached review, use `"delivery": "detached"`. The response is the same shape, but `reviewThreadId` will be the id of the new review thread (different from the original `threadId`). The server also emits a `thread/started` notification for that new thread before streaming the review turn.
-
-Codex streams the usual `turn/started` notification followed by an `item/started` with an `enteredReviewMode` item:
-
-```json
-{
- "method": "item/started",
- "params": {
- "item": {
- "type": "enteredReviewMode",
- "id": "turn_900",
- "review": "current changes"
- }
- }
-}
-```
-
-When the reviewer finishes, the server emits `item/started` and `item/completed` containing an `exitedReviewMode` item with the final review text:
-
-```json
-{
- "method": "item/completed",
- "params": {
- "item": {
- "type": "exitedReviewMode",
- "id": "turn_900",
- "review": "Looks solid overall..."
- }
- }
-}
-```
-
-Use this notification to render the reviewer output in your client.
-
-## Process execution
-
-`process/*` is an experimental, explicit process-control API. It requires
-`capabilities.experimentalApi = true` and runs outside Codex's sandbox. Use it
-only when your client intentionally exposes local process control without a
-sandbox.
-
-Start a process with `process/spawn` and provide a `processHandle`, then use
-that handle for stdin, resize, and kill requests. Output streams through
-`process/outputDelta` notifications and completion streams through
-`process/exited`.
-
-```json
-{ "method": "process/spawn", "id": 48, "params": {
- "command": ["python3", "-m", "pytest", "-q"],
- "processHandle": "pytest-1",
- "cwd": "/Users/me/project",
- "tty": true
-} }
-{ "id": 48, "result": {} }
-{ "method": "process/outputDelta", "params": {
- "processHandle": "pytest-1",
- "stream": "stdout",
- "deltaBase64": "Li4u"
-} }
-{ "method": "process/exited", "params": {
- "processHandle": "pytest-1",
- "exitCode": 0
-} }
-```
-
-Use `process/writeStdin` with `deltaBase64`, `closeStdin`, or both to send
-input. Use `process/resizePty` for PTY resize events and `process/kill` to
-terminate a running process.
-
-## Command execution
-
-`command/exec` runs a single command (`argv` array) under the server sandbox without creating a thread.
-
-```json
-{ "method": "command/exec", "id": 50, "params": {
- "command": ["ls", "-la"],
- "cwd": "/Users/me/project",
- "sandboxPolicy": { "type": "workspaceWrite" },
- "timeoutMs": 10000
-} }
-{ "id": 50, "result": { "exitCode": 0, "stdout": "...", "stderr": "" } }
-```
-
-Use `sandboxPolicy.type = "externalSandbox"` if you already sandbox the server process and want Codex to skip its own sandbox enforcement. For external sandbox mode, set `networkAccess` to `restricted` (default) or `enabled`. For `readOnly` and `workspaceWrite`, use the same optional `access` / `readOnlyAccess` structure shown above.
-
-Notes:
-
-- The server rejects empty `command` arrays.
-- `sandboxPolicy` accepts the same shape used by `turn/start` (for example, `dangerFullAccess`, `readOnly`, `workspaceWrite`, `externalSandbox`).
-- When omitted, `timeoutMs` falls back to the server default.
-- Set `tty: true` for PTY-backed sessions, and use `processId` when you plan to follow up with `command/exec/write`, `command/exec/resize`, or `command/exec/terminate`.
-- Set `streamStdoutStderr: true` to receive `command/exec/outputDelta` notifications while the command is running.
-
-### Read admin requirements (`configRequirements/read`)
-
-Use `configRequirements/read` to inspect the effective admin requirements loaded from `requirements.toml` and/or MDM.
-
-```json
-{ "method": "configRequirements/read", "id": 52, "params": {} }
-{ "id": 52, "result": {
- "requirements": {
- "allowedApprovalPolicies": ["onRequest", "unlessTrusted"],
- "allowedSandboxModes": ["readOnly", "workspaceWrite"],
- "featureRequirements": {
- "personality": true,
- "unified_exec": false
- },
- "network": {
- "enabled": true,
- "allowedDomains": ["api.openai.com"],
- "allowUnixSockets": ["/tmp/example.sock"],
- "dangerouslyAllowAllUnixSockets": false
- }
- }
-} }
-```
-
-`result.requirements` is `null` when no requirements are configured. See the docs on [`requirements.toml`](https://learn.chatgpt.com/docs/config-file/config-reference#requirementstoml) for details on supported keys and values.
-
-### Windows sandbox setup (`windowsSandbox/setupStart`)
-
-Custom Windows clients can trigger sandbox setup asynchronously instead of blocking on startup checks.
-
-```json
-{ "method": "windowsSandbox/setupStart", "id": 53, "params": { "mode": "elevated" } }
-{ "id": 53, "result": { "started": true } }
-```
-
-App-server starts setup in the background and later emits a completion notification:
-
-```json
-{
- "method": "windowsSandbox/setupCompleted",
- "params": { "mode": "elevated", "success": true, "error": null }
-}
-```
-
-Modes:
-
-- `elevated` - run the elevated Windows sandbox setup path.
-- `unelevated` - run the legacy setup/preflight path.
-
-## Filesystem
-
-The v2 filesystem APIs operate on absolute paths. Use `fs/watch` when a client needs to invalidate UI state after a file or directory changes.
-
-```json
-{ "method": "fs/watch", "id": 54, "params": {
- "watchId": "0195ec6b-1d6f-7c2e-8c7a-56f2c4a8b9d1",
- "path": "/Users/me/project/.git/HEAD"
-} }
-{ "id": 54, "result": { "path": "/Users/me/project/.git/HEAD" } }
-{ "method": "fs/changed", "params": {
- "watchId": "0195ec6b-1d6f-7c2e-8c7a-56f2c4a8b9d1",
- "changedPaths": ["/Users/me/project/.git/HEAD"]
-} }
-{ "method": "fs/unwatch", "id": 55, "params": {
- "watchId": "0195ec6b-1d6f-7c2e-8c7a-56f2c4a8b9d1"
-} }
-{ "id": 55, "result": {} }
-```
-
-Watching a file emits `fs/changed` for that file path, including updates delivered by replace or rename operations.
-
-## Events
-
-Event notifications are the server-initiated stream for thread lifecycles, turn lifecycles, and the items within them. After you start or resume a thread, keep reading the active transport stream for `thread/started`, `thread/archived`, `thread/unarchived`, `thread/closed`, `thread/status/changed`, `turn/*`, `item/*`, and `serverRequest/resolved` notifications.
-
-### Notification opt-out
-
-Clients can suppress specific notifications per connection by sending exact method names in `initialize.params.capabilities.optOutNotificationMethods`.
-
-- Exact-match only: `item/agentMessage/delta` suppresses only that method.
-- Unknown method names are ignored.
-- Applies to the current `thread/*`, `turn/*`, `item/*`, and related v2 notifications.
-- Doesn't apply to requests, responses, or errors.
-
-### Fuzzy file search events (experimental)
-
-The fuzzy file search session API emits per-query notifications:
-
-- `fuzzyFileSearch/sessionUpdated` - `{ sessionId, query, files }` with the current matches for the active query.
-- `fuzzyFileSearch/sessionCompleted` - `{ sessionId }` once indexing and matching for that query completes.
-
-### Warning events
-
-- `configWarning` - `{ summary, details?, path?, range? }` for recoverable
- configuration or initialization problems.
-- `warning` - `{ threadId?, message }` for non-fatal runtime warnings.
-
-### Windows sandbox setup events
-
-- `windowsSandbox/setupCompleted` - `{ mode, success, error }` emitted after a `windowsSandbox/setupStart` request finishes.
-
-### Turn events
-
-- `turn/started` - `{ turn }` with the turn id, empty `items`, and `status: "inProgress"`.
-- `turn/completed` - `{ turn }` where `turn.status` is `completed`, `interrupted`, or `failed`; failures carry `{ error: { message, codexErrorInfo?, additionalDetails? } }`.
-- `turn/diff/updated` - `{ threadId, turnId, diff }` with the latest aggregated unified diff across every file change in the turn.
-- `turn/plan/updated` - `{ turnId, explanation?, plan }` whenever the agent shares or changes its plan; each `plan` entry is `{ step, status }` with `status` in `pending`, `inProgress`, or `completed`.
-- `hook/started` and `hook/completed` - `{ threadId, turnId?, run }` when a lifecycle hook starts and when its final run summary is available.
-- `model/safetyBuffering/updated` - `{ threadId, turnId, model, useCases, reasons, showBufferingUi, fasterModel }` when a response enters transient safety buffering.
-- `model/rerouted` - `{ threadId, turnId, fromModel, toModel, reason }` when the service routes a request to another model.
-- `model/verification` - `{ threadId, turnId, verifications }` when the service requires additional account verification.
-- `thread/tokenUsage/updated` - usage updates for the active thread.
-
-`turn/diff/updated` and `turn/plan/updated` currently include empty `items` arrays even when item events stream. Use `item/*` notifications as the source of truth for turn items.
-
-### Items
-
-`ThreadItem` is the tagged union carried in turn responses and `item/*` notifications. Common item types include:
-
-- `userMessage` - `{id, content}` where `content` is a list of user inputs (`text`, `image`, or `localImage`).
-- `agentMessage` - `{id, text, phase?}` containing the accumulated agent reply. When present, `phase` uses Responses API wire values (`commentary`, `final_answer`).
-- `plan` - `{id, text}` containing proposed plan text in plan mode. Treat the final `plan` item from `item/completed` as authoritative.
-- `reasoning` - `{id, summary, content}` where `summary` holds streamed reasoning summaries and `content` holds raw reasoning blocks.
-- `commandExecution` - `{id, command, cwd, status, commandActions, aggregatedOutput?, exitCode?, durationMs?}`.
-- `fileChange` - `{id, changes, status}` describing proposed edits; `changes` list `{path, kind, diff}`.
-- `mcpToolCall` - `{id, server, tool, status, arguments, appContext?, pluginId?, result?, error?}`. For trusted MCP apps, `appContext` can include `connectorId`, `linkId`, `resourceUri`, `appName`, `templateId`, and the stable connector `actionName`. Older persisted items can omit newer metadata. Use `appContext.resourceUri` instead of the deprecated top-level `mcpAppResourceUri`.
-- `dynamicToolCall` - `{id, tool, arguments, status, contentItems?, success?, durationMs?}` for client-executed dynamic tool invocations.
-- `collabToolCall` - `{id, tool, status, senderThreadId, receiverThreadId?, newThreadId?, prompt?, agentStatus?}`.
-- `webSearch` - `{id, query, action?}` for web search requests issued by the agent.
-- `imageView` - `{id, path}` emitted when the agent invokes the image viewer tool.
-- `enteredReviewMode` - `{id, review}` sent when the reviewer starts.
-- `exitedReviewMode` - `{id, review}` emitted when the reviewer finishes.
-- `contextCompaction` - `{id}` emitted when Codex compacts the conversation history.
-
-For `webSearch.action`, the action `type` can be `search` (`query?`, `queries?`), `openPage` (`url?`), or `findInPage` (`url?`, `pattern?`).
-
-The app server deprecates the legacy `thread/compacted` notification; use the `contextCompaction` item instead.
-
-All items emit two shared lifecycle events:
-
-- `item/started` - emits the full `item` when a new unit of work begins; the `item.id` matches the `itemId` used by deltas.
-- `item/completed` - sends the final `item` once work finishes; treat this as the authoritative state.
-
-### Item deltas
-
-- `item/agentMessage/delta` - appends streamed text for the agent message.
-- `item/plan/delta` - streams proposed plan text. The final `plan` item may not exactly equal the concatenated deltas.
-- `item/reasoning/summaryTextDelta` - streams readable reasoning summaries; `summaryIndex` increments when a new summary section opens.
-- `item/reasoning/summaryPartAdded` - marks a boundary between reasoning summary sections.
-- `item/reasoning/textDelta` - streams raw reasoning text (when supported by the model).
-- `item/commandExecution/outputDelta` - streams stdout/stderr for a command; append deltas in order.
-- `item/fileChange/outputDelta` - deprecated compatibility notification for legacy `apply_patch` text output. Current app-server versions no longer emit it; use `fileChange` items and `turn/diff/updated` instead.
-
-## Errors
-
-If a turn fails, the server emits an `error` event with `{ error: { message, codexErrorInfo?, additionalDetails? } }` and then finishes the turn with `status: "failed"`. When an upstream HTTP status is available, it appears in `codexErrorInfo.httpStatusCode`.
-
-Common `codexErrorInfo` values include:
-
-- `ContextWindowExceeded`
-- `UsageLimitExceeded`
-- `HttpConnectionFailed` (4xx/5xx upstream errors)
-- `ResponseStreamConnectionFailed`
-- `ResponseStreamDisconnected`
-- `ResponseTooManyFailedAttempts`
-- `BadRequest`, `Unauthorized`, `SandboxError`, `InternalServerError`, `Other`
-
-When an upstream HTTP status is available, the server forwards it in `httpStatusCode` on the relevant `codexErrorInfo` variant.
-
-## Approvals
-
-Depending on a user's Codex settings, command execution and file changes may require approval. The app-server sends a server-initiated JSON-RPC request to the client, and the client responds with a decision payload.
-
-- Command execution decisions: `accept`, `acceptForSession`, `decline`, `cancel`, or `{ "acceptWithExecpolicyAmendment": { "execpolicy_amendment": ["cmd", "..."] } }`.
-- File change decisions: `accept`, `acceptForSession`, `decline`, `cancel`.
-
-- Requests include `threadId` and `turnId` - use them to scope UI state to the active conversation.
-- The server resumes or declines the work and ends the item with `item/completed`.
-
-### Command execution approvals
-
-Order of messages:
-
-1. `item/started` shows the pending `commandExecution` item with `command`, `cwd`, and other fields.
-2. `item/commandExecution/requestApproval` includes `itemId`, `threadId`, `turnId`, optional `reason`, optional `command`, optional `cwd`, optional `commandActions`, optional `proposedExecpolicyAmendment`, optional `networkApprovalContext`, and optional `availableDecisions`. When `initialize.params.capabilities.experimentalApi = true`, the payload can also include experimental `additionalPermissions` describing requested per-command sandbox access. Any filesystem paths inside `additionalPermissions` are absolute on the wire.
-3. Client responds with one of the command execution approval decisions above.
-4. `serverRequest/resolved` confirms that the pending request has been answered or cleared.
-5. `item/completed` returns the final `commandExecution` item with `status: completed | failed | declined`.
-
-When `networkApprovalContext` is present, the prompt is for managed network access (not a general shell-command approval). The current v2 schema exposes the target `host` and `protocol`; clients should render a network-specific prompt and not rely on `command` being a user-meaningful shell command preview.
-
-Codex groups concurrent network approval prompts by destination (`host`, protocol, and port). The app-server may therefore send one prompt that unblocks multiple queued requests to the same destination, while different ports on the same host are treated separately.
-
-### File change approvals
-
-Order of messages:
-
-1. `item/started` emits a `fileChange` item with proposed `changes` and `status: "inProgress"`.
-2. `item/fileChange/requestApproval` includes `itemId`, `threadId`, `turnId`, optional `reason`, and optional `grantRoot`.
-3. Client responds with one of the file change approval decisions above.
-4. `serverRequest/resolved` confirms that the pending request has been answered or cleared.
-5. `item/completed` returns the final `fileChange` item with `status: completed | failed | declined`.
-
-### `tool/requestUserInput`
-
-When the client responds to `item/tool/requestUserInput`, app-server emits `serverRequest/resolved` with `{ threadId, requestId }`. If the pending request is cleared by turn start, turn completion, or turn interruption before the client answers, the server emits the same notification for that cleanup.
-
-Request params include `autoResolutionMs` as an integer millisecond timeout or
-`null`. When present, host clients can resolve the prompt automatically after that
-interval if the user doesn't answer.
-
-### Permission requests
-
-The built-in `request_permissions` tool sends
-`item/permissions/requestApproval` with the `threadId`, `turnId`, `itemId`,
-`environmentId`, `cwd`, optional `reason`, and requested network or filesystem
-permissions. Respond with `permissions` containing only the granted subset.
-Set `scope` to `"session"` to persist the grant for later turns in the same
-session; omit it or use `"turn"` for a turn-scoped grant. Permissions that
-weren't requested are ignored.
-
-### MCP server elicitation requests
-
-An MCP server can interrupt a turn with `mcpServer/elicitation/request`. The
-request includes `threadId`, an optional `turnId`, `serverName`, and one of
-these request shapes:
-
-- `mode: "form"` or `mode: "openai/form"`, with `message` and
- `requestedSchema`.
-- `mode: "url"`, with `message`, `url`, and `elicitationId`.
-
-Respond with `action: "accept"` and the requested `content`, or with
-`action: "decline"` or `"cancel"` and `content: null`. App-server then emits
-`serverRequest/resolved`. To receive the `openai/form` variant, opt in with
-`initialize.params.capabilities.mcpServerOpenaiFormElicitation`.
-
-### Dynamic tool calls (experimental)
-
-`dynamicTools` on `thread/start` and the corresponding `item/tool/call` request or response flow are experimental APIs.
-
-Dynamic tool names and namespace names must follow Responses API naming
-constraints. Avoid reserved namespace names used by built-in Codex tools.
-
-When a dynamic tool is invoked during a turn, app-server emits:
-
-1. `item/started` with `item.type = "dynamicToolCall"`, `status = "inProgress"`, plus `tool` and `arguments`.
-2. `item/tool/call` as a server request to the client.
-3. The client response payload with returned content items.
-4. `item/completed` with `item.type = "dynamicToolCall"`, the final `status`, and any returned `contentItems` or `success` value.
-
-### MCP tool-call approvals (apps)
-
-App (connector) tool calls can also require approval. When an app tool call has side effects, the server may elicit approval with `tool/requestUserInput` and options such as **Accept**, **Decline**, and **Cancel**. Destructive tool annotations always trigger approval even when the tool also advertises less-privileged hints. If the user declines or cancels, the related `mcpToolCall` item completes with an error instead of running the tool.
-
-## Skills
-
-Invoke a skill by including `$` in the user text input. Add a `skill` input item (recommended) so the server injects full skill instructions instead of relying on the model to resolve the name.
-
-```json
-{
- "method": "turn/start",
- "id": 101,
- "params": {
- "threadId": "thread-1",
- "input": [
- {
- "type": "text",
- "text": "$skill-creator Add a new skill for triaging flaky CI."
- },
- {
- "type": "skill",
- "name": "skill-creator",
- "path": "/Users/me/.codex/skills/skill-creator/SKILL.md"
- }
- ]
- }
-}
-```
-
-If you omit the `skill` item, the model will still parse the `$` marker and try to locate the skill, which can add latency.
-
-Example:
-
-```
-$skill-creator Add a new skill for triaging flaky CI and include step-by-step usage.
-```
-
-Use `skills/list` to fetch available skills (optionally scoped by `cwds`, with `forceReload`). You can also include `perCwdExtraUserRoots` to scan extra absolute paths as `user` scope for specific `cwd` values. App-server ignores entries whose `cwd` isn't present in `cwds`. `skills/list` may reuse a cached result per `cwd`; set `forceReload: true` to refresh from disk. When present, the server reads `interface` and `dependencies` from `SKILL.json`.
-
-```json
-{ "method": "skills/list", "id": 25, "params": {
- "cwds": ["/Users/me/project", "/Users/me/other-project"],
- "forceReload": true,
- "perCwdExtraUserRoots": [
- {
- "cwd": "/Users/me/project",
- "extraUserRoots": ["/Users/me/shared-skills"]
- }
- ]
-} }
-{ "id": 25, "result": {
- "data": [{
- "cwd": "/Users/me/project",
- "skills": [
- {
- "name": "skill-creator",
- "description": "Create or update a Codex skill",
- "enabled": true,
- "interface": {
- "displayName": "Skill Creator",
- "shortDescription": "Create or update a Codex skill"
- },
- "dependencies": {
- "tools": [
- {
- "type": "env_var",
- "value": "GITHUB_TOKEN",
- "description": "GitHub API token"
- },
- {
- "type": "mcp",
- "value": "github",
- "transport": "streamable_http",
- "url": "https://example.com/mcp"
- }
- ]
- }
- }
- ],
- "errors": []
- }]
-} }
-```
-
-The server also emits `skills/changed` notifications when watched local skill files change. Treat this as an invalidation signal and rerun `skills/list` with your current params when needed.
-
-To enable or disable a skill by path:
-
-```json
-{
- "method": "skills/config/write",
- "id": 26,
- "params": {
- "path": "/Users/me/.codex/skills/skill-creator/SKILL.md",
- "enabled": false
- }
-}
-```
-
-## Apps (connectors)
-
-Use `app/list` to fetch available apps. In the CLI/TUI, `/apps` is the user-facing picker; in custom clients, call `app/list` directly. Each entry includes both `isAccessible` (available to the user) and `isEnabled` (enabled in `config.toml`) so clients can distinguish install/access from local enabled state. App entries can also include optional `branding`, `appMetadata`, and `labels` fields.
-
-```json
-{ "method": "app/list", "id": 50, "params": {
- "cursor": null,
- "limit": 50,
- "threadId": "thread-1",
- "forceRefetch": false
-} }
-{ "id": 50, "result": {
- "data": [
- {
- "id": "demo-app",
- "name": "Demo App",
- "description": "Example connector for documentation.",
- "logoUrl": "https://example.com/demo-app.png",
- "logoUrlDark": null,
- "distributionChannel": null,
- "branding": null,
- "appMetadata": null,
- "labels": null,
- "installUrl": "https://chatgpt.com/apps/demo-app/demo-app",
- "isAccessible": true,
- "isEnabled": true
- }
- ],
- "nextCursor": null
-} }
-```
-
-If you provide `threadId`, app feature gating (`features.apps`) uses that thread's config snapshot. When omitted, app-server uses the latest global config.
-
-`app/list` returns after both accessible apps and directory apps load. Set `forceRefetch: true` to bypass app caches and fetch fresh data. Cache entries are only replaced when refreshes succeed.
-
-The server also emits `app/list/updated` notifications whenever either source (accessible apps or directory apps) finishes loading. Each notification includes the latest merged app list.
-
-```json
-{
- "method": "app/list/updated",
- "params": {
- "data": [
- {
- "id": "demo-app",
- "name": "Demo App",
- "description": "Example connector for documentation.",
- "logoUrl": "https://example.com/demo-app.png",
- "logoUrlDark": null,
- "distributionChannel": null,
- "branding": null,
- "appMetadata": null,
- "labels": null,
- "installUrl": "https://chatgpt.com/apps/demo-app/demo-app",
- "isAccessible": true,
- "isEnabled": true
- }
- ]
- }
-}
-```
-
-Invoke an app by inserting `$` in the text input and adding a `mention` input item with the `app://` path (recommended).
-
-```json
-{
- "method": "turn/start",
- "id": 51,
- "params": {
- "threadId": "thread-1",
- "input": [
- {
- "type": "text",
- "text": "$demo-app Pull the latest updates from the team."
- },
- {
- "type": "mention",
- "name": "Demo App",
- "path": "app://demo-app"
- }
- ]
- }
-}
-```
-
-### Config RPC examples for app settings
-
-Use `config/read`, `config/value/write`, and `config/batchWrite` to inspect or update app controls in `config.toml`.
-
-Read the effective app config shape (including `_default` and per-tool overrides):
-
-```json
-{ "method": "config/read", "id": 60, "params": { "includeLayers": false } }
-{ "id": 60, "result": {
- "config": {
- "apps": {
- "_default": {
- "enabled": true,
- "destructive_enabled": true,
- "open_world_enabled": true,
- "approvals_reviewer": "user",
- "default_tools_approval_mode": "auto"
- },
- "google_drive": {
- "enabled": true,
- "destructive_enabled": false,
- "approvals_reviewer": "auto_review",
- "default_tools_approval_mode": "prompt",
- "tools": {
- "files/delete": { "enabled": false, "approval_mode": "approve" }
- }
- }
- }
- }
-} }
-```
-
-`apps._default.approvals_reviewer` sets the reviewer for all apps unless a
-per-app value overrides it. When both are omitted, the app inherits the
-top-level `approvals_reviewer` value. `apps._default.default_tools_approval_mode`
-sets the fallback approval mode for tools without a per-app or per-tool
-override. Managed approval-mode requirements override tool approval-mode
-settings.
-
-Update a single app setting:
-
-```json
-{
- "method": "config/value/write",
- "id": 61,
- "params": {
- "keyPath": "apps.google_drive.default_tools_approval_mode",
- "value": "prompt",
- "mergeStrategy": "replace"
- }
-}
-```
-
-Apply multiple app edits atomically:
-
-```json
-{
- "method": "config/batchWrite",
- "id": 62,
- "params": {
- "edits": [
- {
- "keyPath": "apps._default.destructive_enabled",
- "value": false,
- "mergeStrategy": "upsert"
- },
- {
- "keyPath": "apps.google_drive.tools.files/delete.approval_mode",
- "value": "approve",
- "mergeStrategy": "upsert"
- }
- ]
- }
-}
-```
-
-### Detect and import external agent config
-
-Use `externalAgentConfig/detect` to discover external-agent artifacts that can be migrated, then pass the selected entries to `externalAgentConfig/import`.
-
-Detection example:
-
-```json
-{ "method": "externalAgentConfig/detect", "id": 63, "params": {
- "includeHome": true,
- "cwds": ["/Users/me/project"]
-} }
-{ "id": 63, "result": {
- "items": [
- {
- "itemType": "AGENTS_MD",
- "description": "Import /Users/me/project/CLAUDE.md to /Users/me/project/AGENTS.md.",
- "cwd": "/Users/me/project"
- },
- {
- "itemType": "SKILLS",
- "description": "Copy skill folders from /Users/me/.claude/skills to /Users/me/.agents/skills.",
- "cwd": null
- }
- ]
-} }
-```
-
-Import example:
-
-```json
-{ "method": "externalAgentConfig/import", "id": 64, "params": {
- "migrationItems": [
- {
- "itemType": "AGENTS_MD",
- "description": "Import /Users/me/project/CLAUDE.md to /Users/me/project/AGENTS.md.",
- "cwd": "/Users/me/project"
- }
- ],
- "source": "claude-code"
-} }
-{ "id": 64, "result": { "importId": "8ae96ff3-3425-4f4c-8772-b6fd61502868" } }
-```
-
-The optional top-level `source` import parameter labels the product that
-produced the selected migration items.
-
-The server emits `externalAgentConfig/import/progress` as item types complete,
-and `externalAgentConfig/import/completed` after all synchronous and background
-imports finish. These notifications include the same `importId` from the
-response and `itemTypeResults` with per-type `successes` and `failures`.
-Completion may arrive immediately after the response or after background remote
-imports complete.
-
-```json
-{ "method": "externalAgentConfig/import/progress", "params": {
- "importId": "8ae96ff3-3425-4f4c-8772-b6fd61502868",
- "itemTypeResults": [
- {
- "itemType": "AGENTS_MD",
- "successes": [
- { "itemType": "AGENTS_MD", "cwd": "/Users/me/project", "source": null, "target": "/Users/me/project/AGENTS.md" }
- ],
- "failures": []
- }
- ]
-} }
-{ "method": "externalAgentConfig/import/completed", "params": {
- "importId": "8ae96ff3-3425-4f4c-8772-b6fd61502868",
- "itemTypeResults": [
- {
- "itemType": "AGENTS_MD",
- "successes": [
- { "itemType": "AGENTS_MD", "cwd": "/Users/me/project", "source": null, "target": "/Users/me/project/AGENTS.md" }
- ],
- "failures": []
- }
- ]
-} }
-```
-
-Read prior completed imports:
-
-```json
-{ "method": "externalAgentConfig/import/readHistories", "id": 65 }
-{ "id": 65, "result": { "data": [
- {
- "importId": "8ae96ff3-3425-4f4c-8772-b6fd61502868",
- "completedAtMs": 1781784000000,
- "successes": [
- { "itemType": "AGENTS_MD", "cwd": "/Users/me/project", "source": null, "target": "/Users/me/project/AGENTS.md" }
- ],
- "failures": []
- }
-] } }
-```
-
-Supported `itemType` values are `AGENTS_MD`, `CONFIG`, `SKILLS`, `PLUGINS`,
-`MCP_SERVER_CONFIG`, `SUBAGENTS`, `HOOKS`, `COMMANDS`, and `SESSIONS`. For
-`PLUGINS` items, `details.plugins` lists each `marketplaceName` and the
-`pluginNames` Codex can try to migrate. Detection returns only items that still
-have work to do. For example, Codex skips AGENTS migration when `AGENTS.md`
-already exists and is non-empty, and skill imports don't overwrite existing
-skill directories.
-
-When detecting plugins from `.claude/settings.json`, Codex reads configured
-marketplace sources from `extraKnownMarketplaces`. If `enabledPlugins` contains
-plugins from `claude-plugins-official` but the marketplace source is missing,
-Codex infers `anthropics/claude-plugins-official` as the source.
-
-## Auth endpoints
-
-The JSON-RPC auth/account surface exposes request/response methods plus server-initiated notifications (no `id`). Use these to determine auth state, start or cancel logins, logout, inspect ChatGPT rate limits, and notify workspace owners about depleted credits or usage limits.
-
-### Authentication modes
-
-Codex supports these authentication modes. `account/updated.authMode` shows the active mode and includes the current ChatGPT `planType` when available. `account/read` also reports account and plan details.
-
-- **API key (`apikey`)** - the caller supplies an OpenAI API key with `type: "apiKey"`, and Codex stores it for API requests.
-- **ChatGPT managed (`chatgpt`)** - Codex owns the ChatGPT OAuth flow, persists tokens, and refreshes them automatically. Start with `type: "chatgpt"` for the browser flow or `type: "chatgptDeviceCode"` for the device-code flow.
-- **ChatGPT external tokens (`chatgptAuthTokens`)** - experimental and intended for host apps that already own the user's ChatGPT auth lifecycle. The host app supplies an `accessToken`, `chatgptAccountId`, and optional `chatgptPlanType` directly, and must refresh the token when asked.
-- **Amazon Bedrock** - `account/read` reports Bedrock accounts as `type: "amazonBedrock"` and indicates whether credentials come from a Codex-managed Bedrock API key (`credentialSource: "codexManaged"`) or the external AWS credential chain (`credentialSource: "awsManaged"`). `account/updated.authMode` uses `bedrockApiKey` for Codex-managed Bedrock API keys.
-
-### API overview
-
-- `account/read` - fetch current account info; optionally refresh tokens.
-- `account/login/start` - begin login (`apiKey`, `chatgpt`, `chatgptDeviceCode`, or experimental `chatgptAuthTokens`).
-- `account/login/completed` (notify) - emitted when a login attempt finishes (success or error).
-- `account/login/cancel` - cancel a pending managed ChatGPT login by `loginId`.
-- `account/logout` - sign out; triggers `account/updated`.
-- `account/updated` (notify) - emitted whenever auth mode changes (`authMode`: `apikey`, `chatgpt`, `chatgptAuthTokens`, `agentIdentity`, `personalAccessToken`, `bedrockApiKey`, or `null`) and includes `planType` when available.
-- `account/chatgptAuthTokens/refresh` (server request) - request fresh externally managed ChatGPT tokens after an authorization error.
-- `account/rateLimits/read` - fetch ChatGPT rate limits.
-- `account/rateLimits/updated` (notify) - emitted whenever a user's ChatGPT rate limits change.
-- `account/sendAddCreditsNudgeEmail` - ask ChatGPT to email a workspace owner about depleted credits or a reached usage limit.
-- `account/rateLimitResetCredit/consume` - consume one earned rate-limit reset using a caller-provided `idempotencyKey` value.
-- `account/usage/read` - fetch ChatGPT account token-activity summaries and daily buckets.
-- `account/workspaceMessages/read` - fetch active workspace messages, including notification headlines when available.
-- `mcpServer/oauthLogin/completed` (notify) - emitted after a `mcpServer/oauth/login` flow finishes; payload includes `{ name, threadId, success, error? }`. `threadId` can be `null` for app-scoped or plugin OAuth flows.
-- `mcpServer/startupStatus/updated` (notify) - emitted when a configured MCP server's startup status changes; payload includes `{ threadId, name, status, error, failureReason }`. `threadId` is `null` for app-scoped startup. On failed startup, `failureReason: "reauthenticationRequired"` means stored OAuth credentials expired and couldn't be refreshed, so the client should offer to reconnect the server.
-
-### 1) Check auth state
-
-Request:
-
-```json
-{ "method": "account/read", "id": 1, "params": { "refreshToken": false } }
-```
-
-Response examples:
-
-```json
-{ "id": 1, "result": { "account": null, "requiresOpenaiAuth": false } }
-```
-
-```json
-{ "id": 1, "result": { "account": null, "requiresOpenaiAuth": true } }
-```
-
-```json
-{
- "id": 1,
- "result": { "account": { "type": "apiKey" }, "requiresOpenaiAuth": true }
-}
-```
-
-```json
-{
- "id": 1,
- "result": {
- "account": {
- "type": "amazonBedrock",
- "credentialSource": "codexManaged"
- },
- "requiresOpenaiAuth": false
- }
-}
-```
-
-```json
-{
- "id": 1,
- "result": {
- "account": {
- "type": "amazonBedrock",
- "credentialSource": "awsManaged"
- },
- "requiresOpenaiAuth": false
- }
-}
-```
-
-```json
-{
- "id": 1,
- "result": {
- "account": {
- "type": "chatgpt",
- "email": "user@example.com",
- "planType": "pro"
- },
- "requiresOpenaiAuth": true
- }
-}
-```
-
-Field notes:
-
-- `refreshToken` (boolean): set `true` to force a token refresh in managed ChatGPT mode. In external token mode (`chatgptAuthTokens`), app-server ignores this flag.
-- `email` is `null` when the ChatGPT account doesn't have an email address.
-- `requiresOpenaiAuth` reflects the active provider; when `false`, Codex can run without OpenAI credentials.
-- Amazon Bedrock reports `credentialSource: "codexManaged"` when it uses a
- Bedrock API key managed by Codex. It reports `credentialSource: "awsManaged"`
- for the external AWS credential path. This identifies the selected credential
- source; it doesn't validate that the AWS credential chain can resolve
- credentials.
-
-### 2) Log in with an API key
-
-1. Send:
-
-```json
- {
- "method": "account/login/start",
- "id": 2,
- "params": { "type": "apiKey", "apiKey": "sk-..." }
- }
-```
-
-2. Expect:
-
-```json
- { "id": 2, "result": { "type": "apiKey" } }
-```
-
-3. Notifications:
-
-```json
- {
- "method": "account/login/completed",
- "params": { "loginId": null, "success": true, "error": null }
- }
-```
-
-```json
- {
- "method": "account/updated",
- "params": { "authMode": "apikey", "planType": null }
- }
-```
-
-### 3) Log in with ChatGPT (browser flow)
-
-1. Start:
-
-```json
- {
- "method": "account/login/start",
- "id": 3,
- "params": {
- "type": "chatgpt",
- "useHostedLoginSuccessPage": true,
- "appBrand": "chatgpt"
- }
- }
-```
-
-By default, a successful browser callback redirects to a local success page.
-Set `useHostedLoginSuccessPage: true` to use the hosted success page when
-organization setup isn't required. With hosted success enabled, `appBrand`
-can be `"codex"` or `"chatgpt"`; omitted or `null` values default to
-`"codex"`.
-
-```json
- {
- "id": 3,
- "result": {
- "type": "chatgpt",
- "loginId": "",
- "authUrl": "https://chatgpt.com/...&redirect_uri=http%3A%2F%2Flocalhost%3A%2Fauth%2Fcallback"
- }
- }
-```
-
-2. Open `authUrl` in a browser; the app-server hosts the local callback.
-3. Wait for notifications:
-
-```json
- {
- "method": "account/login/completed",
- "params": { "loginId": "", "success": true, "error": null }
- }
-```
-
-```json
- {
- "method": "account/updated",
- "params": { "authMode": "chatgpt", "planType": "plus" }
- }
-```
-
-### 3b) Log in with ChatGPT (device-code flow)
-
-Use this flow when your client owns the sign-in ceremony or when a browser callback is brittle.
-
-1. Start:
-
-```json
- {
- "method": "account/login/start",
- "id": 4,
- "params": { "type": "chatgptDeviceCode" }
- }
-```
-
-```json
- {
- "id": 4,
- "result": {
- "type": "chatgptDeviceCode",
- "loginId": "",
- "verificationUrl": "https://auth.openai.com/codex/device",
- "userCode": "ABCD-1234"
- }
- }
-```
-
-2. Show `verificationUrl` and `userCode` to the user; the frontend owns the UX.
-3. Wait for notifications:
-
-```json
- {
- "method": "account/login/completed",
- "params": { "loginId": "", "success": true, "error": null }
- }
-```
-
-```json
- {
- "method": "account/updated",
- "params": { "authMode": "chatgpt", "planType": "plus" }
- }
-```
-
-### 3c) Log in with externally managed ChatGPT tokens (`chatgptAuthTokens`)
-
-Use this experimental mode only when a host application owns the user's ChatGPT auth lifecycle and supplies tokens directly. Clients must set `capabilities.experimentalApi = true` during `initialize` before using this login type.
-
-1. Send:
-
-```json
- {
- "method": "account/login/start",
- "id": 7,
- "params": {
- "type": "chatgptAuthTokens",
- "accessToken": "",
- "chatgptAccountId": "org-123",
- "chatgptPlanType": "business"
- }
- }
-```
-
-2. Expect:
-
-```json
- { "id": 7, "result": { "type": "chatgptAuthTokens" } }
-```
-
-3. Notifications:
-
-```json
- {
- "method": "account/login/completed",
- "params": { "loginId": null, "success": true, "error": null }
- }
-```
-
-```json
- {
- "method": "account/updated",
- "params": { "authMode": "chatgptAuthTokens", "planType": "business" }
- }
-```
-
-When the server receives a `401 Unauthorized`, it may request refreshed tokens from the host app:
-
-```json
-{
- "method": "account/chatgptAuthTokens/refresh",
- "id": 8,
- "params": { "reason": "unauthorized", "previousAccountId": "org-123" }
-}
-{ "id": 8, "result": { "accessToken": "", "chatgptAccountId": "org-123", "chatgptPlanType": "business" } }
-```
-
-The server retries the original request after a successful refresh response. Requests time out after about 10 seconds.
-
-### 4) Cancel a ChatGPT login
-
-```json
-{ "method": "account/login/cancel", "id": 4, "params": { "loginId": "" } }
-{ "method": "account/login/completed", "params": { "loginId": "", "success": false, "error": "..." } }
-```
-
-### 5) Logout
-
-```json
-{ "method": "account/logout", "id": 5 }
-{ "id": 5, "result": {} }
-{ "method": "account/updated", "params": { "authMode": null, "planType": null } }
-```
-
-### 6) Rate limits (ChatGPT)
-
-```json
-{ "method": "account/rateLimits/read", "id": 6 }
-{ "id": 6, "result": {
- "rateLimits": {
- "limitId": "codex",
- "limitName": null,
- "primary": { "usedPercent": 25, "windowDurationMins": 15, "resetsAt": 1730947200 },
- "secondary": null,
- "rateLimitReachedType": null
- },
- "rateLimitsByLimitId": {
- "codex": {
- "limitId": "codex",
- "limitName": null,
- "primary": { "usedPercent": 25, "windowDurationMins": 15, "resetsAt": 1730947200 },
- "secondary": null,
- "rateLimitReachedType": null
- },
- "codex_other": {
- "limitId": "codex_other",
- "limitName": "codex_other",
- "primary": { "usedPercent": 42, "windowDurationMins": 60, "resetsAt": 1730950800 },
- "secondary": null,
- "rateLimitReachedType": null
- }
- },
- "rateLimitResetCredits": {
- "availableCount": 2,
- "credits": [{
- "id": "RateLimitResetCredit_1",
- "resetType": "codexRateLimits",
- "status": "available",
- "grantedAt": 1781654400,
- "expiresAt": 1784246400,
- "title": "Rate-limit reset",
- "description": "Reset an eligible Codex rate-limit window."
- }]
- }
-} }
-{ "method": "account/rateLimits/updated", "params": {
- "rateLimits": {
- "limitId": "codex",
- "primary": { "usedPercent": 31, "windowDurationMins": 15, "resetsAt": 1730948100 }
- }
-} }
-```
-
-Field notes:
-
-- `rateLimits` is the backward-compatible single-bucket view.
-- `rateLimitsByLimitId` (when present) is the multi-bucket view keyed by metered `limit_id` (for example `codex`).
-- `limitId` is the metered bucket identifier.
-- `limitName` is an optional user-facing label for the bucket.
-- `usedPercent` is current usage within the quota window.
-- `windowDurationMins` is the quota window length.
-- `resetsAt` is a Unix timestamp (seconds) for the next reset.
-- `planType` is included when the server returns the ChatGPT plan associated with a bucket.
-- `credits` is included when the server returns remaining workspace credit details.
-- `rateLimitReachedType` identifies the server-classified limit state when one has been reached.
-- `rateLimitResetCredits` contains the available earned-reset count when the service provides it; otherwise it's `null`.
-- `rateLimitResetCredits.credits` is `null` when only the count is known. An empty array means the service fetched details and returned no available credits. The service can cap the detail rows, so `availableCount` is authoritative.
-- Each detail row includes an opaque `id`, `resetType`, `status`, `grantedAt`, `expiresAt` (which can be `null`), `title` (which can be `null`), and `description` (which can be `null`).
-- Fetch `account/rateLimits/read` after consuming a reset.
-
-### 7) Token usage (ChatGPT)
-
-Use `account/usage/read` to fetch ChatGPT token-activity summary fields and
-optional daily buckets.
-
-```json
-{ "method": "account/usage/read", "id": 7 }
-{ "id": 7, "result": {
- "summary": {
- "lifetimeTokens": 1234567,
- "peakDailyTokens": 45678,
- "longestRunningTurnSec": 540,
- "currentStreakDays": 8,
- "longestStreakDays": 14
- },
- "dailyUsageBuckets": [
- { "startDate": "2026-06-18", "tokens": 12345 }
- ]
-} }
-```
-
-Field notes:
-
-- `summary` values may be `null` when the service hasn't returned that metric.
-- `dailyUsageBuckets` may be `null`; when present, each bucket includes `startDate` and `tokens`.
-- The endpoint requires authentication backed by Codex services. ChatGPT,
- external ChatGPT tokens, agent identity, and personal access token auth work;
- API-key-only and Bedrock auth don't.
-
-### 8) Earned rate-limit resets (ChatGPT)
-
-Use `account/rateLimitResetCredit/consume` to consume one earned reset.
-
-```json
-{ "method": "account/rateLimitResetCredit/consume", "id": 8, "params": { "idempotencyKey": "8ae96ff3-3425-4f4c-8772-b6fd61502868", "creditId": "RateLimitResetCredit_1" } }
-{ "id": 8, "result": { "outcome": "reset" } }
-```
-
-Field notes:
-
-- `idempotencyKey` must be non-empty. Use a UUID for each logical redemption attempt and reuse the same value when retrying that attempt.
-- `creditId` is optional. When provided, it must be a non-empty opaque ID from `account/rateLimits/read`. When omitted, the service selects the next available credit.
-- `reset` means a credit was consumed.
-- `alreadyRedeemed` means the same redemption completed previously. Treat it as an idempotent success and refresh account limits.
-- `nothingToReset` means there is no eligible rate-limit window to reset.
-- `noCredit` means the account has no earned reset credits available.
-- Fetch `account/rateLimits/read` after consuming a reset instead of inferring updated windows from this response.
-
-### 9) Notify a workspace owner about a limit
-
-Use `account/sendAddCreditsNudgeEmail` to ask ChatGPT to email a workspace owner when credits are depleted or a usage limit has been reached.
-
-```json
-{ "method": "account/sendAddCreditsNudgeEmail", "id": 9, "params": { "creditType": "credits" } }
-{ "id": 9, "result": { "status": "sent" } }
-```
-
-Use `creditType: "credits"` when workspace credits are depleted, or `creditType: "usage_limit"` when the workspace usage limit has been reached. If the owner was already notified recently, the response status is `cooldown_active`.
-
-### 10) Workspace messages (ChatGPT)
-
-Use `account/workspaceMessages/read` to fetch active messages for the current
-workspace, including notification headlines when available.
-
-```json
-{ "method": "account/workspaceMessages/read", "id": 10 }
-{ "id": 10, "result": { "featureEnabled": true, "messages": [
- { "messageId": "msg_123", "messageType": "headline", "messageBody": "Workspace maintenance starts at 5pm.", "createdAt": 1781395200, "archivedAt": null }
-] } }
-```
\ No newline at end of file
diff --git a/canvas-agent/package-lock.json b/canvas-agent/package-lock.json
deleted file mode 100644
index 023df71..0000000
--- a/canvas-agent/package-lock.json
+++ /dev/null
@@ -1,1658 +0,0 @@
-{
- "name": "@basketikun/canvas-agent",
- "version": "0.3.1",
- "lockfileVersion": 3,
- "requires": true,
- "packages": {
- "": {
- "name": "@basketikun/canvas-agent",
- "version": "0.3.1",
- "license": "MIT",
- "dependencies": {
- "@modelcontextprotocol/sdk": "^1.12.1",
- "@openai/codex": "0.146.0",
- "express": "^5.1.0",
- "gray-matter": "^4.0.3",
- "strip-ansi": "7.2.0",
- "winston": "^3.19.0",
- "yaml": "^2.9.0",
- "zod": "^3.25.0"
- },
- "bin": {
- "canvas-agent": "dist/index.js"
- },
- "devDependencies": {
- "@types/express": "^5.0.0",
- "@types/node": "^22.0.0",
- "tsx": "^4.19.0",
- "typescript": "^5.8.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@colors/colors": {
- "version": "1.6.0",
- "license": "MIT",
- "engines": {
- "node": ">=0.1.90"
- }
- },
- "node_modules/@dabh/diagnostics": {
- "version": "2.0.8",
- "license": "MIT",
- "dependencies": {
- "@so-ric/colorspace": "^1.1.6",
- "enabled": "2.0.x",
- "kuler": "^2.0.0"
- }
- },
- "node_modules/@esbuild/darwin-arm64": {
- "version": "0.28.1",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@hono/node-server": {
- "version": "1.19.14",
- "license": "MIT",
- "engines": {
- "node": ">=18.14.1"
- },
- "peerDependencies": {
- "hono": "^4"
- }
- },
- "node_modules/@modelcontextprotocol/sdk": {
- "version": "1.29.0",
- "license": "MIT",
- "dependencies": {
- "@hono/node-server": "^1.19.9",
- "ajv": "^8.17.1",
- "ajv-formats": "^3.0.1",
- "content-type": "^1.0.5",
- "cors": "^2.8.5",
- "cross-spawn": "^7.0.5",
- "eventsource": "^3.0.2",
- "eventsource-parser": "^3.0.0",
- "express": "^5.2.1",
- "express-rate-limit": "^8.2.1",
- "hono": "^4.11.4",
- "jose": "^6.1.3",
- "json-schema-typed": "^8.0.2",
- "pkce-challenge": "^5.0.0",
- "raw-body": "^3.0.0",
- "zod": "^3.25 || ^4.0",
- "zod-to-json-schema": "^3.25.1"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "@cfworker/json-schema": "^4.1.1",
- "zod": "^3.25 || ^4.0"
- },
- "peerDependenciesMeta": {
- "@cfworker/json-schema": {
- "optional": true
- },
- "zod": {
- "optional": false
- }
- }
- },
- "node_modules/@openai/codex": {
- "version": "0.146.0",
- "resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.146.0.tgz",
- "integrity": "sha512-yG3sPWNda/2YAIQIDq9MrrjoCTIQ7rxYM5IasrG3VBcuhCLTkgeg/JzqmJq1V98RE4MJ5jCxDXXQlOjrditFRw==",
- "license": "Apache-2.0",
- "bin": {
- "codex": "bin/codex.js"
- },
- "engines": {
- "node": ">=16"
- },
- "optionalDependencies": {
- "@openai/codex-darwin-arm64": "npm:@openai/codex@0.146.0-darwin-arm64",
- "@openai/codex-darwin-x64": "npm:@openai/codex@0.146.0-darwin-x64",
- "@openai/codex-linux-arm64": "npm:@openai/codex@0.146.0-linux-arm64",
- "@openai/codex-linux-x64": "npm:@openai/codex@0.146.0-linux-x64",
- "@openai/codex-win32-arm64": "npm:@openai/codex@0.146.0-win32-arm64",
- "@openai/codex-win32-x64": "npm:@openai/codex@0.146.0-win32-x64"
- }
- },
- "node_modules/@openai/codex-darwin-arm64": {
- "name": "@openai/codex",
- "version": "0.146.0-darwin-arm64",
- "resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.146.0-darwin-arm64.tgz",
- "integrity": "sha512-nb61yX4r5L6Z0dlC4o3u0GAK1YCd4TUvjaB382bajDoh84V+uv2hTBIVZ++fgXWV9yoeuNrNnNcn7GoTGOe2Tg==",
- "cpu": [
- "arm64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=16"
- }
- },
- "node_modules/@openai/codex-darwin-x64": {
- "name": "@openai/codex",
- "version": "0.146.0-darwin-x64",
- "resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.146.0-darwin-x64.tgz",
- "integrity": "sha512-hTQR5jy/ObfTf1MDnuJCZJAe+SljKE8DDwQWN6lDFgjsPhMQz852U2tILt8Ei+G5GkQSzemHYKl2AYPwW0Y5xw==",
- "cpu": [
- "x64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=16"
- }
- },
- "node_modules/@openai/codex-linux-arm64": {
- "name": "@openai/codex",
- "version": "0.146.0-linux-arm64",
- "resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.146.0-linux-arm64.tgz",
- "integrity": "sha512-qiYDxkkEFnXG7joadJW6Q+XcgyDXCpGdpa9nk/c+i0gEomur1j7bHvx12NfWWCF/y8Tqri6ay+FLuC2MjdehtA==",
- "cpu": [
- "arm64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=16"
- }
- },
- "node_modules/@openai/codex-linux-x64": {
- "name": "@openai/codex",
- "version": "0.146.0-linux-x64",
- "resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.146.0-linux-x64.tgz",
- "integrity": "sha512-fswvyGprAPCMiOEue/7MKMk7pCjh9kZIJfJX5i9atmfnmGYbYCcUhZsEH9LEP0+0t5xyPqDbfNXY7NSxIVuXxA==",
- "cpu": [
- "x64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=16"
- }
- },
- "node_modules/@openai/codex-win32-arm64": {
- "name": "@openai/codex",
- "version": "0.146.0-win32-arm64",
- "resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.146.0-win32-arm64.tgz",
- "integrity": "sha512-EW6zdjDe+SLX2Iw+xymJ5+Pz2+DGexdstfFHXh4Ub+TfJsQPiMjGfZfNaoWgdJ2FsqSIzVKu2+G0KCMGYz2W8g==",
- "cpu": [
- "arm64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=16"
- }
- },
- "node_modules/@openai/codex-win32-x64": {
- "name": "@openai/codex",
- "version": "0.146.0-win32-x64",
- "resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.146.0-win32-x64.tgz",
- "integrity": "sha512-b3lxMYeR0+IhstNo4JjX1P9cPc1xwVcCVkPd1lD1wpWPJ0SBhpIkPczwbu3ZRkJcdyl342+rgyf4DUrbZLdrGA==",
- "cpu": [
- "x64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=16"
- }
- },
- "node_modules/@so-ric/colorspace": {
- "version": "1.1.6",
- "license": "MIT",
- "dependencies": {
- "color": "^5.0.2",
- "text-hex": "1.0.x"
- }
- },
- "node_modules/@types/body-parser": {
- "version": "1.19.6",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/connect": "*",
- "@types/node": "*"
- }
- },
- "node_modules/@types/connect": {
- "version": "3.4.38",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/node": "*"
- }
- },
- "node_modules/@types/express": {
- "version": "5.0.6",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/body-parser": "*",
- "@types/express-serve-static-core": "^5.0.0",
- "@types/serve-static": "^2"
- }
- },
- "node_modules/@types/express-serve-static-core": {
- "version": "5.1.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/node": "*",
- "@types/qs": "*",
- "@types/range-parser": "*",
- "@types/send": "*"
- }
- },
- "node_modules/@types/http-errors": {
- "version": "2.0.5",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/node": {
- "version": "22.19.21",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "undici-types": "~6.21.0"
- }
- },
- "node_modules/@types/qs": {
- "version": "6.15.1",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/range-parser": {
- "version": "1.2.7",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/send": {
- "version": "1.2.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/node": "*"
- }
- },
- "node_modules/@types/serve-static": {
- "version": "2.2.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/http-errors": "*",
- "@types/node": "*"
- }
- },
- "node_modules/@types/triple-beam": {
- "version": "1.3.5",
- "license": "MIT"
- },
- "node_modules/accepts": {
- "version": "2.0.0",
- "license": "MIT",
- "dependencies": {
- "mime-types": "^3.0.0",
- "negotiator": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/ajv": {
- "version": "8.20.0",
- "license": "MIT",
- "dependencies": {
- "fast-deep-equal": "^3.1.3",
- "fast-uri": "^3.0.1",
- "json-schema-traverse": "^1.0.0",
- "require-from-string": "^2.0.2"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/epoberezkin"
- }
- },
- "node_modules/ajv-formats": {
- "version": "3.0.1",
- "license": "MIT",
- "dependencies": {
- "ajv": "^8.0.0"
- },
- "peerDependencies": {
- "ajv": "^8.0.0"
- },
- "peerDependenciesMeta": {
- "ajv": {
- "optional": true
- }
- }
- },
- "node_modules/ansi-regex": {
- "version": "6.2.2",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
- "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-regex?sponsor=1"
- }
- },
- "node_modules/argparse": {
- "version": "1.0.10",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
- "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
- "license": "MIT",
- "dependencies": {
- "sprintf-js": "~1.0.2"
- }
- },
- "node_modules/async": {
- "version": "3.2.6",
- "license": "MIT"
- },
- "node_modules/body-parser": {
- "version": "2.2.2",
- "license": "MIT",
- "dependencies": {
- "bytes": "^3.1.2",
- "content-type": "^1.0.5",
- "debug": "^4.4.3",
- "http-errors": "^2.0.0",
- "iconv-lite": "^0.7.0",
- "on-finished": "^2.4.1",
- "qs": "^6.14.1",
- "raw-body": "^3.0.1",
- "type-is": "^2.0.1"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
- "node_modules/bytes": {
- "version": "3.1.2",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/call-bind-apply-helpers": {
- "version": "1.0.2",
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "function-bind": "^1.1.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/call-bound": {
- "version": "1.0.4",
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.2",
- "get-intrinsic": "^1.3.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/color": {
- "version": "5.0.3",
- "license": "MIT",
- "dependencies": {
- "color-convert": "^3.1.3",
- "color-string": "^2.1.3"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/color-convert": {
- "version": "3.1.3",
- "license": "MIT",
- "dependencies": {
- "color-name": "^2.0.0"
- },
- "engines": {
- "node": ">=14.6"
- }
- },
- "node_modules/color-name": {
- "version": "2.1.1",
- "license": "MIT",
- "engines": {
- "node": ">=12.20"
- }
- },
- "node_modules/color-string": {
- "version": "2.1.4",
- "license": "MIT",
- "dependencies": {
- "color-name": "^2.0.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/content-disposition": {
- "version": "1.1.0",
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
- "node_modules/content-type": {
- "version": "1.0.5",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/cookie": {
- "version": "0.7.2",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/cookie-signature": {
- "version": "1.2.2",
- "license": "MIT",
- "engines": {
- "node": ">=6.6.0"
- }
- },
- "node_modules/cors": {
- "version": "2.8.6",
- "license": "MIT",
- "dependencies": {
- "object-assign": "^4",
- "vary": "^1"
- },
- "engines": {
- "node": ">= 0.10"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
- "node_modules/cross-spawn": {
- "version": "7.0.6",
- "license": "MIT",
- "dependencies": {
- "path-key": "^3.1.0",
- "shebang-command": "^2.0.0",
- "which": "^2.0.1"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/debug": {
- "version": "4.4.3",
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/depd": {
- "version": "2.0.0",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/dunder-proto": {
- "version": "1.0.1",
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.1",
- "es-errors": "^1.3.0",
- "gopd": "^1.2.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/ee-first": {
- "version": "1.1.1",
- "license": "MIT"
- },
- "node_modules/enabled": {
- "version": "2.0.0",
- "license": "MIT"
- },
- "node_modules/encodeurl": {
- "version": "2.0.0",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/es-define-property": {
- "version": "1.0.1",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-errors": {
- "version": "1.3.0",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-object-atoms": {
- "version": "1.1.2",
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/esbuild": {
- "version": "0.28.1",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "bin": {
- "esbuild": "bin/esbuild"
- },
- "engines": {
- "node": ">=18"
- },
- "optionalDependencies": {
- "@esbuild/aix-ppc64": "0.28.1",
- "@esbuild/android-arm": "0.28.1",
- "@esbuild/android-arm64": "0.28.1",
- "@esbuild/android-x64": "0.28.1",
- "@esbuild/darwin-arm64": "0.28.1",
- "@esbuild/darwin-x64": "0.28.1",
- "@esbuild/freebsd-arm64": "0.28.1",
- "@esbuild/freebsd-x64": "0.28.1",
- "@esbuild/linux-arm": "0.28.1",
- "@esbuild/linux-arm64": "0.28.1",
- "@esbuild/linux-ia32": "0.28.1",
- "@esbuild/linux-loong64": "0.28.1",
- "@esbuild/linux-mips64el": "0.28.1",
- "@esbuild/linux-ppc64": "0.28.1",
- "@esbuild/linux-riscv64": "0.28.1",
- "@esbuild/linux-s390x": "0.28.1",
- "@esbuild/linux-x64": "0.28.1",
- "@esbuild/netbsd-arm64": "0.28.1",
- "@esbuild/netbsd-x64": "0.28.1",
- "@esbuild/openbsd-arm64": "0.28.1",
- "@esbuild/openbsd-x64": "0.28.1",
- "@esbuild/openharmony-arm64": "0.28.1",
- "@esbuild/sunos-x64": "0.28.1",
- "@esbuild/win32-arm64": "0.28.1",
- "@esbuild/win32-ia32": "0.28.1",
- "@esbuild/win32-x64": "0.28.1"
- }
- },
- "node_modules/escape-html": {
- "version": "1.0.3",
- "license": "MIT"
- },
- "node_modules/esprima": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
- "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
- "license": "BSD-2-Clause",
- "bin": {
- "esparse": "bin/esparse.js",
- "esvalidate": "bin/esvalidate.js"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/etag": {
- "version": "1.8.1",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/eventsource": {
- "version": "3.0.7",
- "license": "MIT",
- "dependencies": {
- "eventsource-parser": "^3.0.1"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/eventsource-parser": {
- "version": "3.1.0",
- "license": "MIT",
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/express": {
- "version": "5.2.1",
- "license": "MIT",
- "dependencies": {
- "accepts": "^2.0.0",
- "body-parser": "^2.2.1",
- "content-disposition": "^1.0.0",
- "content-type": "^1.0.5",
- "cookie": "^0.7.1",
- "cookie-signature": "^1.2.1",
- "debug": "^4.4.0",
- "depd": "^2.0.0",
- "encodeurl": "^2.0.0",
- "escape-html": "^1.0.3",
- "etag": "^1.8.1",
- "finalhandler": "^2.1.0",
- "fresh": "^2.0.0",
- "http-errors": "^2.0.0",
- "merge-descriptors": "^2.0.0",
- "mime-types": "^3.0.0",
- "on-finished": "^2.4.1",
- "once": "^1.4.0",
- "parseurl": "^1.3.3",
- "proxy-addr": "^2.0.7",
- "qs": "^6.14.0",
- "range-parser": "^1.2.1",
- "router": "^2.2.0",
- "send": "^1.1.0",
- "serve-static": "^2.2.0",
- "statuses": "^2.0.1",
- "type-is": "^2.0.1",
- "vary": "^1.1.2"
- },
- "engines": {
- "node": ">= 18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
- "node_modules/express-rate-limit": {
- "version": "8.5.2",
- "license": "MIT",
- "dependencies": {
- "ip-address": "^10.2.0"
- },
- "engines": {
- "node": ">= 16"
- },
- "funding": {
- "url": "https://github.com/sponsors/express-rate-limit"
- },
- "peerDependencies": {
- "express": ">= 4.11"
- }
- },
- "node_modules/extend-shallow": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
- "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
- "license": "MIT",
- "dependencies": {
- "is-extendable": "^0.1.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/fast-deep-equal": {
- "version": "3.1.3",
- "license": "MIT"
- },
- "node_modules/fast-uri": {
- "version": "3.1.2",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/fastify"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/fastify"
- }
- ],
- "license": "BSD-3-Clause"
- },
- "node_modules/fecha": {
- "version": "4.2.3",
- "license": "MIT"
- },
- "node_modules/finalhandler": {
- "version": "2.1.1",
- "license": "MIT",
- "dependencies": {
- "debug": "^4.4.0",
- "encodeurl": "^2.0.0",
- "escape-html": "^1.0.3",
- "on-finished": "^2.4.1",
- "parseurl": "^1.3.3",
- "statuses": "^2.0.1"
- },
- "engines": {
- "node": ">= 18.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
- "node_modules/fn.name": {
- "version": "1.1.0",
- "license": "MIT"
- },
- "node_modules/forwarded": {
- "version": "0.2.0",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/fresh": {
- "version": "2.0.0",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/fsevents": {
- "version": "2.3.3",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
- }
- },
- "node_modules/function-bind": {
- "version": "1.1.2",
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/get-intrinsic": {
- "version": "1.3.0",
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.2",
- "es-define-property": "^1.0.1",
- "es-errors": "^1.3.0",
- "es-object-atoms": "^1.1.1",
- "function-bind": "^1.1.2",
- "get-proto": "^1.0.1",
- "gopd": "^1.2.0",
- "has-symbols": "^1.1.0",
- "hasown": "^2.0.2",
- "math-intrinsics": "^1.1.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/get-proto": {
- "version": "1.0.1",
- "license": "MIT",
- "dependencies": {
- "dunder-proto": "^1.0.1",
- "es-object-atoms": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/gopd": {
- "version": "1.2.0",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/gray-matter": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz",
- "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==",
- "license": "MIT",
- "dependencies": {
- "js-yaml": "^3.13.1",
- "kind-of": "^6.0.2",
- "section-matter": "^1.0.0",
- "strip-bom-string": "^1.0.0"
- },
- "engines": {
- "node": ">=6.0"
- }
- },
- "node_modules/has-symbols": {
- "version": "1.1.0",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/hasown": {
- "version": "2.0.4",
- "license": "MIT",
- "dependencies": {
- "function-bind": "^1.1.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/hono": {
- "version": "4.12.25",
- "license": "MIT",
- "engines": {
- "node": ">=16.9.0"
- }
- },
- "node_modules/http-errors": {
- "version": "2.0.1",
- "license": "MIT",
- "dependencies": {
- "depd": "~2.0.0",
- "inherits": "~2.0.4",
- "setprototypeof": "~1.2.0",
- "statuses": "~2.0.2",
- "toidentifier": "~1.0.1"
- },
- "engines": {
- "node": ">= 0.8"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
- "node_modules/iconv-lite": {
- "version": "0.7.2",
- "license": "MIT",
- "dependencies": {
- "safer-buffer": ">= 2.1.2 < 3.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
- "node_modules/inherits": {
- "version": "2.0.4",
- "license": "ISC"
- },
- "node_modules/ip-address": {
- "version": "10.2.0",
- "license": "MIT",
- "engines": {
- "node": ">= 12"
- }
- },
- "node_modules/ipaddr.js": {
- "version": "1.9.1",
- "license": "MIT",
- "engines": {
- "node": ">= 0.10"
- }
- },
- "node_modules/is-extendable": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
- "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-promise": {
- "version": "4.0.0",
- "license": "MIT"
- },
- "node_modules/is-stream": {
- "version": "2.0.1",
- "license": "MIT",
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/isexe": {
- "version": "2.0.0",
- "license": "ISC"
- },
- "node_modules/jose": {
- "version": "6.2.3",
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/panva"
- }
- },
- "node_modules/js-yaml": {
- "version": "3.15.1",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.1.tgz",
- "integrity": "sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag==",
- "license": "MIT",
- "dependencies": {
- "argparse": "^1.0.7",
- "esprima": "^4.0.0"
- },
- "bin": {
- "js-yaml": "bin/js-yaml.js"
- }
- },
- "node_modules/json-schema-traverse": {
- "version": "1.0.0",
- "license": "MIT"
- },
- "node_modules/json-schema-typed": {
- "version": "8.0.2",
- "license": "BSD-2-Clause"
- },
- "node_modules/kind-of": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
- "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/kuler": {
- "version": "2.0.0",
- "license": "MIT"
- },
- "node_modules/logform": {
- "version": "2.7.0",
- "license": "MIT",
- "dependencies": {
- "@colors/colors": "1.6.0",
- "@types/triple-beam": "^1.3.2",
- "fecha": "^4.2.0",
- "ms": "^2.1.1",
- "safe-stable-stringify": "^2.3.1",
- "triple-beam": "^1.3.0"
- },
- "engines": {
- "node": ">= 12.0.0"
- }
- },
- "node_modules/math-intrinsics": {
- "version": "1.1.0",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/media-typer": {
- "version": "1.1.0",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/merge-descriptors": {
- "version": "2.0.0",
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/mime-db": {
- "version": "1.54.0",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/mime-types": {
- "version": "3.0.2",
- "license": "MIT",
- "dependencies": {
- "mime-db": "^1.54.0"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
- "node_modules/ms": {
- "version": "2.1.3",
- "license": "MIT"
- },
- "node_modules/negotiator": {
- "version": "1.0.0",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/object-assign": {
- "version": "4.1.1",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/object-inspect": {
- "version": "1.13.4",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/on-finished": {
- "version": "2.4.1",
- "license": "MIT",
- "dependencies": {
- "ee-first": "1.1.1"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/once": {
- "version": "1.4.0",
- "license": "ISC",
- "dependencies": {
- "wrappy": "1"
- }
- },
- "node_modules/one-time": {
- "version": "1.0.0",
- "license": "MIT",
- "dependencies": {
- "fn.name": "1.x.x"
- }
- },
- "node_modules/parseurl": {
- "version": "1.3.3",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/path-key": {
- "version": "3.1.1",
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/path-to-regexp": {
- "version": "8.4.2",
- "license": "MIT",
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
- "node_modules/pkce-challenge": {
- "version": "5.0.1",
- "license": "MIT",
- "engines": {
- "node": ">=16.20.0"
- }
- },
- "node_modules/proxy-addr": {
- "version": "2.0.7",
- "license": "MIT",
- "dependencies": {
- "forwarded": "0.2.0",
- "ipaddr.js": "1.9.1"
- },
- "engines": {
- "node": ">= 0.10"
- }
- },
- "node_modules/qs": {
- "version": "6.15.2",
- "license": "BSD-3-Clause",
- "dependencies": {
- "side-channel": "^1.1.0"
- },
- "engines": {
- "node": ">=0.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/range-parser": {
- "version": "1.2.1",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/raw-body": {
- "version": "3.0.2",
- "license": "MIT",
- "dependencies": {
- "bytes": "~3.1.2",
- "http-errors": "~2.0.1",
- "iconv-lite": "~0.7.0",
- "unpipe": "~1.0.0"
- },
- "engines": {
- "node": ">= 0.10"
- }
- },
- "node_modules/readable-stream": {
- "version": "3.6.2",
- "license": "MIT",
- "dependencies": {
- "inherits": "^2.0.3",
- "string_decoder": "^1.1.1",
- "util-deprecate": "^1.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/require-from-string": {
- "version": "2.0.2",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/router": {
- "version": "2.2.0",
- "license": "MIT",
- "dependencies": {
- "debug": "^4.4.0",
- "depd": "^2.0.0",
- "is-promise": "^4.0.0",
- "parseurl": "^1.3.3",
- "path-to-regexp": "^8.0.0"
- },
- "engines": {
- "node": ">= 18"
- }
- },
- "node_modules/safe-buffer": {
- "version": "5.2.1",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
- "node_modules/safe-stable-stringify": {
- "version": "2.5.0",
- "license": "MIT",
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/safer-buffer": {
- "version": "2.1.2",
- "license": "MIT"
- },
- "node_modules/section-matter": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
- "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==",
- "license": "MIT",
- "dependencies": {
- "extend-shallow": "^2.0.1",
- "kind-of": "^6.0.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/send": {
- "version": "1.2.1",
- "license": "MIT",
- "dependencies": {
- "debug": "^4.4.3",
- "encodeurl": "^2.0.0",
- "escape-html": "^1.0.3",
- "etag": "^1.8.1",
- "fresh": "^2.0.0",
- "http-errors": "^2.0.1",
- "mime-types": "^3.0.2",
- "ms": "^2.1.3",
- "on-finished": "^2.4.1",
- "range-parser": "^1.2.1",
- "statuses": "^2.0.2"
- },
- "engines": {
- "node": ">= 18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
- "node_modules/serve-static": {
- "version": "2.2.1",
- "license": "MIT",
- "dependencies": {
- "encodeurl": "^2.0.0",
- "escape-html": "^1.0.3",
- "parseurl": "^1.3.3",
- "send": "^1.2.0"
- },
- "engines": {
- "node": ">= 18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
- "node_modules/setprototypeof": {
- "version": "1.2.0",
- "license": "ISC"
- },
- "node_modules/shebang-command": {
- "version": "2.0.0",
- "license": "MIT",
- "dependencies": {
- "shebang-regex": "^3.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/shebang-regex": {
- "version": "3.0.0",
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/side-channel": {
- "version": "1.1.1",
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "object-inspect": "^1.13.4",
- "side-channel-list": "^1.0.1",
- "side-channel-map": "^1.0.1",
- "side-channel-weakmap": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/side-channel-list": {
- "version": "1.0.1",
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "object-inspect": "^1.13.4"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/side-channel-map": {
- "version": "1.0.1",
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.5",
- "object-inspect": "^1.13.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/side-channel-weakmap": {
- "version": "1.0.2",
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.5",
- "object-inspect": "^1.13.3",
- "side-channel-map": "^1.0.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/sprintf-js": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
- "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
- "license": "BSD-3-Clause"
- },
- "node_modules/stack-trace": {
- "version": "0.0.10",
- "license": "MIT",
- "engines": {
- "node": "*"
- }
- },
- "node_modules/statuses": {
- "version": "2.0.2",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/string_decoder": {
- "version": "1.3.0",
- "license": "MIT",
- "dependencies": {
- "safe-buffer": "~5.2.0"
- }
- },
- "node_modules/strip-ansi": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
- "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^6.2.2"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/strip-ansi?sponsor=1"
- }
- },
- "node_modules/strip-bom-string": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz",
- "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/text-hex": {
- "version": "1.0.0",
- "license": "MIT"
- },
- "node_modules/toidentifier": {
- "version": "1.0.1",
- "license": "MIT",
- "engines": {
- "node": ">=0.6"
- }
- },
- "node_modules/triple-beam": {
- "version": "1.4.1",
- "license": "MIT",
- "engines": {
- "node": ">= 14.0.0"
- }
- },
- "node_modules/tsx": {
- "version": "4.22.4",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "esbuild": "~0.28.0"
- },
- "bin": {
- "tsx": "dist/cli.mjs"
- },
- "engines": {
- "node": ">=18.0.0"
- },
- "optionalDependencies": {
- "fsevents": "~2.3.3"
- }
- },
- "node_modules/type-is": {
- "version": "2.1.0",
- "license": "MIT",
- "dependencies": {
- "content-type": "^2.0.0",
- "media-typer": "^1.1.0",
- "mime-types": "^3.0.0"
- },
- "engines": {
- "node": ">= 18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
- "node_modules/type-is/node_modules/content-type": {
- "version": "2.0.0",
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
- "node_modules/typescript": {
- "version": "5.9.3",
- "dev": true,
- "license": "Apache-2.0",
- "bin": {
- "tsc": "bin/tsc",
- "tsserver": "bin/tsserver"
- },
- "engines": {
- "node": ">=14.17"
- }
- },
- "node_modules/undici-types": {
- "version": "6.21.0",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/unpipe": {
- "version": "1.0.0",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/util-deprecate": {
- "version": "1.0.2",
- "license": "MIT"
- },
- "node_modules/vary": {
- "version": "1.1.2",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/which": {
- "version": "2.0.2",
- "license": "ISC",
- "dependencies": {
- "isexe": "^2.0.0"
- },
- "bin": {
- "node-which": "bin/node-which"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/winston": {
- "version": "3.19.0",
- "license": "MIT",
- "dependencies": {
- "@colors/colors": "^1.6.0",
- "@dabh/diagnostics": "^2.0.8",
- "async": "^3.2.3",
- "is-stream": "^2.0.0",
- "logform": "^2.7.0",
- "one-time": "^1.0.0",
- "readable-stream": "^3.4.0",
- "safe-stable-stringify": "^2.3.1",
- "stack-trace": "0.0.x",
- "triple-beam": "^1.3.0",
- "winston-transport": "^4.9.0"
- },
- "engines": {
- "node": ">= 12.0.0"
- }
- },
- "node_modules/winston-transport": {
- "version": "4.9.0",
- "license": "MIT",
- "dependencies": {
- "logform": "^2.7.0",
- "readable-stream": "^3.6.2",
- "triple-beam": "^1.3.0"
- },
- "engines": {
- "node": ">= 12.0.0"
- }
- },
- "node_modules/wrappy": {
- "version": "1.0.2",
- "license": "ISC"
- },
- "node_modules/yaml": {
- "version": "2.9.0",
- "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz",
- "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==",
- "license": "ISC",
- "bin": {
- "yaml": "bin.mjs"
- },
- "engines": {
- "node": ">= 14.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/eemeli"
- }
- },
- "node_modules/zod": {
- "version": "3.25.76",
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/colinhacks"
- }
- },
- "node_modules/zod-to-json-schema": {
- "version": "3.25.2",
- "license": "ISC",
- "peerDependencies": {
- "zod": "^3.25.28 || ^4"
- }
- }
- }
-}
diff --git a/canvas-agent/package.json b/canvas-agent/package.json
deleted file mode 100644
index 676d4d9..0000000
--- a/canvas-agent/package.json
+++ /dev/null
@@ -1,46 +0,0 @@
-{
- "name": "@basketikun/canvas-agent",
- "version": "0.6.0",
- "license": "MIT",
- "type": "module",
- "main": "./dist/index.js",
- "types": "./dist/index.d.ts",
- "bin": {
- "canvas-agent": "dist/index.js"
- },
- "files": [
- "dist",
- "agent-instructions.md",
- "README.md"
- ],
- "scripts": {
- "dev": "tsx src/index.ts",
- "debug": "tsx src/index.ts --debug",
- "test": "tsx --test src/canvas/session.test.ts src/agent/codex-client.test.ts src/agent/codex-history.test.ts src/agent/message-metadata.test.ts src/skills/store.test.ts",
- "build": "tsc -p tsconfig.json",
- "start": "node dist/index.js",
- "prepack": "npm run build"
- },
- "dependencies": {
- "@modelcontextprotocol/sdk": "^1.12.1",
- "@openai/codex": "0.146.0",
- "express": "^5.1.0",
- "gray-matter": "^4.0.3",
- "strip-ansi": "7.2.0",
- "winston": "^3.19.0",
- "yaml": "^2.9.0",
- "zod": "^3.25.0"
- },
- "devDependencies": {
- "@types/express": "^5.0.0",
- "@types/node": "^22.0.0",
- "tsx": "^4.19.0",
- "typescript": "^5.8.0"
- },
- "engines": {
- "node": ">=18"
- },
- "publishConfig": {
- "access": "public"
- }
-}
diff --git a/canvas-agent/src/agent/claude.ts b/canvas-agent/src/agent/claude.ts
deleted file mode 100644
index 511093b..0000000
--- a/canvas-agent/src/agent/claude.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import { spawn } from "node:child_process";
-
-import { AGENT_PROMPT } from "../config.js";
-import { errorMessage } from "../utils/value.js";
-import type { AgentEmit } from "./types.js";
-
-/** 使用 Claude CLI 执行一次带 Canvas Agent 工具的任务。 */
-export function runClaudeTurn(prompt: string, emit: AgentEmit) {
- const fullPrompt = withAgentPrompt(prompt);
- if (!fullPrompt) return;
- const child = spawnAgent("claude", ["-p", "--output-format", "stream-json", "--verbose", "--include-partial-messages", "--allowedTools", "mcp__infinite-canvas__*", fullPrompt], emit);
- if (child) pipeJsonLines(child, emit, "claude");
-}
-
-/** 为 Claude CLI 请求拼接 Canvas Agent 指令。 */
-function withAgentPrompt(prompt: string) {
- return prompt.trim() ? `${AGENT_PROMPT}\n\n用户请求:${prompt}` : "";
-}
-
-/** 将 Claude CLI 的 JSON Lines 输出转换为 Agent 事件。 */
-function pipeJsonLines(child: ReturnType, emit: AgentEmit, agent: string) {
- let out = "";
- child.stdout?.on("data", (chunk) => {
- out += chunk.toString();
- const lines = out.split(/\r?\n/);
- out = lines.pop() || "";
- lines.filter(Boolean).forEach((line) => {
- try {
- emit("agent_event", { agent, ...JSON.parse(line) });
- } catch {
- emit("agent_event", { agent, type: "raw", text: line });
- }
- });
- });
- child.stderr?.on("data", (chunk) => emit("agent_log", { text: chunk.toString() }));
- child.on("error", (error) => emit("agent_error", { message: error.message }));
- child.on("close", (code) => emit("agent_done", { agent, code }));
-}
-
-/** 启动外部 Agent CLI,并将同步启动异常转换为事件。 */
-function spawnAgent(name: string, args: string[], emit: AgentEmit) {
- try {
- return spawn(name, args, { stdio: ["ignore", "pipe", "pipe"], shell: process.platform === "win32", windowsHide: true });
- } catch (error) {
- emit("agent_error", { message: errorMessage(error) });
- return null;
- }
-}
diff --git a/canvas-agent/src/agent/codex-client.test.ts b/canvas-agent/src/agent/codex-client.test.ts
deleted file mode 100644
index 11d2d03..0000000
--- a/canvas-agent/src/agent/codex-client.test.ts
+++ /dev/null
@@ -1,942 +0,0 @@
-import assert from "node:assert/strict";
-import test from "node:test";
-
-import { CodexAppClient } from "./codex-client.js";
-import { assertDraftHasNoSensitiveValues, canvasSkillSource } from "./codex.js";
-
-type TestClient = {
- currentThreadId: string;
- currentTurnId: string;
- skillDraftActive: boolean;
- completedTurns: Map;
- plansByTurn: Map;
- lastUsage: unknown;
- answerServerRequest(message: Record): void;
- failAll(message: string): void;
- handle(message: Record): void;
- handleNotification(method: string, params: Record): void;
-};
-
-const emptyEventHistory = { record: () => Promise.resolve(), recordTurn: () => Promise.resolve() };
-
-test("审批只在 app-server 确认 resolved 后清除", () => {
- const writes: Array> = [];
- const events: Array<{ type: string; payload: unknown }> = [];
- const child = { stdin: { write: (line: string) => (writes.push(JSON.parse(line)), true) } };
- const client = Reflect.construct(CodexAppClient, [child, (type: string, payload: unknown) => events.push({ type, payload }), emptyEventHistory]) as CodexAppClient;
- const testClient = client as unknown as TestClient;
-
- testClient.answerServerRequest({ id: 17, method: "item/commandExecution/requestApproval", params: { threadId: "thread-1", turnId: "turn-1" } });
- assert.equal(events.filter((item) => item.type === "codex_approval").length, 1);
-
- assert.equal(client.resolveApproval("17", "accept"), true);
- assert.equal(writes.length, 1);
- assert.equal(events.some((item) => item.type === "codex_approval_resolved"), false);
- assert.equal(client.resolveApproval("17", "accept"), true);
- assert.equal(writes.length, 1);
-
- testClient.handleNotification("serverRequest/resolved", { requestId: "17" });
- const resolved = events.find((item) => item.type === "codex_approval_resolved");
- assert.deepEqual(resolved?.payload, { threadId: "thread-1", turnId: "turn-1", requestId: "17", decision: "accept" });
- assert.equal(client.resolveApproval("17", "accept"), false);
-});
-
-test("中断请求只作用于当前运行线程", async () => {
- const writes: Array> = [];
- const child = { stdin: { write: (line: string) => (writes.push(JSON.parse(line)), true) } };
- const client = Reflect.construct(CodexAppClient, [child, () => undefined, emptyEventHistory]) as CodexAppClient;
- const testClient = client as unknown as TestClient;
- testClient.currentThreadId = "thread-1";
- testClient.currentTurnId = "turn-1";
-
- assert.equal(await client.interruptCurrentTurn("thread-2"), false);
- assert.equal(writes.length, 0);
-
- const interrupt = client.interruptCurrentTurn("thread-1");
- const request = writes.find((item) => item.method === "turn/interrupt");
- assert.ok(request);
- testClient.handle({ id: request.id, result: {} });
- assert.equal(await interrupt, true);
-
- testClient.currentThreadId = "draft-thread";
- testClient.currentTurnId = "draft-turn";
- const draftInterrupt = client.interruptCurrentTurn();
- const draftRequest = writes.at(-1);
- assert.deepEqual(draftRequest?.params, { threadId: "draft-thread", turnId: "draft-turn" });
- testClient.handle({ id: draftRequest?.id, result: {} });
- assert.equal(await draftInterrupt, true);
-});
-
-test("Skill 列表与启用配置使用 app-server 原生协议", async () => {
- const writes: Array> = [];
- const child = { stdin: { write: (line: string) => (writes.push(JSON.parse(line)), true) } };
- const client = Reflect.construct(CodexAppClient, [child, () => undefined, emptyEventHistory]) as CodexAppClient;
- const testClient = client as unknown as TestClient;
-
- const listing = client.listSkills("D:\\site", true);
- const listRequest = writes.at(-1);
- assert.deepEqual(listRequest, { id: 1, method: "skills/list", params: { cwds: ["D:\\site"], forceReload: true } });
- testClient.handle({ id: listRequest?.id, result: { data: [{ cwd: "D:\\site", skills: [], errors: [] }] } });
- assert.equal((await listing).data[0]?.cwd, "D:\\site");
-
- const configuring = client.setSkillEnabled("D:\\site\\.agents\\skills\\demo\\SKILL.md", false);
- const configRequest = writes.at(-1);
- assert.deepEqual(configRequest, { id: 2, method: "skills/config/write", params: { path: "D:\\site\\.agents\\skills\\demo\\SKILL.md", enabled: false } });
- testClient.handle({ id: configRequest?.id, result: { effectiveEnabled: false } });
- assert.equal((await configuring).effectiveEnabled, false);
-});
-
-test("并发强制刷新同一工作空间时只扫描一次 Skill", async () => {
- const writes: Array> = [];
- const child = { stdin: { write: (line: string) => (writes.push(JSON.parse(line)), true) } };
- const client = Reflect.construct(CodexAppClient, [child, () => undefined, emptyEventHistory]) as CodexAppClient;
- const testClient = client as unknown as TestClient;
-
- const first = client.listSkills("D:\\site", true);
- const second = client.listSkills("D:\\site", true);
- assert.equal(writes.length, 1);
- testClient.handle({ id: writes[0].id, result: { data: [{ cwd: "D:\\site", skills: [], errors: [] }] } });
- assert.deepEqual(await Promise.all([first, second]), [
- { data: [{ cwd: "D:\\site", skills: [], errors: [] }] },
- { data: [{ cwd: "D:\\site", skills: [], errors: [] }] },
- ]);
-
- const next = client.listSkills("D:\\site", true);
- assert.equal(writes.length, 2);
- testClient.handle({ id: writes[1].id, result: { data: [] } });
- await next;
-});
-
-test("显式 Skill 同时使用文本标记和结构化输入传给 turn/start", async () => {
- const writes: Array> = [];
- const child = { stdin: { write: (line: string) => (writes.push(JSON.parse(line)), true) } };
- const client = Reflect.construct(CodexAppClient, [child, () => undefined, emptyEventHistory]) as CodexAppClient;
- const testClient = client as unknown as TestClient;
- const skill = { name: "demo-skill", path: "D:\\site\\.agents\\skills\\demo-skill\\SKILL.md" };
-
- const running = client.startTurn("thread-1", "执行任务", [], "request", undefined, undefined, undefined, skill);
- const request = writes.find((item) => item.method === "turn/start");
- assert.deepEqual((request?.params as { input?: unknown[] })?.input, [
- { type: "text", text: "$demo-skill 执行任务", text_elements: [] },
- { type: "skill", ...skill },
- ]);
- testClient.handle({ id: request?.id, result: { turn: { id: "turn-1" } } });
- await new Promise((resolve) => setImmediate(resolve));
- testClient.handleNotification("turn/completed", { threadId: "thread-1", turn: { id: "turn-1", status: "completed" } });
- await running;
-});
-
-test("显式 Skill 不重复已有的文本标记", async () => {
- const writes: Array> = [];
- const child = { stdin: { write: (line: string) => (writes.push(JSON.parse(line)), true) } };
- const client = Reflect.construct(CodexAppClient, [child, () => undefined, emptyEventHistory]) as CodexAppClient;
- const testClient = client as unknown as TestClient;
- const skill = { name: "demo-skill", path: "D:\\site\\.agents\\skills\\demo-skill\\SKILL.md" };
-
- const running = client.startTurn("thread-1", "Use $demo-skill: 执行任务", [], "request", undefined, undefined, undefined, skill);
- const request = writes.find((item) => item.method === "turn/start");
- assert.equal(((request?.params as { input?: Array<{ text?: string }> })?.input || [])[0]?.text, "Use $demo-skill: 执行任务");
- testClient.handle({ id: request?.id, result: { turn: { id: "turn-1" } } });
- await new Promise((resolve) => setImmediate(resolve));
- testClient.handleNotification("turn/completed", { threadId: "thread-1", turn: { id: "turn-1", status: "completed" } });
- await running;
-});
-
-test("skills/changed 作为站点级事件单独广播", () => {
- const events: Array<{ type: string; payload: unknown }> = [];
- const child = { stdin: { write: () => true } };
- const client = Reflect.construct(CodexAppClient, [child, (type: string, payload: unknown) => events.push({ type, payload }), emptyEventHistory]) as CodexAppClient;
- (client as unknown as TestClient).handleNotification("skills/changed", {});
- assert.deepEqual(events, [{ type: "skills_changed", payload: {} }]);
-});
-
-test("MCP 启动状态区分空对话预热与正常 turn 运行", async () => {
- const writes: Array> = [];
- const events: Array<{ type: string; payload: unknown }> = [];
- const child = { stdin: { write: (line: string) => (writes.push(JSON.parse(line)), true) } };
- const client = Reflect.construct(CodexAppClient, [child, (type: string, payload: unknown) => events.push({ type, payload }), emptyEventHistory]) as CodexAppClient;
- const testClient = client as unknown as TestClient;
-
- const starting = client.startThread("D:\\site", "request", true);
- const request = writes.find((item) => item.method === "thread/start");
- assert.ok(request);
- testClient.handleNotification("mcpServer/startupStatus/updated", { threadId: "thread-1", name: "notion", status: "starting" });
- testClient.handle({ id: request.id, result: { thread: { id: "thread-1" } } });
- await new Promise((resolve) => setImmediate(resolve));
- const statusRequest = writes.find((item) => item.method === "mcpServerStatus/list");
- assert.ok(statusRequest);
- testClient.handleNotification("mcpServer/startupStatus/updated", { threadId: "thread-1", name: "notion", status: "failed" });
- testClient.handle({ id: statusRequest.id, result: { data: [{ name: "notion", authStatus: "notLoggedIn", tools: [], resources: [], resourceTemplates: [] }], nextCursor: null } });
- await starting;
-
- const running = client.startTurn("thread-1", "test", [], "request");
- const turnRequest = writes.find((item) => item.method === "turn/start");
- assert.ok(turnRequest);
- testClient.handleNotification("mcpServer/startupStatus/updated", { threadId: "thread-1", name: "notion", status: "ready" });
- testClient.handle({ id: turnRequest.id, result: { turn: { id: "turn-1" } } });
- testClient.handleNotification("turn/completed", { threadId: "thread-1", turn: { id: "turn-1", status: "completed" } });
- await running;
-
- assert.deepEqual(events.filter((event) => event.type === "agent_bootstrap"), [
- { type: "agent_bootstrap", payload: { type: "mcp.startup", phase: "preheat", threadId: "thread-1", name: "notion", status: "starting", error: undefined, failureReason: undefined } },
- { type: "agent_bootstrap", payload: { type: "mcp.startup", phase: "preheat", threadId: "thread-1", name: "notion", status: "failed", error: undefined, failureReason: undefined } },
- { type: "agent_bootstrap", payload: { type: "mcp.complete", phase: "preheat", threadId: "thread-1", services: [{ name: "notion", authStatus: "notLoggedIn" }] } },
- { type: "agent_bootstrap", payload: { type: "mcp.startup", phase: "runtime", threadId: "thread-1", name: "notion", status: "ready", error: undefined, failureReason: undefined } },
- ]);
-});
-
-test("静默 Skill 草稿 turn 只返回结构化结果,不广播也不写历史", async () => {
- const writes: Array> = [];
- const events: Array<{ type: string; payload: unknown }> = [];
- const persistedItems: unknown[] = [];
- const persistedTurns: unknown[] = [];
- const child = { stdin: { write: (line: string) => (writes.push(JSON.parse(line)), true) } };
- const history = {
- record: (entry: unknown) => (persistedItems.push(entry), Promise.resolve()),
- recordTurn: (entry: unknown) => (persistedTurns.push(entry), Promise.resolve()),
- };
- const client = Reflect.construct(CodexAppClient, [child, (type: string, payload: unknown) => events.push({ type, payload }), history]) as CodexAppClient;
- const testClient = client as unknown as TestClient;
-
- const starting = client.startSkillDraftThread("D:\\site");
- const threadRequest = writes.find((item) => item.method === "thread/start");
- assert.ok(threadRequest);
- testClient.handleNotification("thread/started", { thread: { id: "draft-thread", ephemeral: true } });
- testClient.handle({ id: threadRequest.id, result: { thread: { id: "draft-thread", ephemeral: true } } });
- await starting;
-
- const schema = { type: "object", properties: { name: { type: "string" } } };
- const output = JSON.stringify({ name: "product-image-flow" });
- const generating = client.generateSkillDraft("draft-thread", "提炼流程", schema);
- const turnRequest = writes.find((item) => item.method === "turn/start");
- assert.deepEqual(turnRequest?.params, {
- threadId: "draft-thread",
- input: [{ type: "text", text: "提炼流程", text_elements: [] }],
- approvalPolicy: "never",
- sandboxPolicy: { type: "readOnly", networkAccess: false },
- outputSchema: schema,
- });
- testClient.handle({ id: turnRequest?.id, result: { turn: { id: "draft-turn" } } });
- await new Promise((resolve) => setImmediate(resolve));
- testClient.handleNotification("turn/started", { threadId: "draft-thread", turn: { id: "draft-turn", status: "inProgress" } });
- testClient.handleNotification("item/reasoning/summaryTextDelta", { threadId: "draft-thread", turnId: "draft-turn", itemId: "reasoning-1", delta: "正在提炼" });
- testClient.handleNotification("item/completed", { threadId: "draft-thread", turnId: "draft-turn", item: { id: "reasoning-1", type: "reasoning", summary: ["正在提炼"] } });
- testClient.handleNotification("item/agentMessage/delta", { threadId: "draft-thread", turnId: "draft-turn", itemId: "assistant-1", delta: output });
- testClient.handleNotification("item/completed", { threadId: "draft-thread", turnId: "draft-turn", item: { id: "assistant-1", type: "agentMessage", text: "" } });
- testClient.handleNotification("turn/completed", { threadId: "draft-thread", turn: { id: "draft-turn", status: "completed" } });
-
- assert.equal(await generating, output);
- assert.deepEqual(events, []);
- assert.deepEqual(persistedItems, []);
- assert.deepEqual(persistedTurns, []);
-
- assert.equal(testClient.skillDraftActive, true);
- const closing = client.closeSkillDraftThread("draft-thread");
- const unsubscribe = writes.find((item) => item.method === "thread/unsubscribe");
- assert.ok(unsubscribe);
- testClient.handle({ id: unsubscribe.id, result: { status: "unsubscribed" } });
- await closing;
- assert.equal(testClient.skillDraftActive, false);
-});
-
-test("静默线程创建期间只隐藏响应返回的线程", async () => {
- const writes: Array> = [];
- const events: Array<{ type: string; payload: unknown }> = [];
- const child = { stdin: { write: (line: string) => (writes.push(JSON.parse(line)), true) } };
- const client = Reflect.construct(CodexAppClient, [child, (type: string, payload: unknown) => events.push({ type, payload }), emptyEventHistory]) as CodexAppClient;
- const testClient = client as unknown as TestClient;
-
- const starting = client.startSkillDraftThread("D:\\site");
- const request = writes.find((item) => item.method === "thread/start");
- assert.ok(request);
-
- testClient.handleNotification("thread/started", { thread: { id: "normal-thread" } });
- testClient.handleNotification("thread/started", { thread: { id: "draft-thread", ephemeral: true } });
- assert.deepEqual(events, []);
-
- testClient.handle({ id: request.id, result: { thread: { id: "draft-thread", ephemeral: true } } });
- await starting;
-
- assert.deepEqual(events, [{
- type: "agent_event",
- payload: { agent: "codex", type: "thread.started", thread_id: "normal-thread" },
- }]);
-
- testClient.handleNotification("thread/started", { thread: { id: "draft-thread", ephemeral: true } });
- assert.equal(events.length, 1);
-});
-
-test("静默 Skill 草稿不吞掉全局 Skill 变更通知", async () => {
- const events: Array<{ type: string; payload: unknown }> = [];
- const writes: Array> = [];
- const child = { stdin: { write: (line: string) => (writes.push(JSON.parse(line)), true) } };
- const client = Reflect.construct(CodexAppClient, [child, (type: string, payload: unknown) => events.push({ type, payload }), emptyEventHistory]) as CodexAppClient;
- const testClient = client as unknown as TestClient;
-
- const starting = client.startSkillDraftThread("D:\\site");
- const request = writes.find((item) => item.method === "thread/start");
- assert.ok(request);
- testClient.handle({ id: request.id, result: { thread: { id: "draft-thread", ephemeral: true } } });
- await starting;
-
- testClient.handleNotification("thread/started", { thread: { id: "draft-thread", ephemeral: true } });
- testClient.handleNotification("skills/changed", {});
-
- assert.deepEqual(events, [{ type: "skills_changed", payload: {} }]);
-});
-
-test("静默 Skill 草稿自动拒绝未携带线程 ID 的权限请求", async () => {
- const events: Array<{ type: string; payload: unknown }> = [];
- const writes: Array> = [];
- const child = { stdin: { write: (line: string) => (writes.push(JSON.parse(line)), true) } };
- const client = Reflect.construct(CodexAppClient, [child, (type: string, payload: unknown) => events.push({ type, payload }), emptyEventHistory]) as CodexAppClient;
- const testClient = client as unknown as TestClient;
-
- const starting = client.startSkillDraftThread("D:\\site");
- const request = writes.find((item) => item.method === "thread/start");
- assert.ok(request);
- testClient.handle({ id: request.id, result: { thread: { id: "draft-thread", ephemeral: true } } });
- await starting;
- testClient.handleNotification("turn/started", { threadId: "draft-thread", turn: { id: "draft-turn", status: "inProgress" } });
-
- testClient.answerServerRequest({ id: 17, method: "item/permissions/requestApproval", params: { turnId: "draft-turn", permissions: { network: true } } });
-
- assert.deepEqual(writes.at(-1), { id: 17, result: { permissions: {}, scope: "turn" } });
- assert.deepEqual(events, []);
-});
-
-test("app-server 退出时清理尚未确认的静默线程", async () => {
- const events: Array<{ type: string; payload: unknown }> = [];
- const writes: Array> = [];
- const child = { stdin: { write: (line: string) => (writes.push(JSON.parse(line)), true) } };
- const client = Reflect.construct(CodexAppClient, [child, (type: string, payload: unknown) => events.push({ type, payload }), emptyEventHistory]) as CodexAppClient;
- const testClient = client as unknown as TestClient;
-
- const starting = client.startSkillDraftThread("D:\\site");
- testClient.handleNotification("thread/started", { thread: { id: "draft-thread", ephemeral: true } });
- assert.equal(testClient.skillDraftActive, true);
-
- testClient.failAll("Codex app-server exited: 1");
-
- assert.equal(testClient.skillDraftActive, false);
- assert.deepEqual(events, []);
- await assert.rejects(starting, /Codex app-server exited/);
-});
-
-test("app-server 在草稿运行中退出后不再等待线程取消订阅", async () => {
- const writes: Array> = [];
- const child = { stdin: { write: (line: string) => (writes.push(JSON.parse(line)), true) } };
- const client = Reflect.construct(CodexAppClient, [child, () => undefined, emptyEventHistory]) as CodexAppClient;
- const testClient = client as unknown as TestClient;
-
- const starting = client.startSkillDraftThread("D:\\site");
- const threadRequest = writes.find((item) => item.method === "thread/start");
- assert.ok(threadRequest);
- testClient.handle({ id: threadRequest.id, result: { thread: { id: "draft-thread", ephemeral: true } } });
- await starting;
-
- const generating = client.generateSkillDraft("draft-thread", "提炼流程", { type: "object" });
- const turnRequest = writes.find((item) => item.method === "turn/start");
- assert.ok(turnRequest);
- testClient.handle({ id: turnRequest.id, result: { turn: { id: "draft-turn" } } });
- await new Promise((resolve) => setImmediate(resolve));
- testClient.failAll("Codex app-server exited: 1");
-
- await assert.rejects(generating, /Codex app-server exited/);
- const writeCount = writes.length;
- await client.closeSkillDraftThread("draft-thread");
- assert.equal(writes.length, writeCount);
- await assert.rejects(client.listModels(), /Codex app-server exited/);
- assert.equal(writes.length, writeCount);
-});
-
-test("画布 Skill 草稿源保留流程信息并移除媒体、本地路径、凭据和临时任务字段", () => {
- const source = canvasSkillSource({
- projectId: "private-project",
- clientId: "private-client",
- title: "商品图流程",
- viewport: { x: 100, y: 200, k: 1.5 },
- nodes: [{
- id: "prompt-node",
- type: "text",
- title: "提示词",
- position: { x: 20, y: 30 },
- width: 480,
- height: 240,
- metadata: {
- content: "保留这段可复用流程;API Key=secret-api-key;Bearer abcdefghijklmnop",
- sourceUrl: "https://example.com/reference.png",
- extensionlessMediaUrl: "https://cdn.example.com/private-media/abc123",
- sensitiveUrl: "https://example.com/reference.png?token=secret-value",
- signedUrl: "https://example.com/reference.png?X-Amz-Signature=secret-value",
- basicAuthUrl: "https://user:password@example.com/reference.png",
- dataUrl: "data:image/png;base64,c2VjcmV0",
- blobUrl: "blob:http://localhost/private-image",
- reference: "D:\\private\\reference.png",
- referenceNote: "本地文件 D:\\private\\reference.png 参考图 https://example.com/reference.png",
- unixReference: "/var/lib/private/reference.png",
- unixReferenceNote: "文件 /root/private.png 与 file:///opt/private.png",
- batchRootId: "output-node",
- batchChildIds: ["output-node", "missing-node"],
- primaryImageId: "output-node",
- groupId: "prompt-node",
- storageKey: "canvas-private-key",
- apiKey: "secret-api-key",
- credentials: { token: "secret-token" },
- status: "running",
- progress: 75,
- errorDetails: "temporary failure",
- taskId: "task-private",
- createdAt: "2026-08-03",
- nested: { keep: "保留", password: "secret-password", preview: "blob:http://localhost/nested" },
- },
- }, {
- id: "output-node",
- type: "image",
- title: "结果图片",
- position: { x: 600, y: 30 },
- width: 480,
- height: 480,
- metadata: { content: "data:image/png;base64,c2VjcmV0" },
- }],
- connections: [{ id: "connection-1", fromNodeId: "prompt-node", toNodeId: "output-node" }],
- selectedNodeIds: ["prompt-node"],
- });
-
- assert.deepEqual(source, {
- title: "商品图流程",
- nodes: [{
- ref: "node-1",
- type: "text",
- title: "提示词",
- metadata: {
- content: "保留这段可复用流程;[敏感凭证已移除];[敏感凭证已移除]",
- referenceNote: "本地文件 [本地路径已移除] 参考图 [外部地址已移除]",
- unixReferenceNote: "文件 [本地路径已移除] 与 [本地路径已移除]",
- batchRootRef: "node-2",
- batchChildRefs: ["node-2"],
- primaryImageRef: "node-2",
- groupRef: "node-1",
- nested: { keep: "保留" },
- },
- }, {
- ref: "node-2",
- type: "image",
- title: "结果图片",
- }],
- connections: [{ from: "node-1", to: "node-2" }],
- selectedNodeRefs: ["node-1"],
- });
- assert.doesNotMatch(JSON.stringify(source), /prompt-node|output-node|connection-1|secret-value|secret-api-key|abcdefghijklmnop|D:\\\\private|\/var\/|\/root\/|\/opt\/|file:|https?:\/\//);
-});
-
-test("画布 Skill 草稿源保留普通中文斜杠文本和斜杠命令", () => {
- const source = canvasSkillSource({
- nodes: [{
- id: "prompt-node",
- type: "text",
- title: "提示词",
- position: { x: 0, y: 0 },
- width: 320,
- height: 180,
- metadata: {
- content: "故事概念/剧本圣经 → 配音/音效/音乐",
- prompt: "/imagine 商品图",
- },
- }],
- connections: [],
- });
-
- assert.deepEqual(source, {
- nodes: [{
- ref: "node-1",
- type: "text",
- title: "提示词",
- metadata: {
- content: "故事概念/剧本圣经 → 配音/音效/音乐",
- prompt: "/imagine 商品图",
- },
- }],
- connections: [],
- });
-});
-
-test("画布 Skill 草稿源匿名化任意字符串中的已知节点 ID", () => {
- const imageNodeId = "8f36ab15-30f1-4c73-b169-f12c36584ddb";
- const source = canvasSkillSource({
- nodes: [{
- id: "prompt-node",
- type: "text",
- title: "提示词",
- position: { x: 0, y: 0 },
- width: 320,
- height: 180,
- metadata: { composerContent: `参考 @[node:${imageNodeId}] 生成商品图` },
- }, {
- id: imageNodeId,
- type: "image",
- title: "参考图",
- position: { x: 400, y: 0 },
- width: 320,
- height: 320,
- metadata: {},
- }],
- connections: [],
- });
-
- assert.equal(
- ((source.nodes as Array>)[0]?.metadata as Record).composerContent,
- "参考 @[node:node-2] 生成商品图",
- );
- assert.doesNotMatch(JSON.stringify(source), new RegExp(imageNodeId));
-});
-
-test("画布 Skill 草稿源匿名化 metadata 对象键中的已知节点 ID", () => {
- const imageNodeId = "8f36ab15-30f1-4c73-b169-f12c36584ddb";
- const source = canvasSkillSource({
- nodes: [{
- id: "prompt-node",
- type: "text",
- title: "提示词",
- position: { x: 0, y: 0 },
- width: 320,
- height: 180,
- metadata: { pluginState: { [`node:${imageNodeId}`]: { label: "参考图" } } },
- }, {
- id: imageNodeId,
- type: "image",
- title: "参考图",
- position: { x: 400, y: 0 },
- width: 320,
- height: 320,
- metadata: {},
- }],
- connections: [],
- });
- const metadata = (source.nodes as Array>)[0]?.metadata as Record;
-
- assert.deepEqual(metadata.pluginState, { "node:node-2": { label: "参考图" } });
- assert.doesNotMatch(JSON.stringify(source), new RegExp(imageNodeId));
-});
-
-test("画布 Skill 草稿源为截断节点保留完整快照范围内的安全引用", () => {
- const targetNodeId = "target-node-id";
- const nodes = Array.from({ length: 301 }, (_, index) => ({
- id: index === 300 ? targetNodeId : `filler-node-${index + 1}`,
- type: "text" as const,
- title: index === 0 ? "提示词" : `节点 ${index + 1}`,
- position: { x: index * 10, y: 0 },
- width: 320,
- height: 180,
- metadata: index === 0 ? { composerContent: `参考 @[node:${targetNodeId}]` } : {},
- }));
- const source = canvasSkillSource({ nodes, connections: [] });
- const firstMetadata = (source.nodes as Array>)[0]?.metadata as Record;
-
- assert.equal(firstMetadata.composerContent, "参考 @[node:node-301]");
- assert.equal(source.truncated, true);
- assert.doesNotMatch(JSON.stringify(source), new RegExp(targetNodeId));
-});
-
-test("画布 Skill 草稿源限制总长度并优先保留选中流程", () => {
- const nodes = Array.from({ length: 320 }, (_, index) => ({
- id: `node-${index + 1}`,
- type: "text" as const,
- title: index === 318 ? "关联节点" : index === 319 ? "选中节点" : `普通节点 ${index + 1}`,
- position: { x: index * 10, y: 0 },
- width: 320,
- height: 180,
- metadata: { content: "流程说明".repeat(2_000) },
- }));
- const source = canvasSkillSource({
- nodes,
- connections: [{ id: "private-connection", fromNodeId: "node-319", toNodeId: "node-320" }],
- selectedNodeIds: ["node-320"],
- });
- const sourceNodes = source.nodes as Array>;
-
- assert.ok(JSON.stringify(source).length <= 120_000);
- assert.equal(sourceNodes[0]?.title, "选中节点");
- assert.equal(sourceNodes[1]?.title, "关联节点");
- assert.deepEqual(source.connections, [{ from: "node-2", to: "node-1" }]);
- assert.deepEqual(source.selectedNodeRefs, ["node-1"]);
- assert.equal(source.truncated, true);
-});
-
-test("Skill 草稿拒绝普通外部 URL,避免对话内容泄露媒体地址", () => {
- const draft = {
- name: "product-image-flow",
- displayName: "Product image flow",
- description: "Reusable image generation workflow",
- instructions: "参考图地址:https://example.com/reference.png",
- shortDescription: "Generate product images from a reusable workflow",
- defaultPrompt: "$product-image-flow",
- };
-
- assert.throws(() => assertDraftHasNoSensitiveValues(draft, []), /外部地址/);
-});
-
-test("Skill 草稿允许普通动作名称,但拒绝任意本地绝对路径", () => {
- const draft = {
- name: "request-review",
- displayName: "Review request",
- description: "Use node-workflow-builder to review a reusable flow",
- instructions: "Run generation-status-check, then use job-application-helper.",
- shortDescription: "Review a reusable workflow",
- defaultPrompt: "$request-review",
- };
-
- assert.doesNotThrow(() => assertDraftHasNoSensitiveValues(draft, []));
- assert.throws(() => assertDraftHasNoSensitiveValues({ ...draft, instructions: "读取 /var/lib/private.png" }, []), /本地路径/);
- assert.throws(() => assertDraftHasNoSensitiveValues({ ...draft, instructions: "读取 file:///root/private.png" }, []), /本地路径/);
-});
-
-test("turn/started 早于 turn/start 响应时保持完整事件归属", async () => {
- const writes: Array> = [];
- const events: Array<{ type: string; payload: unknown }> = [];
- const child = { stdin: { write: (line: string) => (writes.push(JSON.parse(line)), true) } };
- const client = Reflect.construct(CodexAppClient, [child, (type: string, payload: unknown) => events.push({ type, payload }), emptyEventHistory]) as CodexAppClient;
- const testClient = client as unknown as TestClient;
-
- const turnIds: string[] = [];
- const running = client.startTurn("thread-1", "测试", [], "request", undefined, undefined, (turnId) => turnIds.push(turnId));
- const request = writes.find((item) => item.method === "turn/start");
- assert.ok(request);
-
- testClient.handleNotification("turn/started", { turn: { id: "turn-1", status: "inProgress" } });
- assert.deepEqual(turnIds, ["turn-1"]);
- testClient.handleNotification("item/started", { item: { id: "reasoning-1", type: "reasoning" } });
- testClient.handleNotification("turn/completed", { turn: { id: "turn-1", status: "completed" } });
- testClient.handle({ id: request.id, result: { turn: { id: "turn-1" } } });
- await running;
-
- assert.deepEqual(turnIds, ["turn-1"]);
- const scopedEvents = events.filter((item) => item.type === "agent_event");
- assert.deepEqual(scopedEvents.map((item) => eventScope(item.payload)), [
- { threadId: "thread-1", turnId: "turn-1" },
- { threadId: "thread-1", turnId: "turn-1" },
- { threadId: "thread-1", turnId: "turn-1" },
- ]);
- assert.deepEqual(eventScope(events.find((item) => item.type === "agent_done")?.payload), { threadId: "thread-1", turnId: "turn-1" });
-});
-
-test("turn/start 响应早于通知时 onTurn 仍只调用一次", async () => {
- const writes: Array> = [];
- const events: Array<{ type: string; payload: unknown }> = [];
- const child = { stdin: { write: (line: string) => (writes.push(JSON.parse(line)), true) } };
- const client = Reflect.construct(CodexAppClient, [child, (type: string, payload: unknown) => events.push({ type, payload }), emptyEventHistory]) as CodexAppClient;
- const testClient = client as unknown as TestClient;
- const turnIds: string[] = [];
-
- const running = client.startTurn("thread-1", "测试", [], "request", undefined, undefined, (turnId) => turnIds.push(turnId));
- const request = writes.find((item) => item.method === "turn/start");
- assert.ok(request);
- testClient.handle({ id: request.id, result: { turn: { id: "turn-1" } } });
- await new Promise((resolve) => setImmediate(resolve));
- assert.deepEqual(turnIds, ["turn-1"]);
-
- testClient.handleNotification("turn/started", { threadId: "thread-1", turn: { id: "turn-1", status: "inProgress" } });
- testClient.handleNotification("turn/completed", { threadId: "thread-1", turn: { id: "turn-1", status: "completed" } });
- await running;
-
- assert.deepEqual(turnIds, ["turn-1"]);
- assert.equal(events.filter((item) => item.type === "agent_event" && eventType(item.payload) === "turn.started").length, 1);
-});
-
-test("turn/started 通知缺失时使用 turn/start 响应回调", async () => {
- const writes: Array> = [];
- const child = { stdin: { write: (line: string) => (writes.push(JSON.parse(line)), true) } };
- const client = Reflect.construct(CodexAppClient, [child, () => undefined, emptyEventHistory]) as CodexAppClient;
- const testClient = client as unknown as TestClient;
- const turnIds: string[] = [];
-
- const running = client.startTurn("thread-1", "测试", [], "request", undefined, undefined, (turnId) => turnIds.push(turnId));
- const request = writes.find((item) => item.method === "turn/start");
- assert.ok(request);
- testClient.handle({ id: request.id, result: { turn: { id: "turn-1" } } });
- await new Promise((resolve) => setImmediate(resolve));
-
- assert.deepEqual(turnIds, ["turn-1"]);
- testClient.handleNotification("turn/completed", { threadId: "thread-1", turn: { id: "turn-1", status: "completed" } });
- await running;
- assert.deepEqual(turnIds, ["turn-1"]);
-});
-
-test("onTurn 按 threadId 和 turnId 去重", async () => {
- const writes: Array> = [];
- const child = { stdin: { write: (line: string) => (writes.push(JSON.parse(line)), true) } };
- const client = Reflect.construct(CodexAppClient, [child, () => undefined, emptyEventHistory]) as CodexAppClient;
- const testClient = client as unknown as TestClient;
- const turnIds: string[] = [];
-
- const first = client.startTurn("thread-1", "测试", [], "request", undefined, undefined, (turnId) => turnIds.push(`thread-1:${turnId}`));
- const firstRequest = writes.find((item) => item.method === "turn/start");
- assert.ok(firstRequest);
- testClient.handle({ id: firstRequest.id, result: { turn: { id: "turn-1" } } });
- await new Promise((resolve) => setImmediate(resolve));
- testClient.handleNotification("turn/started", { threadId: "thread-1", turn: { id: "turn-1", status: "inProgress" } });
- testClient.handleNotification("turn/completed", { threadId: "thread-1", turn: { id: "turn-1", status: "completed" } });
- await first;
-
- const second = client.startTurn("thread-2", "测试", [], "request", undefined, undefined, (turnId) => turnIds.push(`thread-2:${turnId}`));
- const secondRequest = writes.filter((item) => item.method === "turn/start").at(-1);
- assert.ok(secondRequest);
- testClient.handle({ id: secondRequest.id, result: { turn: { id: "turn-1" } } });
- await new Promise((resolve) => setImmediate(resolve));
- testClient.handleNotification("turn/started", { threadId: "thread-2", turn: { id: "turn-1", status: "inProgress" } });
- testClient.handleNotification("turn/completed", { threadId: "thread-2", turn: { id: "turn-1", status: "completed" } });
- await second;
-
- assert.deepEqual(turnIds, ["thread-1:turn-1", "thread-2:turn-1"]);
-});
-
-test("稀疏的命令完成通知会保留开始通知中的命令内容", () => {
- const events: Array<{ type: string; payload: unknown }> = [];
- const persisted: unknown[] = [];
- const child = { stdin: { write: () => true } };
- const history = { record: (entry: unknown) => (persisted.push(entry), Promise.resolve()) };
- const client = Reflect.construct(CodexAppClient, [child, (type: string, payload: unknown) => events.push({ type, payload }), history]) as CodexAppClient;
- const testClient = client as unknown as TestClient;
-
- testClient.handleNotification("item/started", { threadId: "thread-1", turnId: "turn-1", item: { id: "command-1", type: "commandExecution", command: "Get-Location", cwd: "D:\\infinite-canvas" } });
- testClient.handleNotification("item/commandExecution/outputDelta", { threadId: "thread-1", turnId: "turn-1", itemId: "command-1", delta: "D:\\infinite-canvas" });
- testClient.handleNotification("item/completed", { threadId: "thread-1", turnId: "turn-1", item: { id: "command-1", type: "commandExecution", status: "completed", exitCode: 0 } });
-
- const completed = events.find((event) => event.type === "agent_event" && eventType(event.payload) === "item.completed");
- const item = (completed?.payload as { item?: Record })?.item;
- assert.equal(item?.command, "Get-Location");
- assert.equal(item?.cwd, "D:\\infinite-canvas");
- assert.equal(item?.status, "completed");
- assert.equal(item?.aggregatedOutput, "D:\\infinite-canvas");
- assert.deepEqual(persisted, [{
- threadId: "thread-1",
- turnId: "turn-1",
- itemId: "command-1",
- sequence: 1,
- item,
- }]);
-});
-
-test("稀疏的 plan 完成通知会保留流式正文", () => {
- const events: Array<{ type: string; payload: unknown }> = [];
- const persisted: unknown[] = [];
- const child = { stdin: { write: () => true } };
- const history = { record: (entry: unknown) => (persisted.push(entry), Promise.resolve()), recordTurn: () => Promise.resolve() };
- const client = Reflect.construct(CodexAppClient, [child, (type: string, payload: unknown) => events.push({ type, payload }), history]) as CodexAppClient;
- const testClient = client as unknown as TestClient;
-
- testClient.handleNotification("item/plan/delta", { threadId: "thread-1", turnId: "turn-1", itemId: "plan-1", delta: "第一步\n第二步" });
- testClient.handleNotification("item/completed", { threadId: "thread-1", turnId: "turn-1", item: { id: "plan-1", type: "plan", status: "completed" } });
-
- const completed = events.find((event) => event.type === "agent_event" && eventType(event.payload) === "item.completed");
- assert.equal((completed?.payload as { item?: { text?: string } })?.item?.text, "第一步\n第二步");
- assert.equal(((persisted[0] as { item?: { text?: string } })?.item?.text), "第一步\n第二步");
-});
-
-test("reasoning 完成通知缺少 summary 时保留流式摘要", () => {
- const events: Array<{ type: string; payload: unknown }> = [];
- const persisted: unknown[] = [];
- const child = { stdin: { write: () => true } };
- const history = { record: (entry: unknown) => (persisted.push(entry), Promise.resolve()), recordTurn: () => Promise.resolve() };
- const client = Reflect.construct(CodexAppClient, [child, (type: string, payload: unknown) => events.push({ type, payload }), history]) as CodexAppClient;
- const testClient = client as unknown as TestClient;
-
- testClient.handleNotification("item/reasoning/summaryTextDelta", { threadId: "thread-1", turnId: "turn-1", itemId: "reasoning-1", summaryIndex: 0, delta: "分析结果" });
- testClient.handleNotification("item/completed", { threadId: "thread-1", turnId: "turn-1", item: { id: "reasoning-1", type: "reasoning", status: "completed" } });
-
- const completed = events.find((event) => event.type === "agent_event" && eventType(event.payload) === "item.completed");
- assert.equal((completed?.payload as { item?: { summary?: string } })?.item?.summary, "分析结果");
- assert.equal(((persisted[0] as { item?: { summary?: string } })?.item?.summary), "分析结果");
-});
-
-test("流式更新只发送当前增量而不重复传输累计正文", async () => {
- const events: Array<{ type: string; payload: unknown }> = [];
- const child = { stdin: { write: () => true } };
- const client = Reflect.construct(CodexAppClient, [child, (type: string, payload: unknown) => events.push({ type, payload }), emptyEventHistory]) as CodexAppClient;
- const testClient = client as unknown as TestClient;
-
- testClient.handleNotification("item/agentMessage/delta", { threadId: "thread-1", turnId: "turn-1", itemId: "assistant-1", delta: "第一段" });
- await new Promise((resolve) => setTimeout(resolve, 50));
- testClient.handleNotification("item/agentMessage/delta", { threadId: "thread-1", turnId: "turn-1", itemId: "assistant-1", delta: "第二段" });
- await new Promise((resolve) => setTimeout(resolve, 50));
-
- const updates = events.filter((event) => event.type === "agent_event" && eventType(event.payload) === "item.updated");
- assert.deepEqual(updates.map((event) => (event.payload as { item: { delta?: string; text?: string } }).item), [
- { id: "assistant-1", type: "agent_message", delta: "第一段" },
- { id: "assistant-1", type: "agent_message", delta: "第二段" },
- ]);
-});
-
-test("新版协作工具完成通知会归一化并写入补充历史", () => {
- const events: Array<{ type: string; payload: unknown }> = [];
- const persisted: unknown[] = [];
- const child = { stdin: { write: () => true } };
- const history = { record: (entry: unknown) => (persisted.push(entry), Promise.resolve()), recordTurn: () => Promise.resolve() };
- const client = Reflect.construct(CodexAppClient, [child, (type: string, payload: unknown) => events.push({ type, payload }), history]) as CodexAppClient;
- const testClient = client as unknown as TestClient;
-
- testClient.handleNotification("item/completed", { threadId: "thread-1", turnId: "turn-1", item: { id: "collab-1", type: "collabAgentToolCall", status: "completed" } });
-
- const completed = events.find((event) => event.type === "agent_event" && eventType(event.payload) === "item.completed");
- assert.equal((completed?.payload as { item?: { type?: string } })?.item?.type, "collab_tool_call");
- assert.equal(((persisted[0] as { item?: { type?: string } })?.item?.type), "collab_tool_call");
-});
-
-test("并行条目按开始顺序保存而不是完成顺序", () => {
- const persisted: unknown[] = [];
- const child = { stdin: { write: () => true } };
- const history = { record: (entry: unknown) => (persisted.push(entry), Promise.resolve()) };
- const client = Reflect.construct(CodexAppClient, [child, () => undefined, history]) as CodexAppClient;
- const testClient = client as unknown as TestClient;
-
- testClient.handleNotification("item/started", { threadId: "thread-1", turnId: "turn-1", item: { id: "first", type: "commandExecution", command: "first" } });
- testClient.handleNotification("item/started", { threadId: "thread-1", turnId: "turn-1", item: { id: "second", type: "commandExecution", command: "second" } });
- testClient.handleNotification("item/completed", { threadId: "thread-1", turnId: "turn-1", item: { id: "second", type: "commandExecution", status: "completed" } });
- testClient.handleNotification("item/completed", { threadId: "thread-1", turnId: "turn-1", item: { id: "first", type: "commandExecution", status: "completed" } });
-
- assert.deepEqual(persisted.map((entry) => ({ itemId: (entry as { itemId: string }).itemId, sequence: (entry as { sequence: number }).sequence })), [
- { itemId: "second", sequence: 2 },
- { itemId: "first", sequence: 1 },
- ]);
-});
-
-test("turn 完成通知会保存本轮输入与终态 turn", async () => {
- const persistedTurns: unknown[] = [];
- const writes: Array> = [];
- const child = { stdin: { write: (line: string) => (writes.push(JSON.parse(line)), true) } };
- const history = { record: () => Promise.resolve(), recordTurn: (entry: unknown) => (persistedTurns.push(entry), Promise.resolve()) };
- const client = Reflect.construct(CodexAppClient, [child, () => undefined, history]) as CodexAppClient;
- const testClient = client as unknown as TestClient;
-
- const running = client.startTurn("thread-1", "$command-runner 执行 Get-Location", [], "request", undefined, undefined, undefined, undefined, "执行 Get-Location");
- const request = writes.find((item) => item.method === "turn/start");
- assert.ok(request);
- testClient.handle({ id: request.id, result: { turn: { id: "turn-1" } } });
- await new Promise((resolve) => setImmediate(resolve));
- testClient.handleNotification("turn/completed", { threadId: "thread-1", turn: { id: "turn-1", status: "completed", durationMs: 120 } });
- await running;
-
- assert.deepEqual(persistedTurns, [{ threadId: "thread-1", turnId: "turn-1", turn: { id: "turn-1", status: "completed", durationMs: 120, input: "$command-runner 执行 Get-Location", messageText: "执行 Get-Location" } }]);
-});
-
-test("turn 完成状态会等待补充历史落盘后再广播", async () => {
- const events: Array<{ type: string; payload: unknown }> = [];
- let release!: () => void;
- const persisted = new Promise((resolve) => { release = resolve; });
- const child = { stdin: { write: () => true } };
- const history = { record: () => Promise.resolve(), recordTurn: () => persisted };
- const client = Reflect.construct(CodexAppClient, [child, (type: string, payload: unknown) => events.push({ type, payload }), history]) as CodexAppClient;
- const testClient = client as unknown as TestClient;
-
- testClient.handleNotification("turn/completed", { threadId: "thread-1", turn: { id: "turn-1", status: "completed" } });
- assert.equal(events.some((event) => event.type === "agent_event" && eventType(event.payload) === "turn.completed"), false);
- assert.equal(events.some((event) => event.type === "agent_done"), false);
-
- release();
- await new Promise((resolve) => setImmediate(resolve));
-
- assert.equal(events.some((event) => event.type === "agent_event" && eventType(event.payload) === "turn.completed"), true);
- assert.equal(events.some((event) => event.type === "agent_done"), true);
-});
-
-test("app-server 失效时清除不能跨进程复用的 turn 状态", () => {
- const child = { stdin: { write: () => true } };
- const client = Reflect.construct(CodexAppClient, [child, () => undefined, emptyEventHistory]) as CodexAppClient;
- const testClient = client as unknown as TestClient;
- testClient.completedTurns.set("thread-1\0turn-1", null);
- testClient.plansByTurn.set("thread-1\0turn-1", { threadId: "thread-1" });
- testClient.lastUsage = { inputTokens: 1 };
-
- testClient.failAll("app-server stopped");
-
- assert.equal(testClient.completedTurns.size, 0);
- assert.equal(testClient.plansByTurn.size, 0);
- assert.equal(testClient.lastUsage, null);
-});
-
-test("app-server 在 turn 完成通知前退出时保存失败终态", async () => {
- const events: Array<{ type: string; payload: unknown }> = [];
- const persistedTurns: unknown[] = [];
- const child = { stdin: { write: () => true } };
- const history = { record: () => Promise.resolve(), recordTurn: (entry: unknown) => (persistedTurns.push(entry), Promise.resolve()) };
- const client = Reflect.construct(CodexAppClient, [child, (type: string, payload: unknown) => events.push({ type, payload }), history]) as CodexAppClient;
- const testClient = client as unknown as TestClient;
-
- const running = client.startTurn("thread-1", "$failure-check 执行失败任务", [], "request", undefined, undefined, undefined, undefined, "执行失败任务");
- testClient.handle({ id: 1, result: { turn: { id: "turn-1" } } });
- await new Promise((resolve) => setImmediate(resolve));
- testClient.failAll("Codex app-server exited: 1");
- await assert.rejects(running, /Codex app-server exited/);
-
- assert.deepEqual(persistedTurns, [{
- threadId: "thread-1",
- turnId: "turn-1",
- turn: { id: "turn-1", status: "failed", error: { message: "Codex app-server exited: 1" }, input: "$failure-check 执行失败任务", messageText: "执行失败任务" },
- }]);
- const completed = events.find((event) => event.type === "agent_event" && eventType(event.payload) === "turn.completed");
- assert.equal((completed?.payload as { status?: string })?.status, "failed");
-});
-
-test("turn/started 已到达但 turn/start 尚未响应时退出仍保存失败终态", async () => {
- const writes: Array> = [];
- const events: Array<{ type: string; payload: unknown }> = [];
- const persistedTurns: unknown[] = [];
- const child = { stdin: { write: (line: string) => (writes.push(JSON.parse(line)), true) } };
- const history = { record: () => Promise.resolve(), recordTurn: (entry: unknown) => (persistedTurns.push(entry), Promise.resolve()) };
- const client = Reflect.construct(CodexAppClient, [child, (type: string, payload: unknown) => events.push({ type, payload }), history]) as CodexAppClient;
- const testClient = client as unknown as TestClient;
-
- const running = client.startTurn("thread-1", "响应前退出", [], "request");
- assert.ok(writes.some((item) => item.method === "turn/start"));
- testClient.handleNotification("turn/started", { threadId: "thread-1", turn: { id: "turn-1", status: "inProgress" } });
- testClient.failAll("Codex app-server exited: 1");
- await assert.rejects(running, /Codex app-server exited/);
-
- assert.deepEqual(persistedTurns, [{
- threadId: "thread-1",
- turnId: "turn-1",
- turn: { id: "turn-1", status: "failed", error: { message: "Codex app-server exited: 1" }, input: "响应前退出" },
- }]);
- assert.equal(events.some((event) => event.type === "agent_event" && eventType(event.payload) === "turn.completed"), true);
- assert.equal(events.some((event) => event.type === "agent_done"), true);
-});
-
-test("app-server 退出时等待失败历史落盘后再结束 turn", async () => {
- const writes: Array> = [];
- const events: Array<{ type: string; payload: unknown }> = [];
- let release!: () => void;
- const persisted = new Promise((resolve) => { release = resolve; });
- const child = { stdin: { write: (line: string) => (writes.push(JSON.parse(line)), true) } };
- const history = { record: () => Promise.resolve(), recordTurn: () => persisted };
- const client = Reflect.construct(CodexAppClient, [child, (type: string, payload: unknown) => events.push({ type, payload }), history]) as CodexAppClient;
- const testClient = client as unknown as TestClient;
-
- const running = client.startTurn("thread-1", "等待落盘", [], "request");
- const request = writes.find((item) => item.method === "turn/start");
- assert.ok(request);
- testClient.handle({ id: request.id, result: { turn: { id: "turn-1" } } });
- await new Promise((resolve) => setImmediate(resolve));
- let settled = false;
- const outcome = running.then(() => { settled = true; }, () => { settled = true; });
-
- testClient.failAll("Codex app-server exited: 1");
- await new Promise((resolve) => setImmediate(resolve));
- assert.equal(settled, false);
- assert.equal(events.some((event) => event.type === "agent_event" && eventType(event.payload) === "turn.completed"), false);
- assert.equal(events.some((event) => event.type === "agent_done"), false);
-
- release();
- await outcome;
- assert.equal(settled, true);
- assert.equal(events.some((event) => event.type === "agent_event" && eventType(event.payload) === "turn.completed"), true);
- assert.equal(events.some((event) => event.type === "agent_done"), true);
-});
-
-function eventScope(payload: unknown) {
- const value = payload && typeof payload === "object" ? payload as Record : {};
- return { threadId: value.thread_id, turnId: value.turn_id };
-}
-
-function eventType(payload: unknown) {
- return payload && typeof payload === "object" ? (payload as Record).type : undefined;
-}
diff --git a/canvas-agent/src/agent/codex-client.ts b/canvas-agent/src/agent/codex-client.ts
deleted file mode 100644
index 7a08a41..0000000
--- a/canvas-agent/src/agent/codex-client.ts
+++ /dev/null
@@ -1,896 +0,0 @@
-import { spawn, type ChildProcess } from "node:child_process";
-import { createRequire } from "node:module";
-import path from "node:path";
-import { fileURLToPath } from "node:url";
-import stripAnsi from "strip-ansi";
-
-import { VERSION } from "../config.js";
-import { logger } from "../utils/logger.js";
-import { field, type JsonRecord } from "../utils/value.js";
-import { codexEventHistory, type CodexEventHistory } from "./codex-event-history.js";
-import type { CodexNotificationParams, CodexPlanUpdate, CodexReasoningEffort, CodexRequestMethod, CodexRequestParams, CodexRequestResult, CodexSkillSelector, CodexTurnInput } from "./codex-protocol.js";
-import type { AgentEmit, AgentPermissionMode } from "./types.js";
-
-type AgentEvent = JsonRecord & { type: string; usage?: unknown };
-type PendingRequest = { resolve: (value: unknown) => void; reject: (error: Error) => void; silent?: boolean };
-type ActiveTurn = PendingRequest & { threadId: string; turnId: string; prompt: string; messageText?: string };
-type ItemDeltaParams = { threadId: string; turnId: string; itemId: string; delta: string; summaryIndex?: number };
-type PendingDelta = { delta: string; itemType: string; params: ItemDeltaParams; timer: ReturnType };
-type ApprovalRequest = { id: number; method: string; params: JsonRecord; decision?: string };
-type PendingTurnStart = { threadId: string; prompt: string; messageText?: string; turnId?: string; onTurn?: (turnId: string) => void };
-
-const canvasAgentMcp = canvasAgentMcpCommand();
-const require = createRequire(import.meta.url);
-const STREAM_UPDATE_INTERVAL_MS = 40;
-const supplementalItemTypes = new Set(["agent_message", "reasoning", "plan", "mcp_tool_call", "command_execution", "file_change", "dynamic_tool_call", "collab_tool_call", "web_search", "image_view", "image_generation", "context_compaction"]);
-const SKILL_DRAFT_INSTRUCTIONS = "你只负责根据已提供的对话或画布快照生成可编辑的 Codex Skill 草稿。不要调用任何工具,不要执行命令,不要读取文件,不要访问网络,不要修改任何状态。严格按 outputSchema 返回结果,并排除凭证、密钥、Token、本地路径、临时错误、调试日志和一次性结果。";
-
-/** 表示错误已经通过 app-server 终态或进程事件通知过网页。 */
-export class CodexReportedError extends Error {
- override name = "CodexReportedError";
-}
-
-/** 封装 Codex app-server 的 JSON-RPC 通信与事件转换。 */
-export class CodexAppClient {
- private nextId = 1;
- private buffer = "";
- private currentThreadId = "";
- private currentTurnId = "";
- private pendingTurnStart?: PendingTurnStart;
- private startedTurnKeys = new Set();
- private textByItem = new Map();
- private reasoningTextByItem = new Map>();
- private lastUsage: unknown = null;
- private pending = new Map();
- private activeTurns = new Map();
- private completedTurns = new Map();
- private completedTurnResults = new Map();
- private pendingDeltas = new Map();
- private startedItems = new Map();
- private itemSequences = new Map();
- private nextItemSequences = new Map();
- private plansByTurn = new Map();
- private approvalRequests = new Map();
- private finalizingTurns = new Map>();
- private skillReloads = new Map>>();
- private silentThreadIds = new Set();
- private structuredOutputByTurn = new Map();
- private pendingSilentThreadStarts = new Set();
- private pendingThreadStartedNotifications: JsonRecord[] = [];
- private pendingPreheatThreadStarts = 0;
- private preheatingThreadIds = new Set();
- private failing = false;
- private failureMessage = "";
-
- /** 保存 app-server 子进程和事件出口。 */
- private constructor(private child: ChildProcess, private emit: AgentEmit, private eventHistory: Pick = codexEventHistory) {}
-
- /** 启动并初始化 Codex app-server。 */
- static async start(emit: AgentEmit, onExit: () => void) {
- logger.info("Starting Codex app-server", { executable: process.execPath, codex: codexBin() });
- const child = spawn(process.execPath, [codexBin(), "app-server", "--stdio"], { stdio: ["pipe", "pipe", "pipe"], windowsHide: true });
- const client = new CodexAppClient(child, emit);
- let stopped = false;
- const stop = () => {
- if (stopped) return;
- stopped = true;
- onExit();
- };
- child.stdout?.on("data", (chunk) => client.read(chunk.toString()));
- child.stderr?.on("data", (chunk) => {
- if (client.skillDraftActive) return;
- const text = stripAnsi(chunk.toString()).replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z\s+/, "");
- logger.warn("Codex app-server stderr", { text });
- emit("agent_log", { text });
- });
- child.on("error", (error) => {
- logger.error("Codex app-server process error", error);
- emit("agent_error", { message: error.message });
- client.failAll(error.message, true);
- stop();
- });
- child.on("exit", (code) => {
- logger.warn("Codex app-server exited", { code });
- client.failAll(`Codex app-server exited: ${code ?? 0}`);
- stop();
- emit("agent_log", { text: `Codex app-server exited: ${code ?? 0}` });
- });
- await client.request("initialize", { clientInfo: { name: "canvas-agent", title: "Infinite Canvas Agent", version: VERSION }, capabilities: { experimentalApi: true, requestAttestation: false } });
- client.notify("initialized");
- return client;
- }
-
- /** 创建新的 Codex 线程。 */
- async startThread(cwd?: string, permissionMode: AgentPermissionMode = "request", preheat = false) {
- if (preheat) this.pendingPreheatThreadStarts += 1;
- let threadId = "";
- try {
- const { thread } = await this.request("thread/start", { ...threadSettings(permissionMode), ...(cwd ? { cwd } : {}), threadSource: "user" });
- if (!thread.id) throw new Error("Codex app-server 没有返回 thread id");
- threadId = thread.id;
- if (preheat) {
- this.preheatingThreadIds.add(threadId);
- await this.completeMcpPreheat(threadId);
- }
- return thread;
- } finally {
- if (preheat) this.pendingPreheatThreadStarts -= 1;
- if (threadId) this.preheatingThreadIds.delete(threadId);
- }
- }
-
- /** 创建不会持久化或向网页广播的草稿线程。 */
- async startSkillDraftThread(cwd: string) {
- return await this.startSilentThread("thread/start", { ...skillDraftThreadSettings(cwd), threadSource: "user" });
- }
-
- /** 从指定对话派生不会持久化或向网页广播的草稿线程。 */
- async forkSkillDraftThread(threadId: string, cwd: string) {
- return await this.startSilentThread("thread/fork", { ...skillDraftThreadSettings(cwd), threadId, threadSource: "user" });
- }
-
- /** 恢复已有 Codex 线程。 */
- async resumeThread(threadId: string, cwd?: string, permissionMode: AgentPermissionMode = "request", preheat = false) {
- if (preheat) this.pendingPreheatThreadStarts += 1;
- try {
- if (preheat) this.preheatingThreadIds.add(threadId);
- const { thread } = await this.request("thread/resume", { threadId, ...threadSettings(permissionMode), ...(cwd ? { cwd } : {}) });
- if (!thread.id) throw new Error("Codex app-server 没有返回 thread id");
- if (preheat) await this.completeMcpPreheat(thread.id);
- return thread;
- } finally {
- if (preheat) this.pendingPreheatThreadStarts -= 1;
- if (preheat) this.preheatingThreadIds.delete(threadId);
- }
- }
-
- /** 以 app-server 的权威 MCP 清单响应作为预热完成边界。 */
- private async completeMcpPreheat(threadId: string) {
- const result = await this.request("mcpServerStatus/list", { threadId, limit: 100, detail: "toolsAndAuthOnly" });
- this.emit("agent_bootstrap", { type: "mcp.complete", phase: "preheat", threadId, services: result.data.map(({ name, authStatus }) => ({ name, authStatus })) });
- }
-
- /** 查询 Codex 线程列表。 */
- listThreads(params: CodexRequestParams<"thread/list">) {
- return this.request("thread/list", params);
- }
-
- /** 读取指定 Codex 线程。 */
- readThread(threadId: string, includeTurns = true) {
- return this.request("thread/read", { threadId, includeTurns });
- }
-
- /** 归档指定 Codex 线程。 */
- archiveThread(threadId: string) {
- return this.request("thread/archive", { threadId });
- }
-
- /** 释放临时草稿线程的 App Server 订阅和进程内缓存。 */
- async closeSkillDraftThread(threadId: string) {
- try {
- if (!this.failing) await this.request("thread/unsubscribe", { threadId }, true);
- } finally {
- this.silentThreadIds.delete(threadId);
- const prefix = `${threadId}\0`;
- [...this.structuredOutputByTurn.keys()].filter((key) => key.startsWith(prefix)).forEach((key) => this.structuredOutputByTurn.delete(key));
- }
- }
-
- /** 查询当前账号可用的 Codex 模型。 */
- listModels() {
- return this.request("model/list", { limit: 100, includeHidden: false });
- }
-
- /** 查询指定工作空间可发现的 Codex Skills。 */
- listSkills(cwd: string, forceReload = false) {
- if (!forceReload) return this.request("skills/list", { cwds: [cwd] });
- const key = process.platform === "win32" ? path.resolve(cwd).toLowerCase() : path.resolve(cwd);
- const current = this.skillReloads.get(key);
- if (current) return current;
- const reload = this.request("skills/list", { cwds: [cwd], forceReload: true });
- this.skillReloads.set(key, reload);
- const clear = () => {
- if (this.skillReloads.get(key) === reload) this.skillReloads.delete(key);
- };
- void reload.then(clear, clear);
- return reload;
- }
-
- /** 修改一个已发现 Skill 的启用状态。 */
- setSkillEnabled(path: string, enabled: boolean) {
- return this.request("skills/config/write", { path, enabled });
- }
-
- /** 返回指定线程在当前进程中收到的最新任务计划。 */
- planUpdates(threadId: string) {
- return [...this.plansByTurn.values()].filter((item) => item.threadId === threadId);
- }
-
- /** 清理已归档线程的任务计划缓存。 */
- clearPlanUpdates(threadId: string) {
- this.plansByTurn.forEach((item, key) => {
- if (item.threadId === threadId) this.plansByTurn.delete(key);
- });
- }
-
- /** 启动一个 Codex turn 并等待完成通知。 */
- async startTurn(threadId: string, prompt: string, images: string[], permissionMode: AgentPermissionMode, model?: string, effort?: CodexReasoningEffort, onTurn?: (turnId: string) => void, skill?: CodexSkillSelector, messageText?: string, outputSchema?: JsonRecord) {
- this.preheatingThreadIds.delete(threadId);
- this.currentThreadId = threadId;
- this.currentTurnId = "";
- this.lastUsage = null;
- const pendingStart: PendingTurnStart = { threadId, prompt, messageText, onTurn };
- this.pendingTurnStart = pendingStart;
- try {
- const { turn } = await this.request("turn/start", { threadId, input: codexInput(prompt, images, skill), ...(outputSchema ? skillDraftTurnSettings() : turnSettings(permissionMode)), ...(model ? { model } : {}), ...(effort ? { effort } : {}), ...(outputSchema ? { outputSchema } : {}) }, Boolean(outputSchema));
- const turnId = turn.id;
- if (!turnId) throw new Error("Codex app-server 没有返回 turn id");
- pendingStart.turnId = turnId;
- this.currentTurnId = turnId;
- this.notifyTurnStarted(threadId, turnId, pendingStart);
- const turnKey = turnCacheKey(threadId, turnId);
- const completed = this.completedTurns.get(turnKey);
- if (this.completedTurns.has(turnKey)) {
- this.completedTurns.delete(turnKey);
- const result = this.completedTurnResults.get(turnKey);
- this.completedTurnResults.delete(turnKey);
- this.currentThreadId = "";
- this.currentTurnId = "";
- if (completed) throw completed;
- return result;
- }
- return await new Promise((resolve, reject) => this.activeTurns.set(turnKey, { resolve, reject, threadId, turnId, prompt, messageText }));
- } catch (error) {
- if (!this.currentTurnId) this.currentThreadId = "";
- throw error;
- } finally {
- if (this.pendingTurnStart === pendingStart) this.pendingTurnStart = undefined;
- if (pendingStart.turnId) this.startedTurnKeys.delete(turnCacheKey(threadId, pendingStart.turnId));
- }
- }
-
- /** 在静默线程中生成结构化输出。 */
- async generateSkillDraft(threadId: string, prompt: string, outputSchema: JsonRecord, model?: string, effort?: CodexReasoningEffort) {
- this.silentThreadIds.add(threadId);
- const result = await this.startTurn(threadId, prompt, [], "request", model, effort, undefined, undefined, undefined, outputSchema);
- const output = String(field(result, "output") || "").trim();
- if (!output) throw new Error("Codex 没有返回 Skill 草稿");
- return output;
- }
-
- /** 中断当前正在运行且属于指定线程的 Codex turn。 */
- async interruptCurrentTurn(requestedThreadId?: string) {
- const threadId = this.currentThreadId;
- const turnId = this.currentTurnId;
- if (!threadId || !turnId || (requestedThreadId && requestedThreadId !== threadId)) return false;
- try {
- logger.warn("Interrupting active Codex turn", { threadId, turnId });
- await this.request("turn/interrupt", { threadId, turnId });
- return true;
- } catch (error) {
- logger.warn("Failed to interrupt Codex turn", { error, threadId, turnId });
- return false;
- }
- }
-
- /** 回复网页端已经确认的 Codex 权限请求。 */
- resolveApproval(requestId: string, decision: string) {
- const request = this.approvalRequests.get(requestId);
- if (!request) return false;
- if (request.decision) return true;
- request.decision = decision;
- const permissions = field(request.params, "permissions") || field(request.params, "requestedPermissions");
- const accepted = decision === "accept" || decision === "acceptForSession";
- const result = request.method === "item/permissions/requestApproval"
- ? { permissions: accepted ? permissions || {} : {}, scope: decision === "acceptForSession" ? "session" : "turn" }
- : { decision };
- this.write({ id: request.id, result });
- return true;
- }
-
- /** 标记下一次临时线程创建,使早于请求响应到达的通知也不会外泄。 */
- private async startSilentThread(method: Method, params: CodexRequestParams) {
- const pendingStart = Symbol();
- this.pendingSilentThreadStarts.add(pendingStart);
- try {
- const result = await this.request(method, params, true);
- const thread = result.thread;
- if (!thread.id) throw new Error("Codex app-server 没有返回 thread id");
- this.silentThreadIds.add(thread.id);
- return thread;
- } finally {
- this.pendingSilentThreadStarts.delete(pendingStart);
- if (!this.pendingSilentThreadStarts.size) this.flushPendingThreadStartedNotifications();
- }
- }
-
- /** 发送 JSON-RPC 请求并保存待处理 Promise。 */
- private request(method: Method, params: CodexRequestParams, silent = false) {
- if (this.failing) return Promise.reject(new CodexReportedError(this.failureMessage || "Codex app-server 已停止")) as Promise>;
- const id = this.nextId++;
- this.write({ id, method, params }, silent);
- return new Promise>((resolve, reject) => this.pending.set(id, { resolve: (result) => resolve(result as CodexRequestResult), reject, silent }));
- }
-
- /** 发送无需响应的 JSON-RPC 通知。 */
- private notify(method: string, params?: unknown) {
- this.write(params === undefined ? { method } : { method, params });
- }
-
- /** 将 JSON-RPC 消息写入 app-server 标准输入。 */
- private write(value: unknown, silent = false) {
- const method = String(field(value, "method") || "");
- const params = field(value, "params");
- if (method && !silent) logger.debug(`Codex ${method}`, { id: field(value, "id"), threadId: field(params, "threadId") });
- this.child.stdin?.write(`${JSON.stringify(value)}\n`);
- }
-
- /** 按行解析 app-server 标准输出。 */
- private read(chunk: string) {
- this.buffer += chunk;
- const lines = this.buffer.split(/\r?\n/);
- this.buffer = lines.pop() || "";
- lines.filter(Boolean).forEach((line) => {
- try {
- this.handle(JSON.parse(line) as JsonRecord);
- } catch (error) {
- if (!this.skillDraftActive) {
- logger.warn("Invalid Codex app-server output", { error, line });
- this.emit("agent_log", { text: line });
- }
- }
- });
- }
-
- /** 分派单条 JSON-RPC 响应、请求或通知。 */
- private handle(message: JsonRecord) {
- const id = Number(message.id);
- if (message.error && this.pending.has(id)) {
- const error = String(field(message.error, "message") || "Codex request failed");
- if (!this.pending.get(id)?.silent) {
- if (/not materialized yet.*includeTurns/i.test(error)) logger.debug("Codex thread has no messages yet", { id });
- else logger.warn("Codex request failed", { id, error });
- }
- return this.reject(id, error);
- }
- if (this.pending.has(id)) return this.resolve(id, message.result);
- if (typeof message.method === "string" && "id" in message) return this.answerServerRequest(message);
- if (typeof message.method === "string") this.handleNotification(message.method, (message.params || {}) as JsonRecord);
- }
-
- /** 转换并广播 app-server 通知。 */
- private handleNotification(method: string, params: JsonRecord) {
- if (this.handleSilentNotification(method, params)) return;
- if (method === "skills/changed") {
- this.emit("skills_changed", {});
- return;
- }
- if (method === "serverRequest/resolved") {
- const requestId = String(field(params, "requestId") || "");
- const request = requestId ? this.approvalRequests.get(requestId) : undefined;
- if (request) {
- this.approvalRequests.delete(requestId);
- this.emit("codex_approval_resolved", { ...request.params, ...params, requestId, decision: request.decision });
- }
- return;
- }
- if (method === "mcpServer/startupStatus/updated") {
- const value = params as unknown as CodexNotificationParams<"mcpServer/startupStatus/updated">;
- const threadId = value.threadId || this.currentThreadId;
- this.emit("agent_bootstrap", {
- type: "mcp.startup",
- phase: this.pendingPreheatThreadStarts > 0 || this.preheatingThreadIds.has(threadId) ? "preheat" : "runtime",
- threadId,
- name: value.name,
- status: value.status,
- error: value.error,
- failureReason: value.failureReason,
- });
- return;
- }
- const turnEvent = method.startsWith("turn/") || method.startsWith("item/") || method === "thread/tokenUsage/updated" || method === "error";
- if (turnEvent) {
- const threadId = String(field(params, "threadId") || this.currentThreadId);
- const turnId = String(field(params, "turnId") || field(field(params, "turn"), "id") || this.currentTurnId);
- if (method === "turn/started") {
- this.currentThreadId = threadId;
- this.currentTurnId = turnId;
- this.notifyTurnStarted(threadId, turnId);
- }
- params = { ...params, ...(threadId ? { threadId } : {}), ...(turnId ? { turnId } : {}) };
- }
- if (method === "item/agentMessage/delta") {
- const value = params as unknown as CodexNotificationParams<"item/agentMessage/delta">;
- return this.emitDelta("agent_message", value);
- }
- if (method === "item/plan/delta") return this.emitDelta("plan", params as unknown as CodexNotificationParams<"item/plan/delta">);
- if (method === "item/reasoning/summaryTextDelta") return this.emitDelta("reasoning", params as unknown as CodexNotificationParams<"item/reasoning/summaryTextDelta">);
- if (method === "item/commandExecution/outputDelta") return this.emitDelta("command_execution", params as unknown as CodexNotificationParams<"item/commandExecution/outputDelta">);
- if (method === "turn/plan/updated") {
- const value = params as unknown as CodexNotificationParams<"turn/plan/updated">;
- const update: CodexPlanUpdate = { ...value, threadId: value.threadId || "" };
- if (update.threadId && update.turnId) this.plansByTurn.set(turnCacheKey(update.threadId, update.turnId), update);
- params = update as unknown as JsonRecord;
- }
- if (method === "thread/tokenUsage/updated") {
- this.lastUsage = normalizeUsage(params as unknown as CodexNotificationParams<"thread/tokenUsage/updated">);
- this.emit("agent_event", { agent: "codex", type: "usage.updated", usage: this.lastUsage, ...codexEventScope(params) });
- return;
- }
- const event = normalizeCodexNotification(method, params);
- if (!event) return;
- const eventScope = codexEventScope(params);
- if (event.type === "item.started" || event.type === "item.completed") {
- const item = field(event, "item") as JsonRecord | undefined;
- const id = String(field(item, "id") || "");
- const threadId = String(field(event, "thread_id") || this.currentThreadId);
- const turnId = String(field(event, "turn_id") || this.currentTurnId);
- const key = itemCacheKey({ threadId, turnId, itemId: id });
- if (id && threadId && turnId && event.type === "item.started") {
- this.assignItemSequence(threadId, turnId, id);
- this.startedItems.set(key, item || {});
- }
- if (id && event.type === "item.completed") {
- const started = this.startedItems.get(key);
- if (started) event.item = mergeDefinedRecord(started, item || {});
- this.startedItems.delete(key);
- }
- }
- if (event.type === "item.completed") {
- const item = field(event, "item") as JsonRecord | undefined;
- const id = String(field(item, "id") || "");
- const key = itemCacheKey({ threadId: String(field(event, "thread_id") || this.currentThreadId), turnId: String(field(event, "turn_id") || this.currentTurnId), itemId: id });
- this.flushDelta(key);
- const streamedText = this.textByItem.get(key);
- if ((item?.type === "agent_message" || item?.type === "plan") && streamedText && !String(item.text || "").trim()) item.text = streamedText;
- if (item?.type === "reasoning" && streamedText && !readableCodexText(field(item, "summary"))) item.summary = streamedText;
- if (item?.type === "command_execution" && streamedText && !String(item.aggregatedOutput || "").trim()) item.aggregatedOutput = streamedText;
- const threadId = String(field(event, "thread_id") || this.currentThreadId);
- const turnId = String(field(event, "turn_id") || this.currentTurnId);
- if (id && threadId && turnId && item && supplementalItemTypes.has(String(item.type || ""))) {
- const sequence = this.assignItemSequence(threadId, turnId, id);
- void this.eventHistory.record({ threadId, turnId, itemId: id, sequence, item }).catch((error) => logger.warn("Failed to persist Codex event history", { threadId, turnId, itemId: id, error }));
- }
- if (id) {
- this.textByItem.delete(key);
- this.reasoningTextByItem.delete(key);
- }
- }
- let turnPersistence: Promise | undefined;
- if (event.type === "turn.completed") {
- const turn = field(params, "turn");
- const turnId = String(field(turn, "id") || field(params, "turnId") || "");
- const threadId = String(field(params, "threadId") || field(event, "thread_id") || "");
- const planKey = turnCacheKey(threadId, turnId);
- const plan = this.plansByTurn.get(planKey);
- if (plan) this.plansByTurn.set(planKey, { ...plan, turnStatus: String(field(turn, "status") || "completed") });
- if (threadId && turnId) {
- const pendingStart = this.pendingTurnStart?.threadId === threadId && (!this.pendingTurnStart.turnId || this.pendingTurnStart.turnId === turnId) ? this.pendingTurnStart : undefined;
- const turnRecord = {
- ...(turn && typeof turn === "object" && !Array.isArray(turn) ? turn as JsonRecord : { id: turnId, status: field(turn, "status") || "completed" }),
- ...(pendingStart?.prompt ? { input: pendingStart.prompt } : {}),
- ...(pendingStart?.messageText ? { messageText: pendingStart.messageText } : {}),
- };
- turnPersistence = this.eventHistory.recordTurn({ threadId, turnId, turn: turnRecord }).catch((error) => logger.warn("Failed to persist Codex turn history", { threadId, turnId, error }));
- this.finalizingTurns.set(planKey, turnPersistence);
- }
- this.finishTurnDeltas(threadId, turnId);
- }
- if (event.type === "turn.completed") {
- event.usage = this.lastUsage;
- const complete = () => this.completeTurn(event, params, eventScope);
- if (turnPersistence) void turnPersistence.then(complete);
- else complete();
- return;
- }
- this.emit("agent_event", { agent: "codex", ...event });
- }
-
- /** 草稿线程存续期间隐藏 app-server 自身输出,避免混入网页诊断日志。 */
- private get skillDraftActive() {
- return this.pendingSilentThreadStarts.size > 0 || this.silentThreadIds.size > 0;
- }
-
- /** 消化临时草稿线程事件,不广播、不落补充历史。 */
- private handleSilentNotification(method: string, params: JsonRecord) {
- if (method === "thread/started") {
- const thread = field(params, "thread");
- const threadId = String(field(thread, "id") || "");
- if (!threadId) return false;
- if (this.silentThreadIds.has(threadId)) return true;
- if (!this.pendingSilentThreadStarts.size) return false;
- this.pendingThreadStartedNotifications.push(params);
- return true;
- }
- if (!(method === "error" || method.startsWith("turn/") || method.startsWith("item/") || method === "thread/tokenUsage/updated")) return false;
- const threadId = String(field(params, "threadId") || this.currentThreadId);
- if (!threadId || !this.silentThreadIds.has(threadId)) return false;
- const turnId = String(field(params, "turnId") || field(field(params, "turn"), "id") || this.currentTurnId);
- if (method === "turn/started") {
- this.currentThreadId = threadId;
- this.currentTurnId = turnId;
- this.notifyTurnStarted(threadId, turnId);
- return true;
- }
- if (method === "item/agentMessage/delta") {
- const itemId = String(field(params, "itemId") || "");
- if (itemId && turnId) {
- const key = itemCacheKey({ threadId, turnId, itemId });
- this.textByItem.set(key, `${this.textByItem.get(key) || ""}${String(field(params, "delta") || "")}`);
- }
- return true;
- }
- if (method === "item/completed") {
- const item = normalizeItem(field(params, "item"));
- const itemId = String(field(item, "id") || "");
- const key = itemCacheKey({ threadId, turnId, itemId });
- if (item.type === "agent_message" && turnId) {
- const text = String(item.text || this.textByItem.get(key) || "").trim();
- if (text) this.structuredOutputByTurn.set(turnCacheKey(threadId, turnId), text);
- }
- if (itemId) this.textByItem.delete(key);
- return true;
- }
- if (method !== "turn/completed") return true;
- const turn = field(params, "turn") as CodexNotificationParams<"turn/completed">["turn"];
- const completedTurnId = String(field(turn, "id") || turnId);
- const turnKey = turnCacheKey(threadId, completedTurnId);
- const output = this.structuredOutputByTurn.get(turnKey) || "";
- this.structuredOutputByTurn.delete(turnKey);
- this.finishTurnDeltas(threadId, completedTurnId);
- const failure = turn.error ? new CodexReportedError(turn.error.message || "Codex turn failed") : null;
- const pending = this.activeTurns.get(turnKey);
- const result = { output };
- if (pending) {
- this.activeTurns.delete(turnKey);
- failure ? pending.reject(failure) : pending.resolve(result);
- } else if (completedTurnId) {
- this.completedTurns.set(turnKey, failure);
- this.completedTurnResults.set(turnKey, result);
- }
- if (completedTurnId === this.currentTurnId) {
- this.currentThreadId = "";
- this.currentTurnId = "";
- }
- return true;
- }
-
- /** 请求响应确定静默线程 ID 后,重新分流早到的启动通知。 */
- private flushPendingThreadStartedNotifications() {
- const notifications = this.pendingThreadStartedNotifications.splice(0);
- notifications.forEach((params) => this.handleNotification("thread/started", params));
- }
-
- /** 补充历史落盘后再广播 turn 终态,确保界面完成状态可跨 Agent 重启恢复。 */
- private completeTurn(event: AgentEvent, params: JsonRecord, eventScope: ReturnType) {
- const turn = (params as unknown as CodexNotificationParams<"turn/completed">).turn;
- const turnId = turn.id;
- const turnKey = turnCacheKey(String(field(params, "threadId") || field(event, "thread_id") || ""), turnId);
- this.finalizingTurns.delete(turnKey);
- this.emit("agent_event", { agent: "codex", ...event });
- const pending = this.activeTurns.get(turnKey);
- const error = turn.error;
- const failure = error ? new CodexReportedError(error.message || "Codex turn failed") : null;
- if (pending) {
- this.activeTurns.delete(turnKey);
- failure ? pending.reject(failure) : pending.resolve(event);
- } else if (turnId) {
- this.completedTurns.set(turnKey, failure);
- }
- if (turnId === this.currentTurnId) {
- this.currentThreadId = "";
- this.currentTurnId = "";
- }
- this.emit("agent_done", { agent: "codex", usage: event.usage, ...eventScope });
- }
-
- /** 合并并广播 Agent 文本或执行输出增量。 */
- private emitDelta(itemType: string, params: ItemDeltaParams) {
- params = { ...params, threadId: params.threadId || this.currentThreadId, turnId: params.turnId || this.currentTurnId };
- const key = itemCacheKey(params);
- this.textByItem.set(key, itemType === "reasoning" ? this.appendReasoningText(key, params) : `${this.textByItem.get(key) || ""}${params.delta}`);
- const pending = this.pendingDeltas.get(key);
- if (pending) {
- pending.delta += params.delta;
- pending.itemType = itemType;
- pending.params = params;
- return;
- }
- this.pendingDeltas.set(key, {
- delta: params.delta,
- itemType,
- params,
- timer: setTimeout(() => this.flushDelta(key), STREAM_UPDATE_INTERVAL_MS),
- });
- }
-
- /** 合并短时间内的文本增量,减少 SSE 传输和前端渲染次数。 */
- private flushDelta(key: string) {
- const pending = this.pendingDeltas.get(key);
- if (!pending) return;
- clearTimeout(pending.timer);
- this.pendingDeltas.delete(key);
- if (pending.delta) this.emit("agent_event", { agent: "codex", type: "item.updated", item: { id: pending.params.itemId, type: pending.itemType, delta: pending.delta }, ...codexEventScope(pending.params as unknown as JsonRecord) });
- }
-
- /** 按 Codex summaryIndex 保存 reasoning 分段,顺序与线程历史一致。 */
- private appendReasoningText(key: string, params: ItemDeltaParams) {
- const segments = this.reasoningTextByItem.get(key) || new Map();
- this.reasoningTextByItem.set(key, segments);
- return appendReasoningDelta(segments, params.summaryIndex, params.delta);
- }
-
- /** turn 结束时发送最后一批增量并清理未收到 item.completed 的缓存。 */
- private finishTurnDeltas(threadId: string, turnId: string) {
- const prefix = `${turnCacheKey(threadId, turnId)}\0`;
- [...this.pendingDeltas.keys()].filter((key) => key.startsWith(prefix)).forEach((key) => this.flushDelta(key));
- [...this.textByItem.keys()].filter((key) => key.startsWith(prefix)).forEach((key) => this.textByItem.delete(key));
- [...this.reasoningTextByItem.keys()].filter((key) => key.startsWith(prefix)).forEach((key) => this.reasoningTextByItem.delete(key));
- [...this.startedItems.keys()].filter((key) => key.startsWith(prefix)).forEach((key) => this.startedItems.delete(key));
- [...this.itemSequences.keys()].filter((key) => key.startsWith(prefix)).forEach((key) => this.itemSequences.delete(key));
- this.nextItemSequences.delete(turnCacheKey(threadId, turnId));
- }
-
- /** 为一个 turn 内的 item 固定开始顺序,完成通知只更新内容。 */
- private assignItemSequence(threadId: string, turnId: string, itemId: string) {
- const key = itemCacheKey({ threadId, turnId, itemId });
- const existing = this.itemSequences.get(key);
- if (existing !== undefined) return existing;
- const turnKey = turnCacheKey(threadId, turnId);
- const sequence = (this.nextItemSequences.get(turnKey) || 0) + 1;
- this.nextItemSequences.set(turnKey, sequence);
- this.itemSequences.set(key, sequence);
- return sequence;
- }
-
- /** 在通知或 turn/start 响应到达时回调一次 turn 启动状态。 */
- private notifyTurnStarted(threadId: string, turnId: string, fallback?: PendingTurnStart) {
- if (!threadId || !turnId) return;
- const pending = this.pendingTurnStart;
- const registration = pending?.threadId === threadId && (!pending.turnId || pending.turnId === turnId) ? pending : fallback;
- if (registration && !registration.turnId) registration.turnId = turnId;
- const key = turnCacheKey(threadId, turnId);
- if (this.startedTurnKeys.has(key)) return;
- if (!registration) return;
- this.startedTurnKeys.add(key);
- registration.onTurn?.(turnId);
- }
-
- /** 自动回复 app-server 发起的授权或交互请求。 */
- private answerServerRequest(message: JsonRecord) {
- const method = String(message.method);
- const params = (field(message, "params") as JsonRecord) || {};
- const threadId = String(field(params, "threadId") || this.currentThreadId);
- if (this.silentThreadIds.has(threadId)) {
- const result = method === "item/permissions/requestApproval" ? { permissions: {}, scope: "turn" } : { decision: "decline" };
- this.write({ id: message.id, result });
- return;
- }
- if (["item/commandExecution/requestApproval", "item/fileChange/requestApproval", "item/permissions/requestApproval"].includes(method)) {
- const requestId = String(message.id);
- this.approvalRequests.set(requestId, { id: Number(message.id), method, params });
- this.emit("codex_approval", { requestId, method, ...params });
- return;
- }
- const result = method === "mcpServer/elicitation/request" ? { action: "accept", content: {}, _meta: null } : { decision: "decline" };
- this.write({ id: message.id, result });
- this.emit("agent_event", { agent: "codex", type: "server.request", method, params, result });
- }
-
- /** 完成指定 JSON-RPC 请求。 */
- private resolve(id: number, result: unknown) {
- const pending = this.pending.get(id);
- if (pending) (this.pending.delete(id), pending.resolve(result));
- }
-
- /** 拒绝指定 JSON-RPC 请求。 */
- private reject(id: number, message: string) {
- const pending = this.pending.get(id);
- if (pending) (this.pending.delete(id), pending.reject(new Error(message)));
- }
-
- /** 拒绝进程退出时仍未完成的请求与 turn。 */
- private failAll(message: string, reported = false) {
- if (this.failing) return;
- this.failing = true;
- this.failureMessage = message;
- this.approvalRequests.forEach((request, requestId) => this.emit("codex_approval_resolved", { ...request.params, requestId, decision: request.decision || "cancel" }));
- const failedTurns = new Map();
- this.activeTurns.forEach(({ threadId, turnId, prompt, messageText }, key) => {
- if (this.silentThreadIds.has(threadId)) return;
- if (!this.finalizingTurns.has(key)) failedTurns.set(key, { threadId, turnId, prompt, messageText });
- });
- const pendingStart = this.pendingTurnStart;
- if (pendingStart?.turnId && !this.silentThreadIds.has(pendingStart.threadId)) {
- const key = turnCacheKey(pendingStart.threadId, pendingStart.turnId);
- if (!this.finalizingTurns.has(key) && !failedTurns.has(key)) failedTurns.set(key, { threadId: pendingStart.threadId, turnId: pendingStart.turnId, prompt: pendingStart.prompt, messageText: pendingStart.messageText });
- }
- const finalizing = [...this.finalizingTurns.values()];
- const persistence = [...failedTurns.values()].map(({ threadId, turnId, prompt, messageText }) => {
- const turn = { id: turnId, status: "failed", error: { message }, ...(prompt ? { input: prompt } : {}), ...(messageText ? { messageText } : {}) };
- return this.eventHistory.recordTurn({ threadId, turnId, turn }).catch((historyError) => logger.warn("Failed to persist Codex turn failure", { threadId, turnId, error: historyError }));
- });
- const error = reported || failedTurns.size || finalizing.length ? new CodexReportedError(message) : new Error(message);
- this.pendingDeltas.forEach((item) => clearTimeout(item.timer));
- this.pendingDeltas.clear();
- this.textByItem.clear();
- this.reasoningTextByItem.clear();
- this.startedItems.clear();
- this.itemSequences.clear();
- this.nextItemSequences.clear();
- this.plansByTurn.clear();
- this.completedTurns.clear();
- this.completedTurnResults.clear();
- this.approvalRequests.clear();
- this.silentThreadIds.clear();
- this.pendingSilentThreadStarts.clear();
- this.pendingThreadStartedNotifications.length = 0;
- this.structuredOutputByTurn.clear();
- this.pendingTurnStart = undefined;
- this.startedTurnKeys.clear();
- this.lastUsage = null;
- this.currentThreadId = "";
- this.currentTurnId = "";
- void Promise.all([...finalizing, ...persistence]).then(() => {
- failedTurns.forEach(({ threadId, turnId, prompt, messageText }) => {
- const turn = { id: turnId, status: "failed", error: { message }, ...(prompt ? { input: prompt } : {}), ...(messageText ? { messageText } : {}) };
- this.emit("agent_event", { agent: "codex", type: "turn.completed", status: "failed", error: { message }, thread_id: threadId, turn_id: turnId, turn });
- this.emit("agent_done", { agent: "codex", status: "failed", error: { message }, thread_id: threadId, turn_id: turnId });
- });
- this.pending.forEach((item) => item.reject(error));
- this.activeTurns.forEach((item) => item.reject(error));
- this.pending.clear();
- this.activeTurns.clear();
- this.finalizingTurns.clear();
- });
- }
-}
-
-/** 将 Codex 的字符串、摘要数组或文本对象转换为展示文本。 */
-function readableCodexText(value: unknown): string {
- if (typeof value === "string") return value.trim();
- if (Array.isArray(value)) return value.map(readableCodexText).filter(Boolean).join("\n");
- if (!value || typeof value !== "object") return "";
- return readableCodexText(field(value, "text"));
-}
-
-/** 合并单个 reasoning 分段增量并按 summaryIndex 输出。 */
-export function appendReasoningDelta(segments: Map, summaryIndex: number | undefined, delta: string) {
- const index = Number.isInteger(summaryIndex) ? Number(summaryIndex) : 0;
- segments.set(index, `${segments.get(index) || ""}${delta}`);
- return [...segments.entries()].sort(([left], [right]) => left - right).map(([, text]) => text.trim()).filter(Boolean).join("\n");
-}
-
-/** 生成仅供进程内缓存使用的完整 turn 与 item 作用域键。 */
-function itemCacheKey(scope: { threadId: string; turnId: string; itemId: string }) {
- return `${turnCacheKey(scope.threadId, scope.turnId)}\0${scope.itemId}`;
-}
-
-function turnCacheKey(threadId: string, turnId: string) {
- return `${threadId}\0${turnId}`;
-}
-
-/** 生成 Codex 调用 Canvas Agent MCP 的启动命令。 */
-function canvasAgentMcpCommand() {
- const current = process.argv.find((arg) => /index\.(t|j)s$/.test(arg)) || "";
- const entry = path.resolve(current || fileURLToPath(new URL("../index.js", import.meta.url)));
- const tsx = path.join(path.dirname(entry), "..", "node_modules", "tsx", "dist", "cli.mjs");
- return entry.endsWith(".ts") ? { command: process.execPath, args: [tsx, entry, "mcp"] } : { command: process.execPath, args: [entry, "mcp"] };
-}
-
-/** 生成 Codex app-server 使用的 MCP 配置。 */
-function codexConfig(permissionMode: AgentPermissionMode) {
- return { model_reasoning_summary: "auto", ...(permissionMode === "automatic" ? { approvals_reviewer: "auto_review" } : {}), mcp_servers: { "infinite-canvas": { command: canvasAgentMcp.command, args: canvasAgentMcp.args, default_tools_approval_mode: "approve", startup_timeout_sec: 20, tool_timeout_sec: 90 } } };
-}
-
-function threadSettings(permissionMode: AgentPermissionMode) {
- return { approvalPolicy: permissionMode === "full" ? "never" as const : "on-request" as const, sandbox: permissionMode === "full" ? "danger-full-access" as const : "workspace-write" as const, config: codexConfig(permissionMode) };
-}
-
-function skillDraftThreadSettings(cwd: string) {
- return {
- approvalPolicy: "never" as const,
- sandbox: "read-only" as const,
- config: { model_reasoning_summary: "auto", mcp_servers: {} },
- cwd,
- developerInstructions: SKILL_DRAFT_INSTRUCTIONS,
- ephemeral: true,
- };
-}
-
-function turnSettings(permissionMode: AgentPermissionMode) {
- return {
- approvalPolicy: permissionMode === "full" ? "never" as const : "on-request" as const,
- sandboxPolicy: permissionMode === "full" ? { type: "dangerFullAccess" as const } : { type: "workspaceWrite" as const, networkAccess: false },
- };
-}
-
-function skillDraftTurnSettings() {
- return { approvalPolicy: "never" as const, sandboxPolicy: { type: "readOnly" as const, networkAccess: false } };
-}
-
-/** 将文本、本地图片和显式 Skill 转换为 Codex turn 输入。 */
-function codexInput(prompt: string, images: string[], skill?: CodexSkillSelector): CodexTurnInput[] {
- const text = skill && !mentionsSkill(prompt, skill.name) ? `$${skill.name} ${prompt}` : prompt;
- return [
- { type: "text", text, text_elements: [] },
- ...images.map((file) => ({ type: "localImage", path: file })),
- ...(skill ? [{ type: "skill", ...skill } as CodexTurnInput] : []),
- ];
-}
-
-function mentionsSkill(prompt: string, name: string) {
- return new RegExp(`\\$${name}(?![A-Za-z0-9_-]|:[A-Za-z0-9_-])`).test(prompt);
-}
-
-/** 将 app-server 通知转换为前端使用的 Agent 事件。 */
-function normalizeCodexNotification(method: string, params: JsonRecord): AgentEvent | null {
- const scope = codexEventScope(params);
- if (method === "thread/started") return { type: "thread.started", ...scope };
- if (method === "turn/started") return { type: "turn.started", ...scope };
- if (method === "turn/completed") return { type: "turn.completed", status: field(field(params, "turn"), "status"), error: field(field(params, "turn"), "error"), usage: null, duration_ms: field(field(params, "turn"), "durationMs"), ...scope };
- if (method === "turn/plan/updated") return { type: "plan.updated", explanation: field(params, "explanation"), plan: field(params, "plan"), ...scope };
- if (method === "item/started") return { type: "item.started", item: normalizeItem(field(params, "item")), ...scope };
- if (method === "item/completed") return { type: "item.completed", item: normalizeItem(field(params, "item")), ...scope };
- if (method === "error") return { type: "error", message: field(field(params, "error"), "message"), ...scope };
- return null;
-}
-
-/** 提取 Codex 事件所属的线程和 turn。 */
-function codexEventScope(params: JsonRecord) {
- const threadId = String(field(params, "threadId") || field(field(params, "thread"), "id") || "");
- const turnId = String(field(params, "turnId") || field(field(params, "turn"), "id") || "");
- return { ...(threadId ? { thread_id: threadId } : {}), ...(turnId ? { turn_id: turnId } : {}) };
-}
-
-/** 统一 app-server item 的类型和参数格式。 */
-function normalizeItem(item: unknown) {
- const value = item && typeof item === "object" ? { ...(item as JsonRecord) } : {};
- if (value.type === "agentMessage") value.type = "agent_message";
- if (value.type === "mcpToolCall") value.type = "mcp_tool_call";
- if (value.type === "commandExecution") value.type = "command_execution";
- if (value.type === "fileChange") value.type = "file_change";
- if (value.type === "dynamicToolCall") value.type = "dynamic_tool_call";
- if (value.type === "collabToolCall" || value.type === "collabAgentToolCall") value.type = "collab_tool_call";
- if (value.type === "webSearch") value.type = "web_search";
- if (value.type === "imageView") value.type = "image_view";
- if (value.type === "imageGeneration") value.type = "image_generation";
- if (value.type === "contextCompaction") value.type = "context_compaction";
- if (value.type === "agent_message" && typeof value.id === "string") value.text = String(value.text || "");
- if ("arguments" in value) value.arguments = parseMaybeJson(value.arguments);
- return value;
-}
-
-function mergeDefinedRecord(started: JsonRecord, completed: JsonRecord) {
- const merged = { ...started };
- Object.entries(completed).forEach(([key, value]) => {
- if (value !== undefined) merged[key] = value;
- });
- return merged;
-}
-
-/** 将 Codex token usage 转换为前端字段。 */
-function normalizeUsage(params: CodexNotificationParams<"thread/tokenUsage/updated">) {
- const last = params.tokenUsage.last;
- return {
- input_tokens: last.inputTokens,
- cached_input_tokens: last.cachedInputTokens,
- output_tokens: last.outputTokens,
- reasoning_output_tokens: last.reasoningOutputTokens,
- };
-}
-
-/** 尝试将字符串解析为 JSON,失败时保留原值。 */
-function parseMaybeJson(value: unknown) {
- if (typeof value !== "string") return value;
- try {
- return JSON.parse(value);
- } catch {
- return value;
- }
-}
-
-/** 定位当前依赖中 Codex CLI 的执行文件。 */
-function codexBin() {
- return path.join(path.dirname(require.resolve("@openai/codex/package.json")), "bin", "codex.js");
-}
diff --git a/canvas-agent/src/agent/codex-event-history.ts b/canvas-agent/src/agent/codex-event-history.ts
deleted file mode 100644
index a2650c4..0000000
--- a/canvas-agent/src/agent/codex-event-history.ts
+++ /dev/null
@@ -1,170 +0,0 @@
-import fs from "node:fs/promises";
-import path from "node:path";
-
-import { CONFIG_DIR } from "../config.js";
-import type { CodexSupplementalHistory, CodexSupplementalHistoryItem, CodexSupplementalHistoryTurn } from "./codex-history.js";
-
-type CodexEventHistoryData = { version: 1; items: CodexSupplementalHistoryItem[]; turns: CodexSupplementalHistoryTurn[] };
-
-const MAX_ITEMS = 5000;
-const MAX_STRING_LENGTH = 100_000;
-
-export const CODEX_EVENT_HISTORY_FILE = path.join(CONFIG_DIR, "codex-event-history.json");
-
-/** 保存 Codex 持久线程投影可能省略的实时完成事件。 */
-export class CodexEventHistory {
- private data?: CodexEventHistoryData;
- private queue: Promise = Promise.resolve();
-
- constructor(private file = CODEX_EVENT_HISTORY_FILE) {}
-
- /** 按 threadId、turnId 和 itemId 新增或更新一条补充事件。 */
- record(entry: CodexSupplementalHistoryItem) {
- return this.run(async () => {
- const data = await this.load();
- const index = data.items.findIndex((item) => sameItem(item, entry));
- const previous = index >= 0 ? data.items[index] : undefined;
- const nextEntry = normalizeEntry({
- ...entry,
- ...(entry.sequence === undefined && previous?.sequence !== undefined ? { sequence: previous.sequence } : {}),
- item: mergeRecord(previous?.item, entry.item),
- });
- const items = [...data.items];
- if (index >= 0) items[index] = nextEntry;
- else items.push(nextEntry);
- const nextData = { ...data, items: items.slice(-MAX_ITEMS) };
- await this.save(nextData);
- this.data = nextData;
- });
- }
-
- /** 保存 turn 终态,使标准线程历史尚未物化时仍可恢复完整轮次。 */
- recordTurn(entry: CodexSupplementalHistoryTurn) {
- return this.run(async () => {
- const data = await this.load();
- const index = data.turns.findIndex((turn) => sameTurn(turn, entry));
- const previous = index >= 0 ? data.turns[index] : undefined;
- const nextEntry = normalizeTurn({ ...entry, turn: mergeRecord(previous?.turn, entry.turn) });
- const turns = [...data.turns];
- if (index >= 0) turns[index] = nextEntry;
- else turns.push(nextEntry);
- const nextData = { ...data, turns: turns.slice(-MAX_ITEMS) };
- await this.save(nextData);
- this.data = nextData;
- });
- }
-
- /** 按 item 开始顺序返回指定线程的补充事件。 */
- readThread(threadId: string) {
- return this.run(async (): Promise => {
- const data = await this.load();
- return {
- items: data.items.filter((item) => item.threadId === threadId).sort(compareEntries).map(cloneEntry),
- turns: data.turns.filter((turn) => turn.threadId === threadId).map(cloneTurn),
- };
- });
- }
-
- /** 归档线程后删除其补充事件。 */
- removeThread(threadId: string) {
- return this.run(async () => {
- const data = await this.load();
- const items = data.items.filter((item) => item.threadId !== threadId);
- const turns = data.turns.filter((turn) => turn.threadId !== threadId);
- if (items.length === data.items.length && turns.length === data.turns.length) return;
- const nextData = { version: 1 as const, items, turns };
- await this.save(nextData);
- this.data = nextData;
- });
- }
-
- private run(task: () => Promise) {
- const result = this.queue.then(task, task);
- this.queue = result.then(() => undefined, () => undefined);
- return result;
- }
-
- private async load() {
- if (this.data) return this.data;
- try {
- const value = JSON.parse(await fs.readFile(this.file, "utf8")) as Partial;
- this.data = value.version === 1 && Array.isArray(value.items) && Array.isArray(value.turns) ? { version: 1, items: value.items.map(normalizeEntry), turns: value.turns.map(normalizeTurn) } : emptyHistory();
- } catch (error) {
- if ((error as NodeJS.ErrnoException).code !== "ENOENT" && !(error instanceof SyntaxError)) throw error;
- this.data = emptyHistory();
- }
- return this.data;
- }
-
- private async save(data: CodexEventHistoryData) {
- await fs.mkdir(path.dirname(this.file), { recursive: true });
- const temporaryFile = `${this.file}.${process.pid}.${Date.now()}.tmp`;
- try {
- await fs.writeFile(temporaryFile, JSON.stringify(data, null, 2));
- await fs.rename(temporaryFile, this.file);
- } finally {
- await fs.unlink(temporaryFile).catch(() => undefined);
- }
- }
-}
-
-export const codexEventHistory = new CodexEventHistory();
-
-function emptyHistory(): CodexEventHistoryData {
- return { version: 1, items: [], turns: [] };
-}
-
-function sameItem(left: CodexSupplementalHistoryItem, right: CodexSupplementalHistoryItem) {
- return left.threadId === right.threadId && left.turnId === right.turnId && left.itemId === right.itemId;
-}
-
-function sameTurn(left: CodexSupplementalHistoryTurn, right: CodexSupplementalHistoryTurn) {
- return left.threadId === right.threadId && left.turnId === right.turnId;
-}
-
-function cloneEntry(entry: CodexSupplementalHistoryItem) {
- return structuredClone(entry);
-}
-
-function cloneTurn(entry: CodexSupplementalHistoryTurn) {
- return structuredClone(entry);
-}
-
-function compareEntries(left: CodexSupplementalHistoryItem, right: CodexSupplementalHistoryItem) {
- if (left.sequence !== undefined && right.sequence !== undefined && left.sequence !== right.sequence) return left.sequence - right.sequence;
- if (left.sequence !== undefined) return -1;
- if (right.sequence !== undefined) return 1;
- return 0;
-}
-
-function normalizeEntry(entry: CodexSupplementalHistoryItem): CodexSupplementalHistoryItem {
- return {
- ...entry,
- ...(entry.sequence === undefined ? {} : { sequence: entry.sequence }),
- item: truncateRecord(entry.item),
- };
-}
-
-function normalizeTurn(entry: CodexSupplementalHistoryTurn): CodexSupplementalHistoryTurn {
- return { ...entry, turn: truncateRecord({ ...entry.turn, id: entry.turnId }) };
-}
-
-function mergeRecord(previous: Record | undefined, next: Record) {
- if (!previous) return next;
- const merged = { ...previous };
- Object.entries(next).forEach(([key, value]) => {
- if (value !== undefined) merged[key] = value;
- });
- return merged;
-}
-
-function truncateRecord(value: Record) {
- return truncateValue(value) as Record;
-}
-
-function truncateValue(value: unknown): unknown {
- if (typeof value === "string") return value.length > MAX_STRING_LENGTH ? `${value.slice(0, MAX_STRING_LENGTH)}\n[输出已截断]` : value;
- if (Array.isArray(value)) return value.map(truncateValue);
- if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, truncateValue(item)]));
- return value;
-}
diff --git a/canvas-agent/src/agent/codex-history.test.ts b/canvas-agent/src/agent/codex-history.test.ts
deleted file mode 100644
index 1fc8dc4..0000000
--- a/canvas-agent/src/agent/codex-history.test.ts
+++ /dev/null
@@ -1,450 +0,0 @@
-import assert from "node:assert/strict";
-import fs from "node:fs/promises";
-import os from "node:os";
-import path from "node:path";
-import test from "node:test";
-
-import { appendReasoningDelta } from "./codex-client.js";
-import { CodexEventHistory } from "./codex-event-history.js";
-import { settledTurnIds, summarizeCodexThread, threadMessages } from "./codex-history.js";
-
-test("线程摘要提取结构化状态类型", () => {
- assert.equal(summarizeCodexThread({ id: "thread-1", status: { type: "notLoaded" } }).status, "notLoaded");
- assert.equal(summarizeCodexThread({ id: "thread-1", status: "idle" }).status, "idle");
-});
-
-test("reasoning 增量按 summaryIndex 聚合", () => {
- const segments = new Map();
- appendReasoningDelta(segments, 1, "第二");
- appendReasoningDelta(segments, 0, "第一");
- assert.equal(appendReasoningDelta(segments, 1, "段"), "第一\n第二段");
-});
-
-test("只有终态 turn 会交给历史快照作为权威内容", () => {
- assert.deepEqual(settledTurnIds({ turns: [
- { id: "completed", status: "completed" },
- { id: "failed", status: "failed" },
- { id: "interrupted", status: "interrupted" },
- { id: "running", status: "inProgress" },
- ] }), ["completed", "failed", "interrupted"]);
-});
-
-test("标准历史的运行中状态不会覆盖本地已经完成的 turn", () => {
- const supplemental = {
- items: [{ threadId: "thread-1", turnId: "turn-1", itemId: "assistant-1", sequence: 1, item: { id: "assistant-1", type: "agent_message", text: "完整回答" } }],
- turns: [{ threadId: "thread-1", turnId: "turn-1", turn: { id: "turn-1", status: "completed", input: "问题" } }],
- };
- const thread = { id: "thread-1", turns: [{ id: "turn-1", status: "inProgress", items: [] }] };
-
- assert.deepEqual(settledTurnIds(thread, supplemental), ["turn-1"]);
- assert.deepEqual(threadMessages(thread, [], supplemental).map((item) => item.text), ["问题", "完整回答"]);
-});
-
-test("标准历史进入终态后保持权威状态", () => {
- const supplemental = {
- items: [],
- turns: [{ threadId: "thread-1", turnId: "turn-1", turn: { id: "turn-1", status: "completed", input: "问题" } }],
- };
- const thread = { id: "thread-1", turns: [{ id: "turn-1", status: "failed", error: { message: "标准错误" }, items: [] }] };
-
- const messages = threadMessages(thread, [], supplemental);
- assert.deepEqual(settledTurnIds(thread, supplemental), ["turn-1"]);
- assert.equal(messages.at(-1)?.role, "error");
- assert.equal(messages.at(-1)?.text, "标准错误");
-});
-
-test("线程历史将多个 reasoning 条目投影为一张稳定卡片", () => {
- const messages = threadMessages({
- id: "thread-1",
- turns: [{
- id: "turn-1",
- status: "completed",
- items: [
- { id: "user-1", type: "userMessage", content: [{ type: "text", text: "问题" }] },
- { id: "reasoning-1", type: "reasoning", summary: ["第一段", "第二段"] },
- { id: "reasoning-2", type: "reasoning", summary: [{ text: "第三段" }] },
- { id: "assistant-1", type: "agentMessage", text: "回答" },
- ],
- }],
- });
- const reasoning = messages.filter((item) => item.itemId === "synthetic:reasoning");
- assert.equal(reasoning.length, 1);
- assert.equal(reasoning[0].id, "thread-1:turn-1:synthetic:reasoning");
- assert.equal(reasoning[0].text, "第一段\n第二段\n\n第三段");
- assert.deepEqual(reasoning[0].activityItems, { "reasoning-1": "第一段\n第二段", "reasoning-2": "第三段" });
-});
-
-test("空 reasoning 不进入完成后的线程历史", () => {
- const messages = threadMessages({ id: "thread-1", turns: [{ id: "turn-1", status: "completed", items: [{ id: "reasoning-1", type: "reasoning", summary: [] }] }] });
- assert.equal(messages.some((item) => item.itemId === "synthetic:reasoning"), false);
-});
-
-test("用户消息使用与实时消息一致的 turn 级稳定 ID", () => {
- const messages = threadMessages({ id: "thread-1", turns: [{ id: "turn-1", status: "completed", items: [{ id: "codex-user-1", type: "userMessage", content: [{ type: "text", text: "问题" }] }] }] });
- assert.equal(messages[0].id, "thread-1:turn-1:synthetic:user");
- assert.equal(messages[0].itemId, "synthetic:user");
-});
-
-test("显式用户正文优先于 Codex 自动补入的 Skill 前缀", () => {
- const messages = threadMessages({ id: "thread-1", turns: [{ id: "turn-1", status: "completed", items: [{
- id: "codex-user-1",
- type: "userMessage",
- content: [
- { type: "text", text: "$product-grid 生成产品图" },
- { type: "skill", name: "product-grid", path: "D:\\site\\.agents\\skills\\product-grid\\SKILL.md" },
- ],
- }] }] }, [], { items: [], turns: [{ threadId: "thread-1", turnId: "turn-1", turn: { messageText: "生成产品图" } }] });
- assert.equal(messages[0].text, "生成产品图");
-});
-
-test("没有显式用户正文时保留用户自行输入的 Skill 标记和正文", () => {
- const messages = threadMessages({ id: "thread-1", turns: [{ id: "turn-1", status: "completed", items: [{
- id: "codex-user-1",
- type: "userMessage",
- content: [
- { type: "text", text: "$product-grid 生成产品图" },
- { type: "skill", name: "product-grid", path: "D:\\site\\.agents\\skills\\product-grid\\SKILL.md" },
- ],
- }] }] });
- assert.equal(messages[0].text, "$product-grid 生成产品图");
-});
-
-test("Codex 历史省略命令时使用补充事件恢复完整命令卡片", () => {
- const messages = threadMessages({
- id: "thread-1",
- turns: [{
- id: "turn-1",
- status: "completed",
- items: [
- { id: "user-1", type: "userMessage", content: [{ type: "text", text: "执行命令" }] },
- { id: "assistant-1", type: "agentMessage", text: "完成" },
- ],
- }],
- }, [], { items: [{
- threadId: "thread-1",
- turnId: "turn-1",
- itemId: "command-1",
- item: { id: "command-1", type: "command_execution", command: "Get-Location", status: "completed", exitCode: 0, aggregatedOutput: "D:\\infinite-canvas" },
- }], turns: [] });
-
- const command = messages.find((item) => item.itemId === "command-1");
- assert.equal(command?.text, "Get-Location");
- assert.deepEqual(command?.detail, {
- kind: "command",
- status: "completed",
- rows: [{ label: "退出状态", value: "0" }],
- output: "D:\\infinite-canvas",
- });
-});
-
-test("Codex 标准历史与补充事件重复时以标准历史为准", () => {
- const messages = threadMessages({
- id: "thread-1",
- turns: [{ id: "turn-1", status: "completed", items: [{ id: "command-1", type: "commandExecution", command: "标准命令", status: "completed" }] }],
- }, [], { items: [{
- threadId: "thread-1",
- turnId: "turn-1",
- itemId: "command-1",
- item: { id: "command-1", type: "command_execution", command: "补充命令", status: "completed" },
- }], turns: [] });
-
- assert.equal(messages.filter((item) => item.itemId === "command-1").length, 1);
- assert.equal(messages.find((item) => item.itemId === "command-1")?.text, "标准命令");
-});
-
-test("标准历史重写 item id 时仍与同一条实时事件合并", () => {
- const messages = threadMessages({
- id: "thread-1",
- turns: [{ id: "turn-1", status: "completed", items: [
- { id: "item-13", type: "userMessage", content: [{ type: "text", text: "执行命令" }] },
- { id: "item-14", type: "agentMessage", text: "我将仅查看当前路径。" },
- { id: "item-15", type: "agentMessage", text: "完成。" },
- ] }],
- }, [], { items: [
- { threadId: "thread-1", turnId: "turn-1", itemId: "msg-commentary", sequence: 2, item: { id: "msg-commentary", type: "agent_message", text: "我将仅查看当前路径。" } },
- { threadId: "thread-1", turnId: "turn-1", itemId: "command-1", sequence: 3, item: { id: "command-1", type: "command_execution", command: "Get-Location", status: "completed" } },
- { threadId: "thread-1", turnId: "turn-1", itemId: "msg-final", sequence: 4, item: { id: "msg-final", type: "agent_message", text: "完成。" } },
- ], turns: [] });
-
- assert.deepEqual(messages.map((item) => item.itemId), ["synthetic:user", "msg-commentary", "command-1", "msg-final"]);
-});
-
-test("标准历史正文损坏且重写 item id 时不会重复显示同一条回复", () => {
- const messages = threadMessages({
- id: "thread-1",
- turns: [{ id: "turn-1", status: "completed", items: [{ id: "item-38", type: "agentMessage", text: "连接��" }] }],
- }, [], { items: [{
- threadId: "thread-1",
- turnId: "turn-1",
- itemId: "msg-007367",
- sequence: 1,
- item: { id: "msg-007367", type: "agent_message", text: "连接服务" },
- }], turns: [] });
-
- const replies = messages.filter((item) => item.role === "assistant");
- assert.equal(replies.length, 1);
- assert.equal(replies[0].itemId, "msg-007367");
- assert.equal(replies[0].text, "连接服务");
-});
-
-test("标准历史同时保留损坏临时条目和稳定条目时移除损坏副本", () => {
- const cleanText = "目前能排除“网页没开”和“Canvas Agent 没连”:前端和 Agent 均正常。";
- const messages = threadMessages({
- id: "thread-1",
- turns: [{ id: "turn-1", status: "completed", items: [
- { id: "user-1", type: "userMessage", content: [{ type: "text", text: "检查连接" }] },
- { id: "item-38", type: "agentMessage", text: "目前能排除“网页没开”和“Canvas Agent 没连��:前端和 Agent 均正常。" },
- { id: "command-1", type: "commandExecution", command: "Get-NetTCPConnection", status: "completed" },
- { id: "msg-stable", type: "agentMessage", text: cleanText },
- ] }],
- }, [], { items: [
- { threadId: "thread-1", turnId: "turn-1", itemId: "command-1", sequence: 1, item: { id: "command-1", type: "command_execution", command: "Get-NetTCPConnection", status: "completed" } },
- { threadId: "thread-1", turnId: "turn-1", itemId: "msg-stable", sequence: 2, item: { id: "msg-stable", type: "agent_message", text: cleanText } },
- ], turns: [] });
-
- const replies = messages.filter((item) => item.role === "assistant");
- assert.equal(replies.length, 1);
- assert.equal(replies[0].itemId, "msg-stable");
- assert.equal(replies[0].text, cleanText);
-});
-
-test("标准历史条目稀疏时按字段补全补充事件", () => {
- const messages = threadMessages({
- id: "thread-1",
- turns: [{ id: "turn-1", status: "completed", items: [{ id: "command-1", type: "commandExecution", command: "标准命令", status: "completed" }] }],
- }, [], { items: [{
- threadId: "thread-1",
- turnId: "turn-1",
- itemId: "command-1",
- sequence: 1,
- item: { id: "command-1", type: "command_execution", command: "补充命令", cwd: "D:\\infinite-canvas", aggregatedOutput: "输出", exitCode: 0 },
- }], turns: [] });
-
- const command = messages.find((item) => item.itemId === "command-1");
- assert.equal(command?.text, "标准命令");
- assert.deepEqual(command?.detail, {
- kind: "command",
- status: "completed",
- rows: [{ label: "工作目录", value: "D:\\infinite-canvas" }, { label: "退出状态", value: "0" }],
- output: "输出",
- });
-});
-
-test("标准历史的 falsy 字段仍优先于补充事件", () => {
- const messages = threadMessages({
- id: "thread-1",
- turns: [{ id: "turn-1", status: "completed", items: [{ id: "command-1", type: "commandExecution", command: "", cwd: null, status: "completed", exitCode: 0, success: false, aggregatedOutput: "" }] }],
- }, [], { items: [{
- threadId: "thread-1",
- turnId: "turn-1",
- itemId: "command-1",
- sequence: 1,
- item: { id: "command-1", type: "command_execution", command: "补充命令", cwd: "补充目录", exitCode: 9, success: true, aggregatedOutput: "补充输出" },
- }], turns: [] });
-
- const command = messages.find((item) => item.itemId === "command-1");
- assert.equal(command?.text, "命令执行失败");
- assert.deepEqual(command?.detail, { kind: "command", status: "failed", rows: [{ label: "退出状态", value: "0" }], output: "" });
-});
-
-test("补充事件按 item 开始顺序插入标准历史锚点之间", () => {
- const messages = threadMessages({
- id: "thread-1",
- turns: [{ id: "turn-1", status: "completed", items: [
- { id: "user-1", type: "userMessage", content: [{ type: "text", text: "执行" }] },
- { id: "second", type: "commandExecution", command: "second", status: "completed" },
- { id: "assistant", type: "agentMessage", text: "完成" },
- ] }],
- }, [], { items: [
- { threadId: "thread-1", turnId: "turn-1", itemId: "first", sequence: 1, item: { id: "first", type: "command_execution", command: "first", status: "completed" } },
- { threadId: "thread-1", turnId: "turn-1", itemId: "second", sequence: 2, item: { id: "second", type: "command_execution", command: "补充 second", status: "completed" } },
- { threadId: "thread-1", turnId: "turn-1", itemId: "third", sequence: 3, item: { id: "third", type: "command_execution", command: "third", status: "completed" } },
- { threadId: "thread-1", turnId: "turn-1", itemId: "assistant", sequence: 4, item: { id: "assistant", type: "agent_message", text: "补充回答" } },
- ], turns: [] });
-
- assert.deepEqual(messages.map((item) => item.itemId), ["synthetic:user", "first", "second", "third", "assistant"]);
- assert.equal(messages.find((item) => item.itemId === "second")?.text, "second");
- assert.equal(messages.find((item) => item.itemId === "assistant")?.text, "完成");
-});
-
-test("补充事件写入本地 JSON 后可在 Agent 重启后恢复", async (context) => {
- const directory = await fs.mkdtemp(path.join(os.tmpdir(), "canvas-agent-history-"));
- context.after(() => fs.rm(directory, { recursive: true, force: true }));
- const file = path.join(directory, "codex-event-history.json");
- const entry = {
- threadId: "thread-1",
- turnId: "turn-1",
- itemId: "command-1",
- item: { id: "command-1", type: "command_execution", command: "Get-Location", status: "completed" },
- };
-
- await new CodexEventHistory(file).record(entry);
-
- assert.deepEqual(await new CodexEventHistory(file).readThread("thread-1"), { items: [entry], turns: [] });
-});
-
-test("补充历史 JSON 损坏后会从空历史恢复并允许重新写入", async (context) => {
- const directory = await fs.mkdtemp(path.join(os.tmpdir(), "canvas-agent-history-"));
- context.after(() => fs.rm(directory, { recursive: true, force: true }));
- const file = path.join(directory, "codex-event-history.json");
- await fs.writeFile(file, "{\"version\":1,\"items\":[");
- const history = new CodexEventHistory(file);
-
- assert.deepEqual(await history.readThread("thread-1"), { items: [], turns: [] });
- await history.record({ threadId: "thread-1", turnId: "turn-1", itemId: "item-1", item: { id: "item-1", type: "agent_message", text: "恢复成功" } });
-
- assert.deepEqual(await new CodexEventHistory(file).readThread("thread-1"), {
- items: [{ threadId: "thread-1", turnId: "turn-1", itemId: "item-1", item: { id: "item-1", type: "agent_message", text: "恢复成功" } }],
- turns: [],
- });
-});
-
-test("标准历史尚未物化 turn 时从本地终态事件恢复完整对话", async (context) => {
- const directory = await fs.mkdtemp(path.join(os.tmpdir(), "canvas-agent-history-"));
- context.after(() => fs.rm(directory, { recursive: true, force: true }));
- const file = path.join(directory, "codex-event-history.json");
- const history = new CodexEventHistory(file);
- await history.record({ threadId: "thread-1", turnId: "turn-1", itemId: "command-1", sequence: 1, item: { id: "command-1", type: "command_execution", command: "Get-Location", status: "completed", exitCode: 0, aggregatedOutput: "D:\\infinite-canvas" } });
- await history.record({ threadId: "thread-1", turnId: "turn-1", itemId: "assistant-1", sequence: 2, item: { id: "assistant-1", type: "agent_message", text: "完成" } });
- await history.recordTurn({ threadId: "thread-1", turnId: "turn-1", turn: { id: "turn-1", status: "completed", input: "执行 Get-Location" } });
-
- const supplemental = await new CodexEventHistory(file).readThread("thread-1");
- const thread = { id: "thread-1", turns: [] };
- const messages = threadMessages(thread, [], supplemental);
-
- assert.deepEqual(messages.map((message) => message.itemId), ["synthetic:user", "command-1", "assistant-1"]);
- assert.equal(messages[0].text, "执行 Get-Location");
- assert.equal(messages[1].text, "Get-Location");
- assert.equal(messages[2].text, "完成");
- assert.deepEqual(settledTurnIds(thread, supplemental), ["turn-1"]);
-});
-
-test("归档线程只清除该线程的补充事件", async (context) => {
- const directory = await fs.mkdtemp(path.join(os.tmpdir(), "canvas-agent-history-"));
- context.after(() => fs.rm(directory, { recursive: true, force: true }));
- const history = new CodexEventHistory(path.join(directory, "codex-event-history.json"));
- const entry = (threadId: string) => ({ threadId, turnId: "turn-1", itemId: "command-1", item: { id: "command-1", type: "command_execution" } });
- await history.record(entry("thread-1"));
- await history.record(entry("thread-2"));
-
- await history.removeThread("thread-1");
-
- assert.deepEqual(await history.readThread("thread-1"), { items: [], turns: [] });
- assert.deepEqual(await history.readThread("thread-2"), { items: [entry("thread-2")], turns: [] });
-});
-
-test("补充事件更新时保留已有字段并限制单项输出大小", async (context) => {
- const directory = await fs.mkdtemp(path.join(os.tmpdir(), "canvas-agent-history-"));
- context.after(() => fs.rm(directory, { recursive: true, force: true }));
- const history = new CodexEventHistory(path.join(directory, "codex-event-history.json"));
- await history.record({ threadId: "thread-1", turnId: "turn-1", itemId: "command-1", sequence: 1, item: { id: "command-1", type: "command_execution", command: "Get-Location", cwd: "D:\\infinite-canvas" } });
- await history.record({ threadId: "thread-1", turnId: "turn-1", itemId: "command-1", item: { id: "command-1", status: "completed", aggregatedOutput: "x".repeat(100_001) } });
-
- const [entry] = (await history.readThread("thread-1")).items;
- assert.equal(entry.sequence, 1);
- assert.equal(entry.item.command, "Get-Location");
- assert.equal(entry.item.cwd, "D:\\infinite-canvas");
- assert.equal(String(entry.item.aggregatedOutput).endsWith("[输出已截断]"), true);
-});
-
-test("补充事件落盘失败时不污染内存读取", async (context) => {
- const directory = await fs.mkdtemp(path.join(os.tmpdir(), "canvas-agent-history-"));
- context.after(() => fs.rm(directory, { recursive: true, force: true }));
- const file = path.join(directory, "history-target");
- const history = new CodexEventHistory(file);
- assert.deepEqual(await history.readThread("thread-1"), { items: [], turns: [] });
- await fs.mkdir(file);
- await assert.rejects(() => history.record({ threadId: "thread-1", turnId: "turn-1", itemId: "command-1", item: { id: "command-1", type: "command_execution" } }));
- assert.deepEqual(await history.readThread("thread-1"), { items: [], turns: [] });
-});
-
-test("turn 错误优先于条目错误且只生成一张错误卡片", () => {
- const messages = threadMessages({
- id: "thread-1",
- turns: [{ id: "turn-1", status: "failed", error: { message: "turn error" }, items: [{ id: "error-1", type: "error", message: "item error" }] }],
- });
- const errors = messages.filter((item) => item.role === "error");
- assert.equal(errors.length, 1);
- assert.equal(errors[0].id, "thread-1:turn-1:synthetic:error");
- assert.equal(errors[0].text, "turn error");
-});
-
-test("失败条目的状态与错误详情在历史中保持完整", () => {
- const messages = threadMessages({
- id: "thread-1",
- turns: [{
- id: "turn-1",
- status: "completed",
- items: [
- { id: "command-1", type: "commandExecution", command: "exit 1", status: "completed", exitCode: 1, aggregatedOutput: "failed" },
- { id: "image-1", type: "imageGeneration", status: "completed", success: false },
- { id: "context-1", type: "contextCompaction", status: "failed", error: { message: "compact failed" } },
- { id: "collab-1", type: "collabToolCall", status: "completed", success: false },
- { id: "reasoning-1", type: "reasoning", status: "failed", summary: ["分析失败"] },
- ],
- }],
- });
- const byId = new Map(messages.map((item) => [item.itemId, item]));
- assert.equal((byId.get("command-1")?.detail as { status?: string }).status, "failed");
- assert.equal((byId.get("image-1")?.detail as { status?: string }).status, "failed");
- assert.equal(byId.get("context-1")?.text, "compact failed");
- assert.equal((byId.get("context-1")?.detail as { output?: string }).output, "compact failed");
- assert.equal((byId.get("collab-1")?.detail as { status?: string }).status, "failed");
- assert.equal((byId.get("synthetic:reasoning")?.detail as { status?: string }).status, "failed");
-});
-
-test("新版协作工具条目在标准历史和补充历史中保持同一张卡片", () => {
- const messages = threadMessages({
- id: "thread-1",
- turns: [{ id: "turn-1", status: "completed", items: [{ id: "collab-1", type: "collabAgentToolCall", status: "completed" }] }],
- }, [], { items: [{
- threadId: "thread-1",
- turnId: "turn-1",
- itemId: "collab-1",
- sequence: 1,
- item: { id: "collab-1", type: "collab_tool_call", status: "completed" },
- }], turns: [] });
-
- assert.equal(messages.filter((item) => item.itemId === "collab-1").length, 1);
- assert.equal(messages[0].title, "协作处理");
-});
-
-test("尚未完成的 turn 只由实时事件展示,不进入历史快照", () => {
- const messages = threadMessages({
- id: "thread-1",
- turns: [{ id: "turn-1", status: "inProgress", items: [{ id: "reasoning-1", type: "reasoning", status: "pending", summary: ["正在分析"] }] }],
- });
- assert.deepEqual(messages, []);
-});
-
-test("缺少稳定 item id 的历史条目不会生成无法对齐的临时消息", () => {
- const messages = threadMessages({ id: "thread-1", turns: [{ id: "turn-1", status: "completed", items: [{ type: "agentMessage", text: "回答" }] }] });
- assert.equal(messages.length, 0);
-});
-
-test("通用工具只由标题和结构化状态表达完成结果", () => {
- const messages = threadMessages({
- id: "thread-1",
- turns: [{ id: "turn-1", status: "completed", items: [{ id: "tool-1", type: "mcpToolCall", tool: "list_mcp_resources", status: "completed" }] }],
- });
- assert.equal(messages.length, 1);
- assert.equal(messages[0].title, "调用工具:list_mcp_resources");
- assert.equal(messages[0].text, "");
- assert.equal((messages[0].detail as { status?: string }).status, "completed");
-});
-
-test("命令完成条目缺少 command 时仍保留历史卡片", () => {
- const messages = threadMessages({
- id: "thread-1",
- turns: [{
- id: "turn-1",
- status: "completed",
- items: [{ id: "command-1", type: "commandExecution", status: "completed", exitCode: 0, aggregatedOutput: "done" }],
- }],
- });
- assert.equal(messages.length, 1);
- assert.equal(messages[0].itemId, "command-1");
- assert.equal(messages[0].title, "执行命令");
- assert.equal(messages[0].text, "命令已完成");
- assert.equal((messages[0].detail as { status?: string }).status, "completed");
-});
diff --git a/canvas-agent/src/agent/codex-history.ts b/canvas-agent/src/agent/codex-history.ts
deleted file mode 100644
index 7e5491d..0000000
--- a/canvas-agent/src/agent/codex-history.ts
+++ /dev/null
@@ -1,740 +0,0 @@
-import { field } from "../utils/value.js";
-import type { CodexPlanUpdate } from "./codex-protocol.js";
-
-type AgentHistoryMessage = { id: string; itemId: string; threadId: string; turnId: string; role: "user" | "assistant" | "tool" | "error"; title?: string; text: string; detail?: unknown; activityItems?: Record };
-export type CodexSupplementalHistoryItem = { threadId: string; turnId: string; itemId: string; sequence?: number; item: Record };
-export type CodexSupplementalHistoryTurn = { threadId: string; turnId: string; turn: Record };
-export type CodexSupplementalHistory = { items: CodexSupplementalHistoryItem[]; turns: CodexSupplementalHistoryTurn[] };
-
-const emptySupplementalHistory: CodexSupplementalHistory = { items: [], turns: [] };
-
-/** 将 Codex 线程转换为列表展示所需的摘要。 */
-export function summarizeCodexThread(thread: unknown) {
- const status = field(thread, "status");
- return {
- id: String(field(thread, "id") || ""),
- sessionId: String(field(thread, "sessionId") || ""),
- preview: displayUserText(String(field(thread, "preview") || "")),
- name: stringOrNull(field(thread, "name")),
- cwd: String(field(thread, "cwd") || ""),
- status: typeof status === "string" ? status : String(field(status, "type") || ""),
- source: field(thread, "source"),
- threadSource: field(thread, "threadSource"),
- createdAt: Number(field(thread, "createdAt") || 0),
- updatedAt: Number(field(thread, "updatedAt") || 0),
- };
-}
-
-/** 返回已经终结、可由持久化历史作为权威内容的 turn。 */
-export function settledTurnIds(thread: unknown, supplementalHistory: CodexSupplementalHistory = emptySupplementalHistory) {
- return mergeHistoryTurns(thread, supplementalHistory).flatMap((turn) => {
- const id = String(field(turn, "id") || "");
- return id && isSettledTurn(turn) ? [id] : [];
- });
-}
-
-/** 将 Codex turn items 转换为网页聊天历史。 */
-export function threadMessages(thread: unknown, planUpdates: CodexPlanUpdate[] = [], supplementalHistory: CodexSupplementalHistory = emptySupplementalHistory): AgentHistoryMessage[] {
- const threadId = String(field(thread, "id") || "");
- if (!threadId) return [];
- const turns = mergeHistoryTurns(thread, supplementalHistory);
- const plansByTurn = new Map(planUpdates.map((item) => [item.turnId, item]));
- const supplementalByTurn = new Map();
- supplementalHistory.items.filter((item) => item.threadId === threadId).forEach((item) => supplementalByTurn.set(item.turnId, [...(supplementalByTurn.get(item.turnId) || []), item]));
- const messages: AgentHistoryMessage[] = [];
- turns.forEach((turn) => {
- const turnId = String(field(turn, "id") || "");
- if (!turnId || !isSettledTurn(turn)) return;
- const turnError = String(field(field(turn, "error"), "message") || "").trim();
- const items = mergeHistoryItems(arrayValue(field(turn, "items")), supplementalByTurn.get(turnId) || []);
- const push = (message: Omit) => messages.push({ ...message, id: historyMessageId(threadId, turnId, message.id), itemId: message.id, threadId, turnId });
- const planMessage = structuredPlanMessage(plansByTurn.get(turnId) || { threadId, turnId, explanation: stringOrNull(field(turn, "explanation")), plan: arrayValue(field(turn, "plan")) as CodexPlanUpdate["plan"], turnStatus: String(field(turn, "status") || "") }, threadId);
- const reasoningItems = items.flatMap((item) => {
- if (field(item, "type") !== "reasoning") return [];
- const id = String(field(item, "id") || "");
- const text = readableText(field(item, "summary"));
- return id && text ? [{ id, text, status: itemStatus(item, itemErrorMessage(item)) }] : [];
- });
- const reasoningText = reasoningItems.map((item) => item.text).join("\n\n");
- const reasoningStatus = aggregateItemStatus(reasoningItems.map((item) => item.status));
- const itemErrors = items.flatMap((item) => field(item, "type") === "error" ? [readableText(field(item, "message"))] : []).filter(Boolean);
- const itemError = itemErrors[itemErrors.length - 1] || "";
- let userAdded = false;
- let planAdded = false;
- let reasoningAdded = false;
- const explicitUserText = displayUserText(String(field(turn, "messageText") || ""));
- const fallbackUserText = explicitUserText || displayUserText(String(field(turn, "input") || ""));
- if (fallbackUserText && (explicitUserText || !items.some((item) => field(item, "type") === "userMessage"))) {
- push({ id: "synthetic:user", role: "user", text: fallbackUserText });
- userAdded = true;
- if (planMessage) {
- messages.push(planMessage);
- planAdded = true;
- }
- }
- items.forEach((item) => {
- const type = String(field(item, "type") || "");
- const id = String(field(item, "id") || "");
- if (!id) return;
- if (type === "userMessage") {
- const text = displayUserText(userInputText(field(item, "content")));
- if (text && !userAdded) {
- push({ id: "synthetic:user", role: "user", text });
- userAdded = true;
- }
- if (planMessage && !planAdded) {
- messages.push(planMessage);
- planAdded = true;
- }
- }
- if (type === "agentMessage") {
- const text = String(field(item, "text") || "").trim();
- if (text) push({ id, role: "assistant", title: "Codex", text });
- }
- if (type === "mcpToolCall") {
- const tool = String(field(item, "tool") || "工具调用");
- const error = String(field(field(item, "error"), "message") || "");
- const input = toolArguments(field(item, "arguments"));
- push({ id, role: "tool", title: toolName(tool), text: error || toolHistorySummary(tool, item, input), detail: toolHistoryDetail(tool, item, input, error) });
- }
- if (type === "commandExecution") {
- const command = String(field(item, "command") || "").trim();
- const detail = commandDetail(item);
- push({ id, role: "tool", title: "执行命令", text: command || (detail.status === "failed" ? "命令执行失败" : "命令已完成"), detail });
- }
- if (type === "fileChange") {
- const changes = arrayValue(field(item, "changes"));
- const error = itemErrorMessage(item);
- push({ id, role: "tool", title: "修改文件", text: error || fileChangeSummary(changes), detail: { kind: "file", status: itemStatus(item, error), files: changes.map((change) => ({ path: String(field(change, "path") || "未知文件"), action: changeKind(field(change, "kind")) })), ...(error ? { output: error } : {}) } });
- }
- if (type === "reasoning" && reasoningText && !reasoningAdded) {
- const sourceItemIds = reasoningItems.map((item) => item.id);
- push({ id: "synthetic:reasoning", role: "tool", title: "思考摘要", text: reasoningText, activityItems: Object.fromEntries(reasoningItems.map((item) => [item.id, item.text])), detail: { kind: "reasoning", status: reasoningStatus, sourceItemIds } });
- reasoningAdded = true;
- }
- if (type === "plan") {
- const text = String(field(item, "text") || "").trim();
- const error = itemErrorMessage(item);
- if (text || error) push({ id, role: "tool", title: "执行计划", text: error || text, detail: { kind: "plan", status: itemStatus(item, error), ...(error ? { output: error } : {}) } });
- }
- if (type === "webSearch") {
- const error = itemErrorMessage(item);
- push({ id, role: "tool", title: "搜索资料", text: error || webSearchSummary(item), detail: { kind: "search", status: itemStatus(item, error), rows: webSearchRows(item), ...(error ? { output: error } : {}) } });
- }
- if (type === "imageView") {
- const error = itemErrorMessage(item);
- push({ id, role: "tool", title: "查看图片", text: error || String(field(item, "path") || "已查看图片"), detail: { kind: "image", status: itemStatus(item, error), ...(error ? { output: error } : {}) } });
- }
- if (type === "imageGeneration") {
- const error = String(field(field(item, "error"), "message") || "");
- const normalizedStatus = itemStatus(item, error);
- push({ id, role: "tool", title: "内置生图", text: error || (normalizedStatus === "failed" ? "图片生成失败" : "图片生成完成"), detail: { kind: "image", status: normalizedStatus, savedPath: field(item, "savedPath"), ...(error ? { output: error } : {}) } });
- }
- if (type === "contextCompaction") {
- const error = itemErrorMessage(item);
- push({ id, role: "tool", title: "整理上下文", text: error || "已整理当前对话,继续处理任务", detail: { kind: "context", status: itemStatus(item, error), ...(error ? { output: error } : {}) } });
- }
- if (type === "dynamicToolCall") {
- const tool = String(field(item, "tool") || "");
- const title = toolName(tool);
- const error = itemErrorMessage(item);
- push({ id, role: "tool", title, text: error || readableText(field(item, "contentItems")), detail: toolHistoryDetail(tool, item, toolArguments(field(item, "arguments")), error) });
- }
- if (type === "collabToolCall" || type === "collabAgentToolCall") {
- const error = String(field(field(item, "error"), "message") || "");
- const normalizedStatus = itemStatus(item, error);
- push({ id, role: "tool", title: "协作处理", text: error || (normalizedStatus === "failed" ? "协作任务失败" : "已完成协作任务"), detail: { kind: "tool", status: normalizedStatus, ...(error ? { output: error } : {}) } });
- }
- });
- if (planMessage && !planAdded) messages.push(planMessage);
- if (itemError || turnError) {
- const error = userFacingCodexError(turnError || itemError);
- push({ id: "synthetic:error", role: "error", title: error.title, text: error.text });
- }
- });
- return latestCompleteTurns(messages.filter((item) => item.text || item.role === "tool"), 120);
-}
-
-/** 合并标准 turn 与本地终态事件,标准历史已有字段保持优先。 */
-function mergeHistoryTurns(thread: unknown, supplementalHistory: CodexSupplementalHistory) {
- const threadId = String(field(thread, "id") || "");
- const standardTurns = arrayValue(field(thread, "turns"));
- const supplementalTurns = supplementalHistory.turns.filter((entry) => entry.threadId === threadId && entry.turnId);
- const supplementalById = new Map(supplementalTurns.map((entry) => [entry.turnId, entry.turn]));
- const standardIds = new Set(standardTurns.map((turn) => String(field(turn, "id") || "")).filter(Boolean));
- return [
- ...standardTurns.map((turn) => {
- const turnId = String(field(turn, "id") || "");
- const supplemental = supplementalById.get(turnId);
- return supplemental ? mergeHistoryTurn(turn, supplemental) : turn;
- }),
- ...supplementalTurns.filter((entry) => !standardIds.has(entry.turnId)).map((entry) => entry.turn),
- ];
-}
-
-/** 终态不可被标准历史中尚未物化完成的临时状态降级。 */
-function mergeHistoryTurn(standard: unknown, supplemental: Record) {
- const merged = mergeHistoryItem(standard, supplemental);
- const standardStatus = String(field(standard, "status") || "");
- const supplementalStatus = String(field(supplemental, "status") || "");
- if (!isSettledStatus(standardStatus) && isSettledStatus(supplementalStatus)) merged.status = supplementalStatus;
- return merged;
-}
-
-/** 以 Codex 标准历史为准,只补入同一 turn 中被持久线程投影省略的实时条目。 */
-function mergeHistoryItems(items: unknown[], supplementalItems: CodexSupplementalHistoryItem[]) {
- const supplemental = supplementalItems
- .filter((entry) => entry.itemId)
- .map((entry, index) => ({ ...entry, item: normalizeSupplementalItem(entry.item), fallbackIndex: index }))
- .sort(compareSupplementalEntries);
- items = removeCorruptedHistoryDuplicates(items, supplemental);
- items = reconcileHistoryItemIds(items, supplemental);
- const standardIds = new Set(items.map((item) => String(field(item, "id") || "")).filter(Boolean));
- const supplementalById = new Map(supplemental.map((entry) => [entry.itemId, entry]));
- const mergedStandard = items.map((item) => {
- const id = String(field(item, "id") || "");
- const supplementalEntry = id ? supplementalById.get(id) : undefined;
- return supplementalEntry ? mergeHistoryItem(item, supplementalEntry.item) : item;
- });
- const sequenced = supplemental.filter((entry) => entry.sequence !== undefined);
- let merged = sequenced.length ? mergeSequencedItems(mergedStandard, sequenced) : mergedStandard;
- supplemental.forEach((entry, index) => {
- if (standardIds.has(entry.itemId) || entry.sequence !== undefined) return;
- const nextStandardId = supplemental.slice(index + 1).find((candidate) => standardIds.has(candidate.itemId))?.itemId;
- const insertAt = nextStandardId ? merged.findIndex((candidate) => String(field(candidate, "id") || "") === nextStandardId) : -1;
- if (insertAt >= 0) merged.splice(insertAt, 0, entry.item);
- else merged.push(entry.item);
- });
- return merged;
-}
-
-/** thread/read 会重写部分 item id;按类型、内容和出现顺序恢复实时事件的稳定身份。 */
-function reconcileHistoryItemIds(items: unknown[], supplementalItems: CodexSupplementalHistoryItem[]) {
- const supplementalById = new Map(supplementalItems.map((entry) => [entry.itemId, entry]));
- const reservedIds = new Set(items.map((item) => String(field(item, "id") || "")).filter((id) => supplementalById.has(id)));
- const candidates = new Map();
- supplementalItems.forEach((entry) => {
- if (reservedIds.has(entry.itemId)) return;
- const identity = historyItemIdentity(entry.item);
- if (identity) candidates.set(identity, [...(candidates.get(identity) || []), entry]);
- });
- const replacements = new Map();
- const usedSupplementalIds = new Set(reservedIds);
- items.forEach((item, index) => {
- const id = String(field(item, "id") || "");
- if (!id || supplementalById.has(id)) return;
- const identity = historyItemIdentity(item);
- const candidate = identity ? candidates.get(identity)?.shift() : undefined;
- if (!candidate || usedSupplementalIds.has(candidate.itemId)) return;
- replacements.set(index, candidate.itemId);
- usedSupplementalIds.add(candidate.itemId);
- });
- const remainingStandard = new Map();
- items.forEach((item, index) => {
- const id = String(field(item, "id") || "");
- if (!id || supplementalById.has(id) || replacements.has(index)) return;
- const type = historyItemType(item);
- if (type) remainingStandard.set(type, [...(remainingStandard.get(type) || []), index]);
- });
- const remainingSupplemental = new Map();
- supplementalItems.forEach((entry) => {
- if (usedSupplementalIds.has(entry.itemId)) return;
- const type = historyItemType(entry.item);
- if (type) remainingSupplemental.set(type, [...(remainingSupplemental.get(type) || []), entry]);
- });
- remainingStandard.forEach((standardIndexes, type) => {
- const supplemental = remainingSupplemental.get(type) || [];
- if (standardIndexes.length === supplemental.length) {
- standardIndexes.forEach((index, position) => {
- const entry = supplemental[position];
- replacements.set(index, entry.itemId);
- usedSupplementalIds.add(entry.itemId);
- });
- return;
- }
- if (supplemental.length !== 1) return;
- const corrupted = standardIndexes.filter((index) => hasReplacementCharacter(items[index]));
- if (corrupted.length === 1) {
- replacements.set(corrupted[0], supplemental[0].itemId);
- usedSupplementalIds.add(supplemental[0].itemId);
- }
- });
- return items.map((item, index) => {
- const itemId = replacements.get(index);
- if (!itemId || !item || typeof item !== "object" || Array.isArray(item)) return item;
- return { ...(item as Record), id: itemId };
- });
-}
-
-function historyItemType(item: unknown) {
- const type = String(field(normalizeSupplementalItem(item && typeof item === "object" && !Array.isArray(item) ? item as Record : {}), "type") || "");
- return type;
-}
-
-function hasReplacementCharacter(item: unknown) {
- const value = item && typeof item === "object" && !Array.isArray(item) ? item as Record : {};
- return [value.text, value.summary, value.message, value.aggregatedOutput].some((fieldValue) => readableText(fieldValue).includes("\uFFFD"));
-}
-
-/** thread/read 偶尔同时返回损坏临时条目和已物化条目,只保留补充历史已确认的稳定副本。 */
-function removeCorruptedHistoryDuplicates(items: unknown[], supplementalItems: CodexSupplementalHistoryItem[]) {
- const standardIds = new Set(items.map((item) => String(field(item, "id") || "")).filter(Boolean));
- const stableItems = supplementalItems.filter((entry) => standardIds.has(entry.itemId) && !hasReplacementCharacter(entry.item));
- return items.filter((item) => {
- if (!hasReplacementCharacter(item)) return true;
- const identity = comparableHistoryText(item);
- if (identity.length < 8) return true;
- return !stableItems.some((entry) => historyItemType(entry.item) === historyItemType(item) && comparableHistoryText(entry.item) === identity);
- });
-}
-
-function comparableHistoryText(item: unknown) {
- const value = item && typeof item === "object" && !Array.isArray(item) ? item as Record : {};
- const type = historyItemType(value);
- const text = type === "agentMessage" ? value.text : type === "reasoning" ? value.summary : type === "plan" ? value.text : "";
- return readableText(text).normalize("NFKC").toLocaleLowerCase().replace(/[\uFFFD\p{P}\p{S}\p{Z}]+/gu, "");
-}
-
-/** 只使用消息本身的稳定语义字段,不使用状态、结果或临时投影 id。 */
-function historyItemIdentity(item: unknown) {
- const normalized = normalizeSupplementalItem(item && typeof item === "object" && !Array.isArray(item) ? item as Record : {});
- const type = String(field(normalized, "type") || "");
- let value: unknown;
- if (type === "agentMessage") value = String(field(normalized, "text") || "").trim();
- else if (type === "reasoning") value = readableText(field(normalized, "summary"));
- else if (type === "plan") value = String(field(normalized, "text") || "").trim();
- else if (type === "mcpToolCall" || type === "dynamicToolCall") value = [field(normalized, "tool"), toolArguments(field(normalized, "arguments"))];
- else if (type === "commandExecution") value = String(field(normalized, "command") || "").trim();
- else if (type === "fileChange") value = arrayValue(field(normalized, "changes")).map((change) => [field(change, "path"), field(change, "kind")]);
- else if (type === "collabToolCall" || type === "collabAgentToolCall") value = [field(normalized, "tool"), field(normalized, "prompt"), field(normalized, "receiverThreadId"), field(normalized, "receiverAgentId")];
- else if (type === "webSearch") value = [field(normalized, "query"), field(field(normalized, "action"), "type"), field(field(normalized, "action"), "query"), field(field(normalized, "action"), "url"), field(field(normalized, "action"), "pattern")];
- else if (type === "imageView") value = field(normalized, "path");
- else if (type === "imageGeneration") value = field(normalized, "prompt") || field(normalized, "savedPath");
- else if (type === "contextCompaction") value = true;
- else return "";
- return `${type}\0${JSON.stringify(stableHistoryValue(value))}`;
-}
-
-function stableHistoryValue(value: unknown): unknown {
- if (Array.isArray(value)) return value.map(stableHistoryValue);
- if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => [key, stableHistoryValue(item)]));
- return value;
-}
-
-function mergeSequencedItems(standardItems: unknown[], supplementalItems: CodexSupplementalHistoryItem[]) {
- const byId = new Map(supplementalItems.map((entry) => [entry.itemId, entry]));
- const emitted = new Set();
- const merged: unknown[] = [];
- standardItems.forEach((item) => {
- const id = String(field(item, "id") || "");
- const sequence = id ? byId.get(id)?.sequence : undefined;
- if (sequence !== undefined) {
- supplementalItems.forEach((entry) => {
- if (entry.sequence !== undefined && entry.sequence < sequence && !emitted.has(entry.itemId)) {
- merged.push(findStandardItem(standardItems, entry.itemId) || entry.item);
- emitted.add(entry.itemId);
- }
- });
- if (emitted.has(id)) return;
- if (id) emitted.add(id);
- merged.push(item);
- return;
- }
- merged.push(item);
- });
- supplementalItems.forEach((entry) => {
- if (!emitted.has(entry.itemId)) merged.push(entry.item);
- });
- return merged;
-}
-
-function findStandardItem(items: unknown[], itemId: string) {
- return items.find((item) => String(field(item, "id") || "") === itemId);
-}
-
-function compareSupplementalEntries(left: CodexSupplementalHistoryItem & { fallbackIndex: number }, right: CodexSupplementalHistoryItem & { fallbackIndex: number }) {
- if (left.sequence !== undefined && right.sequence !== undefined && left.sequence !== right.sequence) return left.sequence - right.sequence;
- if (left.sequence !== undefined) return -1;
- if (right.sequence !== undefined) return 1;
- return left.fallbackIndex - right.fallbackIndex;
-}
-
-/** 标准历史字段优先,补充事件只填充标准历史没有返回的字段。 */
-function mergeHistoryItem(standard: unknown, supplemental: Record) {
- if (!standard || typeof standard !== "object" || Array.isArray(standard)) return supplemental;
- const merged = { ...supplemental };
- Object.entries(standard as Record).forEach(([key, value]) => {
- const supplementalValue = merged[key];
- if (typeof value === "string" && typeof supplementalValue === "string" && value.includes("\uFFFD") && !supplementalValue.includes("\uFFFD")) return;
- if (value !== undefined) merged[key] = value;
- });
- return merged;
-}
-
-/** 将实时事件的 snake_case 类型还原为 thread/read 使用的 camelCase 类型。 */
-function normalizeSupplementalItem(item: Record) {
- const type = String(field(item, "type") || "");
- const normalizedType = supplementalItemTypes[type] || type;
- return normalizedType === type ? item : { ...item, type: normalizedType };
-}
-
-const supplementalItemTypes: Record = {
- agent_message: "agentMessage",
- mcp_tool_call: "mcpToolCall",
- command_execution: "commandExecution",
- file_change: "fileChange",
- dynamic_tool_call: "dynamicToolCall",
- collab_tool_call: "collabToolCall",
- web_search: "webSearch",
- image_view: "imageView",
- image_generation: "imageGeneration",
- context_compaction: "contextCompaction",
-};
-
-/** 只按完整 turn 截取最近历史,避免列表从某轮中间开始。 */
-function latestCompleteTurns(messages: AgentHistoryMessage[], limit: number) {
- const turns: AgentHistoryMessage[][] = [];
- messages.forEach((message) => {
- const current = turns.at(-1);
- if (!current || current[0].turnId !== message.turnId) turns.push([message]);
- else current.push(message);
- });
- const selected: AgentHistoryMessage[][] = [];
- let count = 0;
- for (let index = turns.length - 1; index >= 0; index -= 1) {
- const turn = turns[index];
- if (selected.length && count + turn.length > limit) break;
- selected.unshift(turn);
- count += turn.length;
- }
- return selected.flat();
-}
-
-function isSettledTurn(turn: unknown) {
- return isSettledStatus(String(field(turn, "status") || ""));
-}
-
-function isSettledStatus(status: string) {
- return ["completed", "failed", "interrupted", "cancelled", "canceled"].includes(status);
-}
-
-/** 将结构化任务计划转换为聊天进度卡片。 */
-function structuredPlanMessage(update: CodexPlanUpdate, threadId: string): AgentHistoryMessage | null {
- const tasks = arrayValue(update.plan).flatMap((item) => {
- const step = String(field(item, "step") || "").trim();
- return step ? [{ step, status: String(field(item, "status") || "pending") }] : [];
- });
- if (!tasks.length) return null;
- const completed = tasks.filter((item) => item.status === "completed").length;
- return {
- id: historyMessageId(threadId, update.turnId, "synthetic:plan"),
- itemId: "synthetic:plan",
- threadId,
- turnId: update.turnId,
- role: "tool",
- title: "任务进度",
- text: `已完成 ${completed}/${tasks.length} 项`,
- detail: { kind: "todo", status: planStatus(tasks, update.turnStatus), tasks, explanation: update.explanation || "" },
- };
-}
-
-/** 生成跨线程和 turn 唯一的聊天消息 ID。 */
-function historyMessageId(threadId: string, turnId: string, itemId: string) {
- return `${threadId}:${turnId}:${itemId}`;
-}
-
-/** 根据步骤和 turn 状态生成任务卡片状态。 */
-function planStatus(tasks: Array<{ status: string }>, turnStatus?: string) {
- if (turnStatus === "failed") return "failed";
- if (turnStatus === "interrupted") return "interrupted";
- if (tasks.every((item) => item.status === "completed")) return "completed";
- return turnStatus === "completed" ? "finished" : "inProgress";
-}
-
-/** 将常见 Codex 错误转换为普通用户可理解的提示。 */
-function userFacingCodexError(message: string) {
- if (/selected model is at capacity/i.test(message)) return { title: "模型暂时繁忙", text: "当前选择的模型请求量过大,暂时无法处理。请稍后重试,或切换其他模型后再试。" };
- return { title: "任务失败", text: message || "Codex 未能完成本次任务,请稍后重试。" };
-}
-
-/** 提取用户输入条目中的文本与附件占位信息。 */
-function userInputText(content: unknown) {
- const items = arrayValue(content);
- const text = items
- .map((item) => {
- const type = String(field(item, "type") || "");
- if (type === "text") return String(field(item, "text") || "");
- if (type === "image" || type === "localImage") return "图片附件";
- if (type === "mention") return `@${String(field(item, "name") || "文件")}`;
- return "";
- })
- .filter(Boolean)
- .join("\n");
- return text.trim();
-}
-
-/** 移除用户消息中由旧流程拼接的 Agent 前置提示词。 */
-function displayUserText(text: string) {
- const value = text.trim();
- const marker = "用户请求:";
- const index = value.lastIndexOf(marker);
- const prompt = index >= 0 ? value.slice(index + marker.length) : value;
- return prompt.split("\n\n本轮可用图片附件(顺序与图片输入一致):", 1)[0].trim();
-}
-
-/** 将未知值转换为数组。 */
-function arrayValue(value: unknown) {
- return Array.isArray(value) ? value : [];
-}
-
-/** 将非空字符串保留为字符串,否则返回 null。 */
-function stringOrNull(value: unknown) {
- return typeof value === "string" && value.trim() ? value : null;
-}
-
-/** 生成命令执行的用户可读详情。 */
-function commandDetail(item: unknown) {
- const error = String(field(field(item, "error"), "message") || "");
- const status = String(field(item, "status") || "completed");
- const exitCode = field(item, "exitCode");
- const failed = Boolean(error) || field(item, "success") === false || status === "failed" || status === "error" || (typeof exitCode === "number" && exitCode !== 0);
- const rows = [
- textRow("工作目录", field(item, "cwd")),
- textRow("退出状态", exitCode),
- durationRow(field(item, "durationMs")),
- ].filter(Boolean);
- return { kind: "command", status: failed ? "failed" : status, rows, output: error || String(field(item, "aggregatedOutput") || "").trim() };
-}
-
-/** 生成 MCP 工具的用户可读详情。 */
-function toolHistoryDetail(tool: string, item: unknown, input: unknown, error: string) {
- return { kind: "tool", status: itemStatus(item, error), rows: toolInputRows(tool, input), ...(error ? { output: error } : {}) };
-}
-
-function itemErrorMessage(item: unknown) {
- return String(field(field(item, "error"), "message") || "");
-}
-
-function itemStatus(item: unknown, error = "") {
- const status = String(field(item, "status") || "completed");
- return error || field(item, "success") === false || status === "failed" || status === "error" ? "failed" : status;
-}
-
-function aggregateItemStatus(statuses: string[]) {
- if (statuses.includes("failed")) return "failed";
- if (statuses.includes("interrupted")) return "interrupted";
- if (statuses.some((status) => ["inProgress", "in_progress", "running", "started", "pending"].includes(status))) return "inProgress";
- return "completed";
-}
-
-/** 生成 MCP 工具在对话中的结果摘要。 */
-function toolHistorySummary(tool: string, item: unknown, input: unknown) {
- const result = parseToolResult(field(item, "result"));
- if (tool === "site_navigate") return `已打开${routeName(String(field(input, "path") || "/"))}`;
- if (tool === "canvas_list_projects") return `共 ${numberValue(field(result, "total"))} 个画布`;
- if (tool === "canvas_get_state") {
- const nodes = arrayValue(field(result, "nodes"));
- const connections = arrayValue(field(result, "connections"));
- return Array.isArray(field(result, "nodes")) || Array.isArray(field(result, "connections")) ? canvasContentSummary(nodes, connections.length) : "已读取当前画布内容";
- }
- if (tool === "canvas_get_selection") return "已读取当前选中内容";
- if (tool === "prompts_search") return `找到 ${numberValue(field(result, "total"))} 条提示词`;
- if (tool === "assets_list") return `共 ${numberValue(field(result, "total"))} 个资产`;
- if (tool === "assets_add") return "已加入我的素材";
- if (tool === "generation_get_status") {
- const summary = field(result, "summary");
- return `共 ${numberValue(field(result, "total"))} 个任务,排队 ${numberValue(field(summary, "queued"))},运行中 ${numberValue(field(summary, "running"))},成功 ${numberValue(field(summary, "succeeded"))},失败 ${numberValue(field(summary, "failed"))}`;
- }
- if (tool === "workbench_image_generate" || tool === "workbench_video_generate") return String(field(result, "note") || "已在工作台执行");
- if (tool === "workbench_image_get_config" || tool === "workbench_video_get_config") return "已读取工作台配置";
- return "";
-}
-
-/** 按节点类型生成人类可读的画布内容概览。 */
-function canvasContentSummary(nodes: unknown[], connections: number) {
- const counts = nodes.reduce>((result, node) => {
- const type = String(field(node, "type") || "other");
- result[type] = (result[type] || 0) + 1;
- return result;
- }, {});
- const known = new Set(["text", "image", "config", "video", "audio", "group"]);
- const other = Object.entries(counts).reduce((total, [type, count]) => total + (known.has(type) ? 0 : count), 0);
- const parts = [
- counts.text ? `${counts.text} 个文本` : "",
- counts.image ? `${counts.image} 张图片` : "",
- counts.config ? `${counts.config} 个配置` : "",
- counts.video ? `${counts.video} 个视频` : "",
- counts.audio ? `${counts.audio} 个音频` : "",
- counts.group ? `${counts.group} 个分组` : "",
- other ? `${other} 个其他节点` : "",
- connections ? `${connections} 条连线` : "",
- ].filter(Boolean);
- return parts.length ? parts.join("、") : "当前画布为空";
-}
-
-/** 从 MCP 历史结果中还原工具返回的数据。 */
-function parseToolResult(result: unknown) {
- const content = field(result, "content");
- const text = arrayValue(content)
- .map((item) => field(item, "text"))
- .filter((item): item is string => typeof item === "string")
- .join("\n");
- try {
- return text ? JSON.parse(text) : result;
- } catch {
- return text || result;
- }
-}
-
-/** 提取工具参数中适合普通用户查看的信息。 */
-function toolInputRows(tool: string, input: unknown) {
- if (tool === "site_navigate") return [textRow("目标页面", routeName(String(field(input, "path") || "/")))].filter(Boolean);
- if (tool === "prompts_search") return [textRow("搜索内容", field(input, "query"))].filter(Boolean);
- if (tool === "canvas_create_text_node") return [textRow("文本内容", field(input, "text"))].filter(Boolean);
- if (tool === "canvas_apply_ops") return [textRow("操作内容", summarizeCanvasOps(arrayValue(field(input, "ops"))))].filter(Boolean);
- if (tool === "canvas_create_attachment_nodes") return [textRow("图片数量", arrayValue(field(input, "attachmentIds")).length)].filter(Boolean);
- return [];
-}
-
-function summarizeCanvasOps(ops: unknown[]) {
- const counts = ops.reduce>((result, op) => {
- const type = String(field(op, "type") || "");
- if (type) result[type] = (result[type] || 0) + 1;
- return result;
- }, {});
- return Object.entries(counts).map(([type, count]) => `${canvasOpLabel(type)} ${count}`).join(",");
-}
-
-function canvasOpLabel(type: string) {
- if (type === "add_node") return "新增节点";
- if (type === "update_node") return "更新节点";
- if (type === "delete_node") return "删除节点";
- if (type === "delete_connections") return "删除连线";
- if (type === "connect_nodes") return "连接";
- if (type === "set_viewport") return "调整视图";
- if (type === "select_nodes") return "选择节点";
- if (type === "run_generation") return "触发生成";
- return type;
-}
-
-/** 生成人类可读的文件变更摘要。 */
-function fileChangeSummary(changes: unknown[]) {
- if (!changes.length) return "已完成文件修改";
- const names = changes.slice(0, 3).map((change) => String(field(change, "path") || "未知文件"));
- if (changes.length === 1) return `已${changeKind(field(changes[0], "kind"))} ${names[0]}`;
- return `已修改 ${changes.length} 个文件:${names.join("、")}${changes.length > names.length ? " 等" : ""}`;
-}
-
-/** 生成网页搜索摘要。 */
-function webSearchSummary(item: unknown) {
- const action = field(item, "action");
- const type = String(field(action, "type") || "");
- if (type === "openPage") return `打开网页:${String(field(action, "url") || "")}`;
- if (type === "findInPage") return `在网页中查找“${String(field(action, "pattern") || "内容")}”`;
- return `搜索:${String(field(item, "query") || field(action, "query") || "相关资料")}`;
-}
-
-/** 生成网页搜索详情行。 */
-function webSearchRows(item: unknown) {
- const action = field(item, "action");
- return [textRow("关键词", field(item, "query") || field(action, "query")), textRow("网页", field(action, "url"))].filter(Boolean);
-}
-
-/** 从 reasoning 结构中提取可读文本。 */
-function readableText(value: unknown): string {
- if (typeof value === "string") return value.trim();
- if (Array.isArray(value)) return value.map(readableText).filter(Boolean).join("\n");
- if (!value || typeof value !== "object") return "";
- return readableText(field(value, "text"));
-}
-
-/** 将历史工具参数解析为对象。 */
-function toolArguments(value: unknown) {
- if (typeof value !== "string") return value;
- try {
- return JSON.parse(value) as unknown;
- } catch {
- return {};
- }
-}
-
-/** 创建非空详情行。 */
-function textRow(label: string, value: unknown) {
- return value === undefined || value === null || value === "" ? null : { label, value: String(value) };
-}
-
-/** 创建命令耗时详情行。 */
-function durationRow(value: unknown) {
- const duration = Number(value || 0);
- return duration > 0 ? { label: "耗时", value: `${(duration / 1000).toFixed(1)} 秒` } : null;
-}
-
-/** 将未知数值转换为有限数字。 */
-function numberValue(value: unknown) {
- const number = Number(value || 0);
- return Number.isFinite(number) ? number : 0;
-}
-
-/** 将文件变更类型转换为中文。 */
-function changeKind(value: unknown) {
- if (value === "add") return "新增";
- if (value === "delete") return "删除";
- return "修改";
-}
-
-/** 将站点路由转换为中文页面名称。 */
-function routeName(path: string) {
- if (path === "/") return "首页";
- if (path === "/canvas") return "画布页面";
- if (path.startsWith("/canvas/")) return "指定画布";
- if (path.startsWith("/image")) return "生图工作台";
- if (path.startsWith("/video")) return "视频工作台";
- if (path.startsWith("/prompts")) return "提示词中心";
- if (path.startsWith("/assets")) return "我的素材";
- if (path.startsWith("/config")) return "配置页面";
- return path;
-}
-
-/** 将 MCP 工具名称转换为聊天记录中的中文标题。 */
-function toolName(name: string) {
- if (name === "imagegen" || name.endsWith("__imagegen")) return "生成图片";
- if (name === "view_image" || name.endsWith("__view_image")) return "查看图片";
- if (name === "exec" || name === "exec_command" || name.endsWith("__exec_command")) return "执行命令";
- if (name === "apply_patch" || name.endsWith("__apply_patch")) return "修改文件";
- if (name === "web__run" || name.endsWith("__web__run")) return "搜索资料";
- if (name === "site_navigate") return "打开页面";
- if (name === "canvas_list_projects") return "画布列表";
- if (name === "canvas_apply_ops") return "画布操作";
- if (name === "canvas_get_state") return "读取画布";
- if (name === "canvas_get_selection") return "读取选区";
- if (name === "canvas_export_snapshot") return "导出快照";
- if (name === "canvas_create_node") return "创建节点";
- if (name === "canvas_create_attachment_nodes") return "添加附件图片";
- if (name === "canvas_create_text_node") return "创建文本";
- if (name === "canvas_create_text_nodes") return "批量创建文本";
- if (name === "canvas_create_config_node") return "创建生成配置";
- if (name === "canvas_create_image_prompt_flow") return "创建生图流程";
- if (name === "canvas_create_generation_flow") return "创建生成流程";
- if (name === "canvas_generate_text") return "生成文本";
- if (name === "canvas_generate_image") return "生成图片";
- if (name === "canvas_generate_video") return "生成视频";
- if (name === "canvas_generate_audio") return "生成音频";
- if (name === "canvas_update_node") return "更新节点";
- if (name === "canvas_update_node_text") return "更新文本";
- if (name === "canvas_move_nodes") return "移动节点";
- if (name === "canvas_resize_node") return "调整节点尺寸";
- if (name === "canvas_delete_nodes") return "删除节点";
- if (name === "canvas_connect_nodes") return "连接节点";
- if (name === "canvas_select_nodes") return "选择节点";
- if (name === "canvas_set_viewport") return "调整视口";
- if (name === "canvas_run_generation") return "触发生成";
- if (name === "workbench_image_get_config") return "生图配置";
- if (name === "workbench_image_generate") return "生图工作台生成";
- if (name === "workbench_video_get_config") return "视频配置";
- if (name === "workbench_video_generate") return "视频创作台生成";
- if (name === "prompts_search") return "搜索提示词";
- if (name === "assets_list") return "资产列表";
- if (name === "assets_add") return "添加资产";
- if (name === "generation_get_status") return "生成任务状态";
- return name ? `调用工具:${name}` : "工具操作";
-}
diff --git a/canvas-agent/src/agent/codex-protocol.ts b/canvas-agent/src/agent/codex-protocol.ts
deleted file mode 100644
index 3cb4055..0000000
--- a/canvas-agent/src/agent/codex-protocol.ts
+++ /dev/null
@@ -1,158 +0,0 @@
-import type { JsonRecord } from "../utils/value.js";
-
-export type CodexThread = JsonRecord & { id: string; cwd: string; turns?: CodexTurn[] };
-export type CodexTurn = JsonRecord & { id: string; error?: CodexTurnError | null; durationMs?: number | null };
-export type CodexTurnError = JsonRecord & { message: string };
-export type CodexItem = JsonRecord & { id: string; type: string; text?: string };
-export type CodexPlanStep = { step: string; status: "pending" | "inProgress" | "completed" };
-export type CodexPlanUpdate = { threadId: string; turnId: string; explanation?: string | null; plan: CodexPlanStep[]; turnStatus?: string };
-export type CodexMcpStartupStatus = { threadId: string | null; name: string; status: "starting" | "ready" | "failed" | "cancelled"; error: string | null; failureReason: "reauthenticationRequired" | null };
-export type CodexMcpServerStatus = { name: string; authStatus: "unsupported" | "notLoggedIn" | "bearerToken" | "oAuth" } & JsonRecord;
-export type CodexReasoningEffort = "minimal" | "low" | "medium" | "high" | "xhigh" | "max" | "ultra";
-export type CodexModel = JsonRecord & {
- id: string;
- model: string;
- displayName: string;
- defaultReasoningEffort: CodexReasoningEffort;
- supportedReasoningEfforts: Array<{ reasoningEffort: CodexReasoningEffort; description?: string }>;
- isDefault?: boolean;
-};
-
-export type CodexSkillScope = "user" | "repo" | "system" | "admin";
-export type CodexSkillInterface = JsonRecord & {
- displayName?: string | null;
- shortDescription?: string | null;
- iconSmall?: string | null;
- iconLarge?: string | null;
- iconSmallUrl?: string | null;
- iconLargeUrl?: string | null;
- brandColor?: string | null;
- defaultPrompt?: string | null;
-};
-export type CodexSkillMetadata = JsonRecord & {
- name: string;
- description: string;
- shortDescription?: string | null;
- interface?: CodexSkillInterface | null;
- dependencies?: JsonRecord | null;
- path: string;
- scope: CodexSkillScope;
- enabled: boolean;
-};
-export type CodexSkillError = { path: string; message: string };
-export type CodexSkillsListEntry = { cwd: string; skills: CodexSkillMetadata[]; errors: CodexSkillError[] };
-export type CodexSkillSelector = { name: string; path: string };
-
-export type CodexTurnInput =
- | { type: "text"; text: string; text_elements: [] }
- | { type: "localImage"; path: string }
- | ({ type: "skill" } & CodexSkillSelector);
-
-type ThreadOptions = {
- approvalPolicy: "never" | "on-request";
- sandbox: "read-only" | "workspace-write" | "danger-full-access";
- config: JsonRecord;
- cwd?: string;
- developerInstructions?: string;
- ephemeral?: boolean;
-};
-
-type CodexRequestSpec = {
- initialize: {
- params: {
- clientInfo: { name: string; title: string; version: string };
- capabilities: { experimentalApi: boolean; requestAttestation: boolean };
- };
- result: JsonRecord;
- };
- "thread/start": {
- params: ThreadOptions & { threadSource: "user" };
- result: { thread: CodexThread };
- };
- "thread/resume": {
- params: ThreadOptions & { threadId: string };
- result: { thread: CodexThread };
- };
- "thread/fork": {
- params: ThreadOptions & { threadId: string; threadSource: "user" };
- result: { thread: CodexThread };
- };
- "thread/list": {
- params: {
- limit: number;
- sortKey: "updated_at";
- sortDirection: "desc";
- sourceKinds: Array<"cli" | "vscode" | "appServer" | "exec">;
- cwd: string;
- searchTerm?: string;
- };
- result: { data: CodexThread[]; nextCursor: string | null; backwardsCursor: string | null };
- };
- "thread/read": {
- params: { threadId: string; includeTurns: boolean };
- result: { thread: CodexThread };
- };
- "thread/archive": {
- params: { threadId: string };
- result: Record;
- };
- "thread/unsubscribe": {
- params: { threadId: string };
- result: { status: "notLoaded" | "notSubscribed" | "unsubscribed" };
- };
- "model/list": {
- params: { limit: number; includeHidden: boolean };
- result: { data: CodexModel[]; nextCursor: string | null };
- };
- "mcpServerStatus/list": {
- params: { cursor?: string | null; limit?: number | null; detail?: "full" | "toolsAndAuthOnly" | null; threadId?: string | null };
- result: { data: CodexMcpServerStatus[]; nextCursor: string | null };
- };
- "skills/list": {
- params: { cwds: string[]; forceReload?: boolean };
- result: { data: CodexSkillsListEntry[] };
- };
- "skills/config/write": {
- params: { path?: string | null; name?: string | null; enabled: boolean };
- result: { effectiveEnabled: boolean };
- };
- "turn/start": {
- params: { threadId: string; input: CodexTurnInput[]; approvalPolicy: "never" | "on-request"; sandboxPolicy: { type: "readOnly"; networkAccess: boolean } | { type: "workspaceWrite"; networkAccess: boolean } | { type: "dangerFullAccess" }; model?: string; effort?: CodexReasoningEffort; outputSchema?: JsonRecord };
- result: { turn: CodexTurn };
- };
- "turn/interrupt": {
- params: { threadId: string; turnId: string };
- result: Record;
- };
-};
-
-export type CodexRequestMethod = keyof CodexRequestSpec;
-export type CodexRequestParams = CodexRequestSpec[Method]["params"];
-export type CodexRequestResult = CodexRequestSpec[Method]["result"];
-
-type TokenUsageBreakdown = {
- inputTokens: number;
- cachedInputTokens: number;
- outputTokens: number;
- reasoningOutputTokens: number;
-};
-
-type CodexNotificationSpec = {
- "thread/started": { thread: CodexThread };
- "turn/started": { threadId?: string; turn: CodexTurn };
- "turn/completed": { threadId?: string; turn: CodexTurn };
- "turn/plan/updated": { threadId?: string; turnId: string; explanation?: string | null; plan: CodexPlanStep[] };
- "item/started": { threadId: string; turnId: string; item: CodexItem };
- "item/completed": { threadId: string; turnId: string; item: CodexItem };
- "item/agentMessage/delta": { threadId: string; turnId: string; itemId: string; delta: string };
- "item/plan/delta": { threadId: string; turnId: string; itemId: string; delta: string };
- "item/reasoning/summaryTextDelta": { threadId: string; turnId: string; itemId: string; delta: string; summaryIndex: number };
- "item/commandExecution/outputDelta": { threadId: string; turnId: string; itemId: string; delta: string };
- "thread/tokenUsage/updated": { threadId: string; turnId: string; tokenUsage: { last: TokenUsageBreakdown } };
- "mcpServer/startupStatus/updated": CodexMcpStartupStatus;
- "skills/changed": Record;
- error: { threadId: string; turnId: string; error: CodexTurnError; willRetry: boolean };
-};
-
-export type CodexNotificationMethod = keyof CodexNotificationSpec;
-export type CodexNotificationParams = CodexNotificationSpec[Method];
diff --git a/canvas-agent/src/agent/codex.ts b/canvas-agent/src/agent/codex.ts
deleted file mode 100644
index d57c4e4..0000000
--- a/canvas-agent/src/agent/codex.ts
+++ /dev/null
@@ -1,508 +0,0 @@
-import fs from "node:fs/promises";
-import os from "node:os";
-import path from "node:path";
-import { z } from "zod";
-
-import type { CanvasSnapshot } from "../canvas/types.js";
-import { logger } from "../utils/logger.js";
-import { errorMessage, field, type JsonRecord } from "../utils/value.js";
-import { CodexAppClient, CodexReportedError } from "./codex-client.js";
-import { codexEventHistory } from "./codex-event-history.js";
-import { settledTurnIds, summarizeCodexThread, threadMessages } from "./codex-history.js";
-import { messageMetadataStore } from "./message-metadata.js";
-import type { CodexReasoningEffort, CodexSkillMetadata, CodexSkillSelector, CodexSkillsListEntry } from "./codex-protocol.js";
-import type { AgentAttachment, AgentEmit, AgentPermissionMode } from "./types.js";
-
-type CodexRunOptions = { threadId?: string; cwd?: string; permissionMode?: AgentPermissionMode; model?: string; effort?: CodexReasoningEffort; skill?: CodexSkillSelector; messageText?: string; appEmit?: AgentEmit; onStart?: () => void; onThread?: (threadId: string) => void; onTurn?: (turnId: string) => void; onFinish?: () => void };
-type CodexSkillDraftInput = { model?: string; effort?: CodexReasoningEffort } & ({ source: "conversation"; threadId: string } | { source: "canvas"; snapshot: CanvasSnapshot });
-
-const skillNamePattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
-const skillDraftSchema = z.object({
- name: z.string().trim().min(1).max(64).regex(skillNamePattern),
- displayName: z.string().trim().max(64),
- description: z.string().trim().min(1).max(1024).refine((value) => !/[<>]/.test(value)),
- instructions: z.string().trim().min(1).max(20000),
- shortDescription: z.string().trim().max(64).refine((value) => !value || value.length >= 25),
- defaultPrompt: z.string().trim().max(1024),
-}).strict().superRefine((draft, context) => {
- if (draft.defaultPrompt && !mentionsSkill(draft.defaultPrompt, draft.name)) context.addIssue({ code: "custom", path: ["defaultPrompt"], message: `默认提示词必须包含 $${draft.name}` });
-});
-
-const SKILL_DRAFT_OUTPUT_SCHEMA: JsonRecord = {
- type: "object",
- additionalProperties: false,
- required: ["name", "displayName", "description", "instructions", "shortDescription", "defaultPrompt"],
- properties: {
- name: { type: "string", pattern: "^[a-z0-9]+(?:-[a-z0-9]+)*$", minLength: 1, maxLength: 64 },
- displayName: { type: "string", maxLength: 64 },
- description: { type: "string", pattern: "^[^<>]+$", minLength: 1, maxLength: 1024 },
- instructions: { type: "string", minLength: 1, maxLength: 20000 },
- shortDescription: { anyOf: [{ type: "string", maxLength: 0 }, { type: "string", minLength: 25, maxLength: 64 }] },
- defaultPrompt: { type: "string", maxLength: 1024 },
- },
-};
-
-export type AgentSkillDraft = z.infer;
-
-export class CodexSkillLookupError extends Error {
- override name = "CodexSkillLookupError";
- constructor(message: string, readonly statusCode: 400 | 404 | 409) {
- super(message);
- }
-}
-
-let codexQueue: Promise = Promise.resolve();
-let codexApp: CodexAppClient | null = null;
-let codexAppStart: Promise | null = null;
-/** 仅表示最近主动加载/选择的线程;运行中的 turn 身份由 CodexAppClient 自己维护。 */
-let loadedThreadId = "";
-
-export { summarizeCodexThread } from "./codex-history.js";
-
-/** 将 Codex turn 加入串行队列并等待执行完成。 */
-export async function runCodexTurn(prompt: string, lifecycleEmit: AgentEmit, attachments: AgentAttachment[] = [], options: CodexRunOptions = {}) {
- if (!prompt.trim()) return;
- codexQueue = codexQueue.catch(() => undefined).then(() => runCodexTurnNow(prompt, lifecycleEmit, attachments, options));
- await codexQueue;
-}
-
-/** 从当前对话或指定网页画布生成可编辑草稿,不写入 Skill 文件。 */
-export async function generateCodexSkillDraft(emit: AgentEmit, cwd: string, input: CodexSkillDraftInput): Promise {
- const queued = codexQueue.catch(() => undefined).then(() => generateCodexSkillDraftNow(emit, cwd, input));
- codexQueue = queued;
- return await queued;
-}
-
-/** 中断当前线程正在执行的 Codex turn。 */
-export async function interruptCodexTurn(threadId?: string) {
- if (!codexApp) return false;
- return await codexApp.interruptCurrentTurn(threadId);
-}
-
-/** 回复当前 app-server 的待处理权限请求。 */
-export async function resolveCodexApproval(requestId: string, decision: string) {
- return Boolean(codexApp?.resolveApproval(requestId, decision));
-}
-
-/** 创建新的 Codex 线程并记录当前线程 ID。 */
-export async function startCodexThread(emit: AgentEmit, cwd?: string, permissionMode: AgentPermissionMode = "request", preheat = false) {
- const app = await getCodexApp(emit);
- const thread = await app.startThread(cwd, permissionMode, preheat);
- loadedThreadId = String(field(thread, "id") || "");
- return thread;
-}
-
-/** 恢复指定 Codex 线程并返回聊天历史。 */
-export async function resumeCodexThread(emit: AgentEmit, threadId: string, cwd?: string, permissionMode: AgentPermissionMode = "request", preheat = false) {
- const app = await getCodexApp(emit);
- const thread = await resumeLoadedThread(app, threadId, cwd, permissionMode, true, preheat);
- const history = await loadCodexHistory(emit, threadId, cwd);
- const supplementalItems = await codexEventHistory.readThread(threadId);
- const messages = await mergeMessageMetadata(threadId, threadMessages(history.thread, app.planUpdates(threadId), supplementalItems));
- return { thread, messages, settledTurnIds: settledTurnIds(history.thread, supplementalItems), historyReady: history.historyReady };
-}
-
-/** 查询当前工作空间中的 Codex 线程。 */
-export async function listCodexThreads(emit: AgentEmit, options: { cwd: string; searchTerm?: string; limit?: number }) {
- const app = await getCodexApp(emit);
- const result = await app.listThreads({
- limit: options.limit || 40,
- sortKey: "updated_at",
- sortDirection: "desc",
- sourceKinds: ["cli", "vscode", "appServer", "exec"],
- cwd: options.cwd,
- ...(options.searchTerm ? { searchTerm: options.searchTerm } : {}),
- });
- const data = Array.isArray(field(result, "data")) ? (field(result, "data") as unknown[]).map(summarizeCodexThread).filter((thread) => threadInWorkspace(thread, options.cwd)) : [];
- return { data, nextCursor: field(result, "nextCursor") || null, backwardsCursor: field(result, "backwardsCursor") || null };
-}
-
-/** 查询当前账号可用于新任务的 Codex 模型。 */
-export async function listCodexModels(emit: AgentEmit) {
- return await (await getCodexApp(emit)).listModels();
-}
-
-/** 查询当前工作空间的原生 Skill 列表。 */
-export async function listCodexSkills(emit: AgentEmit, cwd: string, forceReload = false): Promise {
- const result = await (await getCodexApp(emit)).listSkills(cwd, forceReload);
- return result.data.find((entry) => samePath(entry.cwd, cwd)) || { cwd, skills: [], errors: [] };
-}
-
-/** 从原生 Skill 列表中解析并校验浏览器提交的选择器。 */
-export async function resolveCodexSkill(emit: AgentEmit, cwd: string, selector: CodexSkillSelector, requireEnabled = false): Promise {
- const name = String(selector?.name || "");
- const requestedPath = String(selector?.path || "");
- if (!name || !requestedPath || !path.isAbsolute(requestedPath)) throw new CodexSkillLookupError("Skill 选择无效", 400);
- const { skills } = await listCodexSkills(emit, cwd, true);
- const skill = skills.find((item) => item.name === name && samePath(item.path, requestedPath));
- if (!skill) throw new CodexSkillLookupError("找不到指定 Skill,请刷新列表后重试", 404);
- if (requireEnabled && !skill.enabled) throw new CodexSkillLookupError("该 Skill 已停用,请先启用后再使用", 409);
- return skill;
-}
-
-/** 修改经过原生列表校验的 Skill 启用状态。 */
-export async function configureCodexSkill(emit: AgentEmit, cwd: string, selector: CodexSkillSelector, enabled: boolean) {
- const skill = await resolveCodexSkill(emit, cwd, selector);
- const result = await (await getCodexApp(emit)).setSkillEnabled(skill.path, enabled);
- return { ...result, skill: { ...skill, enabled: result.effectiveEnabled } };
-}
-
-/** 读取指定 Codex 线程及其聊天历史。 */
-export async function readCodexThread(emit: AgentEmit, threadId: string, cwd?: string) {
- const app = await getCodexApp(emit);
- const history = await loadCodexHistory(emit, threadId, cwd);
- const supplementalItems = await codexEventHistory.readThread(threadId);
- const messages = await mergeMessageMetadata(threadId, threadMessages(history.thread, app.planUpdates(threadId), supplementalItems));
- return { thread: summarizeCodexThread(history.thread), messages, settledTurnIds: settledTurnIds(history.thread, supplementalItems), historyReady: history.historyReady };
-}
-
-/** 归档指定 Codex 线程。 */
-export async function archiveCodexThread(emit: AgentEmit, threadId: string, cwd?: string) {
- const app = await getCodexApp(emit);
- try {
- await loadCodexThread(emit, threadId, cwd, false);
- } catch (error) {
- if (!isRecoverableThreadError(error)) throw error;
- await resumeLoadedThread(app, threadId, cwd, "request", false);
- }
- await app.archiveThread(threadId);
- app.clearPlanUpdates(threadId);
- await codexEventHistory.removeThread(threadId);
- await messageMetadataStore.removeThread(threadId).catch((error) => logger.warn("Failed to remove archived thread message metadata", { threadId, error }));
- if (loadedThreadId === threadId) loadedThreadId = "";
-}
-
-async function mergeMessageMetadata(threadId: string, messages: T[]) {
- try {
- return await messageMetadataStore.mergeThread(threadId, messages);
- } catch (error) {
- logger.warn("Failed to read thread message metadata", { threadId, error });
- return messages;
- }
-}
-
-/** 判断线程异常是否允许自动新建线程后重试。 */
-export function isRecoverableThreadError(error: unknown) {
- return /thread not loaded|no rollout found/i.test(errorMessage(error));
-}
-
-/** 执行一次 Codex turn,并负责附件临时文件和线程恢复。 */
-async function runCodexTurnNow(prompt: string, lifecycleEmit: AgentEmit, attachments: AgentAttachment[], options: CodexRunOptions) {
- let files: string[] = [];
- try {
- options.onStart?.();
- files = await writeAttachmentFiles(attachments);
- const app = await getCodexApp(options.appEmit || lifecycleEmit);
- let threadId = await ensureCodexThread(app, options, lifecycleEmit);
- options.onThread?.(threadId);
- try {
- await app.startTurn(threadId, prompt, files, options.permissionMode || "request", options.model, options.effort, options.onTurn, options.skill, options.messageText);
- } catch (error) {
- if (!isRecoverableThreadError(error)) throw error;
- lifecycleEmit("agent_log", { text: `Codex thread unavailable, starting a new thread: ${errorMessage(error)}` });
- loadedThreadId = "";
- threadId = await ensureCodexThread(app, { cwd: options.cwd }, lifecycleEmit);
- options.onThread?.(threadId);
- await app.startTurn(threadId, prompt, files, options.permissionMode || "request", options.model, options.effort, options.onTurn, options.skill, options.messageText);
- }
- } catch (error) {
- logger.error("Codex turn failed", error);
- if (!(error instanceof CodexReportedError)) lifecycleEmit("agent_error", { message: errorMessage(error) });
- } finally {
- options.onFinish?.();
- await Promise.all(files.map((file) => fs.unlink(file).catch(() => undefined)));
- }
-}
-
-/** 恢复请求线程或创建新的 Codex 线程。 */
-async function ensureCodexThread(app: CodexAppClient, options: CodexRunOptions, emit: AgentEmit) {
- if (options.threadId) {
- if (options.threadId === loadedThreadId) return loadedThreadId;
- try {
- await resumeLoadedThread(app, options.threadId, options.cwd, options.permissionMode || "request", true);
- return loadedThreadId;
- } catch (error) {
- if (!isRecoverableThreadError(error)) throw error;
- emit("agent_log", { text: `Codex thread unavailable, starting a new thread: ${errorMessage(error)}` });
- loadedThreadId = "";
- }
- }
- if (!loadedThreadId) {
- const thread = await app.startThread(options.cwd, options.permissionMode || "request");
- loadedThreadId = String(field(thread, "id") || "");
- }
- return loadedThreadId;
-}
-
-/** 从 app-server 读取线程并校验工作空间。 */
-async function loadCodexThread(emit: AgentEmit, threadId: string, cwd: string | undefined, includeTurns: boolean) {
- const app = await getCodexApp(emit);
- const result = await app.readThread(threadId, includeTurns);
- const thread = field(result, "thread") || {};
- assertThreadWorkspace(thread, cwd);
- return thread;
-}
-
-async function generateCodexSkillDraftNow(emit: AgentEmit, cwd: string, input: CodexSkillDraftInput) {
- const app = await getCodexApp(emit);
- let threadId = "";
- try {
- const thread = input.source === "conversation" ? await app.forkSkillDraftThread(input.threadId, cwd) : await app.startSkillDraftThread(cwd);
- threadId = String(field(thread, "id") || "");
- const raw = await app.generateSkillDraft(threadId, skillDraftPrompt(input), SKILL_DRAFT_OUTPUT_SCHEMA, input.model, input.effort);
- let value: unknown;
- try {
- value = JSON.parse(raw);
- } catch {
- throw new Error("Codex 返回的 Skill 草稿不是有效 JSON");
- }
- const parsed = skillDraftSchema.safeParse(value);
- if (!parsed.success) throw new Error("Codex 返回的 Skill 草稿格式不正确");
- assertDraftHasNoSensitiveValues(parsed.data, input.source === "canvas" ? canvasPrivateValues(input.snapshot) : []);
- return parsed.data;
- } finally {
- if (threadId) await app.closeSkillDraftThread(threadId).catch((error) => logger.warn("Failed to release Skill draft thread", { threadId, error }));
- }
-}
-
-function skillDraftPrompt(input: CodexSkillDraftInput) {
- const source = input.source === "conversation"
- ? "从这个临时分支继承的完整对话中,识别已经实际完成且值得复用的稳定流程。不要总结本条提炼请求,也不要保留一次性的结论、错误排查过程或工具日志。"
- : `从下面经过清理的画布快照中,识别节点、连线和生成步骤所表达的可复用流程。不要把画布节点 ID 写进执行说明。\n\n画布快照:\n${JSON.stringify(canvasSkillSource(input.snapshot))}`;
- return [
- "请生成一个可编辑的 Codex Skill 草稿。",
- source,
- "要求:",
- "- name 使用不超过 64 个字符的小写字母、数字和连字符,优先使用简短的动词短语。",
- "- description 同时说明能力和触发场景;所有何时使用的信息都写在这里。",
- "- instructions 只写另一个 Codex 真正需要的、可复用的命令式步骤、约束和输出要求,不写 YAML frontmatter。",
- "- shortDescription 写 25–64 个字符的人类可读短说明;没有合适内容时返回空字符串。",
- "- defaultPrompt 必须包含与 name 完全一致的 $skill-name 调用标记;没有合适内容时返回空字符串。",
- "- displayName 使用简洁的人类可读名称。",
- "- 不得输出 Token、API Key、密码、凭证、本地路径、媒体 URL、敏感 URL、临时错误、调试日志或一次性结果。",
- "只按 outputSchema 返回对象。",
- ].join("\n");
-}
-
-const MAX_CANVAS_SKILL_NODES = 300;
-const MAX_CANVAS_SKILL_CONNECTIONS = 600;
-const MAX_CANVAS_SKILL_NODE_CHARS = 100_000;
-const MAX_CANVAS_SKILL_SOURCE_CHARS = 120_000;
-
-/** 只保留理解画布流程所需的信息,避免把媒体、外部地址、坐标和本地凭证送入草稿线程。 */
-export function canvasSkillSource(snapshot: CanvasSnapshot): JsonRecord {
- const allNodes = prioritizedCanvasNodes(snapshot);
- const nodes = allNodes.slice(0, MAX_CANVAS_SKILL_NODES);
- const nodeRefs = new Map(allNodes.map((node, index) => [node.id, `node-${index + 1}`]));
- const title = cleanCanvasString(snapshot.title, nodeRefs);
- const source: JsonRecord & { nodes: JsonRecord[]; connections: Array<{ from: string; to: string }> } = {
- ...(title ? { title } : {}),
- nodes: [],
- connections: [],
- };
- let truncated = nodes.length < allNodes.length;
- nodes.forEach((node, index) => {
- const metadata = { ...(node.metadata || {}) };
- if (node.type !== "text") delete metadata.content;
- const cleanMetadata = sanitizeCanvasValue(metadata, nodeRefs);
- const nodeTitle = cleanCanvasString(node.title, nodeRefs);
- const summary = { ref: `node-${index + 1}`, type: node.type, ...(nodeTitle ? { title: nodeTitle } : {}) };
- const candidate = { ...summary, ...(cleanMetadata && Object.keys(cleanMetadata as JsonRecord).length ? { metadata: cleanMetadata } : {}) };
- if (canvasSourceFits({ ...source, nodes: [...source.nodes, candidate] }, MAX_CANVAS_SKILL_NODE_CHARS)) source.nodes.push(candidate);
- else if (canvasSourceFits({ ...source, nodes: [...source.nodes, summary] }, MAX_CANVAS_SKILL_NODE_CHARS)) (source.nodes.push(summary), truncated = true);
- else truncated = true;
- });
- const includedRefs = new Set(source.nodes.map((node) => String(node.ref || "")));
- const selectedNodeRefs = (snapshot.selectedNodeIds || []).flatMap((id) => nodeRefs.get(id) || []).filter((ref) => includedRefs.has(ref));
- if (selectedNodeRefs.length) source.selectedNodeRefs = selectedNodeRefs;
- const connections = (snapshot.connections || []).flatMap(({ fromNodeId, toNodeId }) => {
- const from = nodeRefs.get(fromNodeId);
- const to = nodeRefs.get(toNodeId);
- return from && to && includedRefs.has(from) && includedRefs.has(to) ? [{ from, to }] : [];
- });
- if (connections.length > MAX_CANVAS_SKILL_CONNECTIONS) truncated = true;
- connections.slice(0, MAX_CANVAS_SKILL_CONNECTIONS).forEach((connection) => {
- if (canvasSourceFits({ ...source, connections: [...source.connections, connection] }, MAX_CANVAS_SKILL_SOURCE_CHARS - 32)) source.connections.push(connection);
- else truncated = true;
- });
- if (truncated) source.truncated = true;
- return source;
-}
-
-const sensitiveCanvasKey = /api.?key|token|secret|password|authorization|credential|storage.?key|(?:local|file).?path/i;
-const transientCanvasKey = /^(?:status|progress|errorDetails|taskId|createdAt|updatedAt|startedAt|completedAt)$/i;
-const canvasNodeReferenceKey = /^.*(?:Node|Group|Parent|Child|Root|Source|Target|PrimaryImage)Ids?$/i;
-const directLocalPath = /^(?:file:(?:\/\/)?|[a-z]:[\\/]|\\\\|\/(?!\/)(?=[^\s`'"“”<>]+\/))/i;
-const fileUrl = /\bfile:(?:\/\/)?[^\s`'"“”<>]+/gi;
-const inlineLocalPath = /(?]+|(?]+\/)[^\s`'"“”<>]+/giu;
-const credentialAssignment = /(?:api[_ -]?key|access[_ -]?(?:key|token)|connect[_ -]?token|token|secret|password|authorization|credential)\s*(?:[:=:]|为|是)\s*(?:bearer\s+)?[`'"“]?[A-Za-z0-9_./+\-=]{8,}/gi;
-const bearerToken = /\bbearer\s+[A-Za-z0-9._~+/=\-]{8,}/gi;
-const jwtToken = /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g;
-const knownApiToken = /\b(?:sk-[A-Za-z0-9_-]{12,}|(?:gh[pousr]|github_pat)_[A-Za-z0-9_]{20,}|AKIA[A-Z0-9]{16})\b/g;
-const transientIdentifier = /\b(?:task|job|request|generation|node)[_-](?:\d{4,}|[A-Fa-f0-9]{8,}|(?=[A-Za-z0-9_-]{12,}\b)(?=[A-Za-z0-9_-]*\d)[A-Za-z0-9_-]+)\b/gi;
-const webUrl = /\bhttps?:\/\/[^\s<>{}\[\]`'"“”]+/gi;
-
-function sanitizeCanvasValue(value: unknown, nodeRefs: Map, depth = 0): unknown {
- if (value === null || typeof value === "boolean" || typeof value === "number") return value;
- if (typeof value === "string") return cleanCanvasString(value, nodeRefs);
- if (depth >= 6) return undefined;
- if (Array.isArray(value)) return value.slice(0, 300).map((item) => sanitizeCanvasValue(item, nodeRefs, depth + 1)).filter((item) => item !== undefined);
- if (!value || typeof value !== "object") return undefined;
- const result: JsonRecord = {};
- Object.entries(value as JsonRecord).forEach(([key, item]) => {
- if (sensitiveCanvasKey.test(key) || transientCanvasKey.test(key)) return;
- if (canvasNodeReferenceKey.test(key)) {
- const refs = (Array.isArray(item) ? item : [item]).flatMap((id) => typeof id === "string" ? nodeRefs.get(id) || [] : []);
- if (refs.length) result[key.replace(/Ids$/i, "Refs").replace(/Id$/i, "Ref")] = Array.isArray(item) ? [...new Set(refs)] : refs[0];
- return;
- }
- const clean = sanitizeCanvasValue(item, nodeRefs, depth + 1);
- if (clean !== undefined) result[replaceCanvasNodeRefs(key, nodeRefs)] = clean;
- });
- return result;
-}
-
-function cleanCanvasString(value: unknown, nodeRefs: Map) {
- const valueText = typeof value === "string" ? value.trim() : "";
- if (!valueText || /^(?:data:|blob:|https?:\/\/|file:)/i.test(valueText) || directLocalPath.test(valueText)) return undefined;
- const text = replaceCanvasNodeRefs(valueText, nodeRefs)
- .replace(/\b(?:data:|blob:)[^\s`'"“”<>]+/gi, "[媒体地址已移除]")
- .replace(fileUrl, "[本地路径已移除]")
- .replace(webUrl, "[外部地址已移除]")
- .replace(inlineLocalPath, "[本地路径已移除]")
- .replace(credentialAssignment, "[敏感凭证已移除]")
- .replace(bearerToken, "[敏感凭证已移除]")
- .replace(jwtToken, "[敏感凭证已移除]")
- .replace(knownApiToken, "[敏感凭证已移除]")
- .trim();
- return text.length > 12000 ? `${text.slice(0, 12000)}\n[内容已截断]` : text || undefined;
-}
-
-function replaceCanvasNodeRefs(value: string, nodeRefs: Map) {
- const nodeIds = [...nodeRefs.keys()].filter(Boolean).sort((left, right) => right.length - left.length);
- const nodeIdPattern = nodeIds.length ? new RegExp(nodeIds.map((id) => id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|"), "g") : undefined;
- return nodeIdPattern ? value.replace(nodeIdPattern, (id) => nodeRefs.get(id) || id) : value;
-}
-
-function prioritizedCanvasNodes(snapshot: CanvasSnapshot) {
- const nodes = snapshot.nodes || [];
- const selectedIds = new Set(snapshot.selectedNodeIds || []);
- const relatedIds = new Set();
- (snapshot.connections || []).forEach(({ fromNodeId, toNodeId }) => {
- if (selectedIds.has(fromNodeId)) relatedIds.add(toNodeId);
- if (selectedIds.has(toNodeId)) relatedIds.add(fromNodeId);
- });
- return [
- ...nodes.filter((node) => selectedIds.has(node.id)),
- ...nodes.filter((node) => !selectedIds.has(node.id) && relatedIds.has(node.id)),
- ...nodes.filter((node) => !selectedIds.has(node.id) && !relatedIds.has(node.id)),
- ];
-}
-
-function canvasSourceFits(source: JsonRecord, limit: number) {
- return JSON.stringify(source).length <= limit;
-}
-
-function canvasPrivateValues(snapshot: CanvasSnapshot) {
- return [snapshot.projectId, snapshot.clientId, ...(snapshot.nodes || []).map((node) => node.id), ...(snapshot.connections || []).map((connection) => connection.id)]
- .filter((value): value is string => typeof value === "string" && value.length >= 6);
-}
-
-function patternMatches(pattern: RegExp, text: string) {
- pattern.lastIndex = 0;
- return pattern.test(text);
-}
-
-function mentionsSkill(prompt: string, name: string) {
- return new RegExp(`\\$${name}(?![A-Za-z0-9_-]|:[A-Za-z0-9_-])`).test(prompt);
-}
-
-export function assertDraftHasNoSensitiveValues(draft: AgentSkillDraft, privateValues: string[]) {
- const text = Object.values(draft).join("\n");
- const localPath = /(?:^|[\s`'"“”((\[{,:;:])(?:file:(?:\/\/)?|[a-z]:[\\/]|\\\\|\/(?!\/))/im;
- const externalUrl = (text.match(webUrl) || []).length > 0;
- const hasPrivateValue = privateValues.some((value) => text.includes(value));
- if (localPath.test(text) || /\b(?:data:|blob:)/i.test(text) || patternMatches(credentialAssignment, text) || patternMatches(bearerToken, text) || patternMatches(jwtToken, text) || patternMatches(knownApiToken, text) || patternMatches(transientIdentifier, text) || externalUrl || hasPrivateValue) {
- throw new Error("生成的 Skill 草稿包含外部地址、本地路径、敏感凭证或一次性标识,已拒绝返回");
- }
-}
-
-/** 读取线程历史,并显式标记 Codex 是否已经物化 turns。 */
-async function loadCodexHistory(emit: AgentEmit, threadId: string, cwd?: string) {
- try {
- return { thread: await loadCodexThread(emit, threadId, cwd, true), historyReady: true };
- } catch (error) {
- if (/not materialized yet.*includeTurns/i.test(errorMessage(error))) return { thread: await loadCodexThread(emit, threadId, cwd, false), historyReady: false };
- if (!isRecoverableThreadError(error)) throw error;
- const app = await getCodexApp(emit);
- const thread = await resumeLoadedThread(app, threadId, cwd, "request", false);
- try {
- return { thread: await loadCodexThread(emit, threadId, cwd, true), historyReady: true };
- } catch (historyError) {
- if (/not materialized yet.*includeTurns/i.test(errorMessage(historyError))) return { thread, historyReady: false };
- throw historyError;
- }
- }
-}
-
-/** 恢复线程并统一校验工作空间与进程内活动线程。 */
-async function resumeLoadedThread(app: CodexAppClient, threadId: string, cwd?: string, permissionMode: AgentPermissionMode = "request", updateLoaded = true, preheat = false) {
- const thread = await app.resumeThread(threadId, cwd, permissionMode, preheat);
- assertThreadWorkspace(thread, cwd);
- if (updateLoaded) loadedThreadId = String(field(thread, "id") || threadId);
- return thread;
-}
-
-/** 获取已启动的 Codex app-server 客户端。 */
-async function getCodexApp(emit: AgentEmit) {
- if (codexApp) return codexApp;
- codexAppStart ||= CodexAppClient.start(emit, () => {
- codexApp = null;
- loadedThreadId = "";
- });
- try {
- codexApp = await codexAppStart;
- return codexApp;
- } finally {
- codexAppStart = null;
- }
-}
-
-/** 校验线程是否属于指定工作空间。 */
-function assertThreadWorkspace(thread: unknown, cwd?: string) {
- if (!cwd || threadInWorkspace(thread, cwd)) return;
- throw new Error("该 Codex 会话不属于当前画布工作空间");
-}
-
-/** 判断线程工作目录是否与当前工作空间一致。 */
-function threadInWorkspace(thread: unknown, cwd: string) {
- const threadCwd = String(field(thread, "cwd") || "");
- return Boolean(threadCwd && samePath(threadCwd, cwd));
-}
-
-/** 比较跨平台绝对路径;Windows 路径不区分大小写。 */
-function samePath(left: string, right: string) {
- const normalize = (value: string) => process.platform === "win32" ? path.resolve(value).toLowerCase() : path.resolve(value);
- return normalize(left) === normalize(right);
-}
-
-/** 将图片附件写入临时文件供 Codex 读取。 */
-async function writeAttachmentFiles(attachments: AgentAttachment[]) {
- return await Promise.all(attachments.filter((item) => item.dataUrl?.startsWith("data:image/")).map(writeAttachmentFile));
-}
-
-/** 将单个 Data URL 图片附件写入临时文件。 */
-async function writeAttachmentFile(item: AgentAttachment) {
- const [, meta = "", data = ""] = item.dataUrl?.match(/^data:([^;]+);base64,(.+)$/) || [];
- if (!data) throw new Error(`图片附件无效:${item.name || "未命名图片"}`);
- const file = path.join(os.tmpdir(), `infinite-canvas-${Date.now()}-${Math.random().toString(16).slice(2)}.${imageExt(meta || item.type)}`);
- await fs.writeFile(file, Buffer.from(data, "base64"));
- return file;
-}
-
-/** 根据图片 MIME 类型返回临时文件扩展名。 */
-function imageExt(type = "") {
- if (type.includes("png")) return "png";
- if (type.includes("webp")) return "webp";
- return "jpg";
-}
diff --git a/canvas-agent/src/agent/message-metadata.test.ts b/canvas-agent/src/agent/message-metadata.test.ts
deleted file mode 100644
index 8c4efc1..0000000
--- a/canvas-agent/src/agent/message-metadata.test.ts
+++ /dev/null
@@ -1,104 +0,0 @@
-import assert from "node:assert/strict";
-import fs from "node:fs/promises";
-import os from "node:os";
-import path from "node:path";
-import test, { type TestContext } from "node:test";
-
-import { MessageMetadataStore } from "./message-metadata.js";
-
-test("message metadata survives Agent restarts", async (context) => {
- const fixture = await createFixture(context);
- await fixture.store.recordPending("message-1", sampleMetadata());
- await fixture.store.bindThread("message-1", "thread-1");
- await fixture.store.bindTurn("message-1", "thread-1", "turn-1");
-
- const [message] = await fixture.reopen().mergeThread("thread-1", [{ role: "user", threadId: "thread-1", turnId: "turn-1", text: "Generate product images" }]);
- assert.equal(message.clientMessageId, "message-1");
- assert.equal(message.attachments?.[0].name, "image.png");
- assert.equal(message.canvasReferences?.[0].nodeId, "node-1");
- assert.equal(message.skill?.name, "product-grid");
-});
-
-test("message preview assets survive restarts and are deleted with their thread", async (context) => {
- const fixture = await createFixture(context);
- const metadata = await fixture.store.recordPending("message-1", sampleMetadata());
- await fixture.store.bindTurn("message-1", "thread-1", "turn-1");
-
- const match = metadata?.attachments?.[0].url.match(/^agent-asset:([a-f0-9]{64})\/([a-f0-9]{64}\.png)$/);
- assert.ok(match);
- const asset = await fixture.reopen().readAsset(match[1], match[2]);
- assert.equal(asset?.contentType, "image/png");
- assert.equal(asset?.data.toString(), "a");
-
- await fixture.store.removeThread("thread-1");
- assert.equal(await fixture.reopen().readAsset(match[1], match[2]), undefined);
-});
-
-test("deleting a thread only removes its metadata", async (context) => {
- const fixture = await createFixture(context);
- for (const number of [1, 2]) {
- await fixture.store.recordPending(`message-${number}`, { skill: { name: `skill-${number}`, path: `D:\\skills\\skill-${number}\\SKILL.md` } });
- await fixture.store.bindTurn(`message-${number}`, `thread-${number}`, `turn-${number}`);
- }
- await fixture.store.removeThread("thread-1");
-
- const first = await fixture.reopen().mergeThread("thread-1", [{ role: "user", threadId: "thread-1", turnId: "turn-1" }]);
- const second = await fixture.reopen().mergeThread("thread-2", [{ role: "user", threadId: "thread-2", turnId: "turn-2" }]);
- assert.equal(first[0].skill, undefined);
- assert.equal(second[0].skill?.name, "skill-2");
-});
-
-test("history metadata is matched by thread and turn instead of client message id alone", async (context) => {
- const fixture = await createFixture(context);
- await fixture.store.recordPending("message-1", { skill: { name: "product-grid", path: "D:\\skills\\product-grid\\SKILL.md" } });
- await fixture.store.bindTurn("message-1", "thread-1", "turn-1");
-
- const [message] = await fixture.store.mergeThread("thread-1", [{ role: "user", threadId: "thread-1", turnId: "turn-2", clientMessageId: "message-1" }]);
- assert.equal(message.skill, undefined);
-});
-
-test("unknown storage versions are never overwritten", async (context) => {
- const fixture = await createFixture(context, false);
- await fs.mkdir(fixture.storeDirectory, { recursive: true });
- const manifestFile = path.join(fixture.storeDirectory, "manifest.json");
- await fs.writeFile(manifestFile, '{"version":99}');
-
- await assert.rejects(() => fixture.store.recordPending("message-1", sampleMetadata()), /Unsupported message metadata version/);
- assert.equal(await fs.readFile(manifestFile, "utf8"), '{"version":99}');
-});
-
-test("storage without a manifest is never overwritten", async (context) => {
- const fixture = await createFixture(context, false);
- await fs.mkdir(fixture.storeDirectory, { recursive: true });
- const existingFile = path.join(fixture.storeDirectory, "existing.json");
- await fs.writeFile(existingFile, "existing data");
-
- await assert.rejects(() => fixture.store.mergeThread("thread-1", []), /missing manifest/);
- assert.equal(await fs.readFile(existingFile, "utf8"), "existing data");
-});
-
-test("oversized image previews are rejected instead of silently dropped", async (context) => {
- const fixture = await createFixture(context);
- await assert.rejects(
- () => fixture.store.recordPending("message-1", { attachments: [{ id: "image-1", name: "large.png", url: `data:image/png;base64,${"a".repeat(500_001)}` }] }),
- /attachments metadata is invalid/,
- );
-});
-
-async function createFixture(context: TestContext, initialize = true) {
- const root = await fs.mkdtemp(path.join(os.tmpdir(), "canvas-agent-message-metadata-"));
- context.after(() => fs.rm(root, { recursive: true, force: true }));
- const storeDirectory = path.join(root, "message-metadata");
- const createStore = () => new MessageMetadataStore(storeDirectory);
- const fixture = { root, storeDirectory, store: createStore(), reopen: createStore };
- if (initialize) await fixture.store.mergeThread("empty", []);
- return fixture;
-}
-
-function sampleMetadata() {
- return {
- attachments: [{ id: "image-1", name: "image.png", url: "data:image/png;base64,YQ==" }],
- canvasReferences: [{ nodeId: "node-1", label: "Image 1", title: "Product image", kind: "image", previewUrl: "data:image/png;base64,YQ==" }],
- skill: { name: "product-grid", path: "D:\\skills\\product-grid\\SKILL.md", displayName: "Product grid" },
- };
-}
diff --git a/canvas-agent/src/agent/message-metadata.ts b/canvas-agent/src/agent/message-metadata.ts
deleted file mode 100644
index ad870fd..0000000
--- a/canvas-agent/src/agent/message-metadata.ts
+++ /dev/null
@@ -1,369 +0,0 @@
-import crypto from "node:crypto";
-import fs from "node:fs/promises";
-import path from "node:path";
-
-import { CONFIG_DIR } from "../config.js";
-import type { AgentAttachmentDisplay, AgentCanvasReference, AgentMessageMetadata, AgentSkillReference } from "./types.js";
-
-type MessageMetadataRecord = { version: 1; clientMessageId: string; threadId?: string; turnId?: string; createdAt: number; metadata: AgentMessageMetadata };
-type MetadataMessage = { role: string; threadId: string; turnId: string; clientMessageId?: string };
-
-const STORAGE_VERSION = 1;
-const MAX_PREVIEW_LENGTH = 500_000;
-const MAX_TEXT_LENGTH = 20_000;
-const MANIFEST_FILE = "manifest.json";
-const MESSAGE_ASSET_PREFIX = "agent-asset:";
-
-const MESSAGE_METADATA_DIRECTORY = path.join(CONFIG_DIR, "message-metadata");
-
-export class MessageMetadataStore {
- private queue: Promise = Promise.resolve();
- private ready?: Promise;
-
- constructor(private directory = MESSAGE_METADATA_DIRECTORY) {}
-
- recordPending(clientMessageId: string, value: unknown) {
- return this.run(async () => {
- const metadata = normalizeMetadata(value, false);
- if (!clientMessageId || !metadata) return undefined;
- await this.ensureReady();
- const storedMetadata = await persistMetadataPreviews(this.directory, clientMessageId, metadata);
- const record: MessageMetadataRecord = { version: STORAGE_VERSION, clientMessageId, createdAt: Date.now(), metadata: storedMetadata };
- await writeJson(this.pendingFile(clientMessageId), record);
- return structuredClone(storedMetadata);
- });
- }
-
- bindThread(clientMessageId: string, threadId: string) {
- return this.run(async () => {
- if (!clientMessageId || !threadId) return;
- await this.ensureReady();
- const pendingFile = this.pendingFile(clientMessageId);
- const targetFile = this.threadFile(threadId, clientMessageId);
- const record = await readRecord(targetFile) || await readRecord(pendingFile);
- if (!record) return;
- await writeJson(targetFile, { ...record, threadId, turnId: record.threadId === threadId ? record.turnId : undefined });
- await removeFile(pendingFile);
- });
- }
-
- bindTurn(clientMessageId: string, threadId: string, turnId: string) {
- return this.run(async () => {
- if (!clientMessageId || !threadId || !turnId) return;
- await this.ensureReady();
- const pendingFile = this.pendingFile(clientMessageId);
- const targetFile = this.threadFile(threadId, clientMessageId);
- const record = await readRecord(targetFile) || await readRecord(pendingFile);
- if (!record) return;
- await writeJson(targetFile, { ...record, threadId, turnId });
- await removeFile(pendingFile);
- });
- }
-
- mergeThread(threadId: string, messages: T[]) {
- return this.run>(async () => {
- await this.ensureReady();
- const records = (await readRecords(this.threadDirectory(threadId))).filter((item) => item.threadId === threadId && item.turnId);
- const byTurnId = new Map(records.map((item) => [item.turnId!, item]));
- return messages.map((message) => {
- if (message.role !== "user" || message.threadId !== threadId) return message;
- const record = byTurnId.get(message.turnId);
- return record ? { ...message, clientMessageId: record.clientMessageId, ...structuredClone(record.metadata) } : message;
- });
- });
- }
-
- remove(clientMessageId: string, threadId = "") {
- return this.run(async () => {
- if (!clientMessageId) return;
- await this.ensureReady();
- await Promise.all([
- removeFile(this.pendingFile(clientMessageId)),
- ...(threadId ? [removeFile(this.threadFile(threadId, clientMessageId))] : []),
- fs.rm(this.assetDirectory(clientMessageId), { recursive: true, force: true }),
- ]);
- });
- }
-
- removeThread(threadId: string) {
- return this.run(async () => {
- if (!threadId) return;
- await this.ensureReady();
- const records = await readRecords(this.threadDirectory(threadId));
- await Promise.all([
- fs.rm(this.threadDirectory(threadId), { recursive: true, force: true }),
- ...records.map((record) => fs.rm(this.assetDirectory(record.clientMessageId), { recursive: true, force: true })),
- ]);
- });
- }
-
- readAsset(messageKey: string, assetFile: string) {
- return this.run(async () => {
- await this.ensureReady();
- if (!/^[a-f0-9]{64}$/.test(messageKey) || !/^[a-f0-9]{64}\.(?:gif|jpe?g|png|webp)$/.test(assetFile)) return undefined;
- try {
- return { data: await fs.readFile(path.join(this.directory, "assets", messageKey, assetFile)), contentType: assetContentType(assetFile) };
- } catch (error) {
- if (isMissing(error)) return undefined;
- throw error;
- }
- });
- }
-
- private run(task: () => Promise) {
- const result = this.queue.then(task, task);
- this.queue = result.then(() => undefined, () => undefined);
- return result;
- }
-
- private ensureReady() {
- this.ready ||= this.initialize();
- return this.ready;
- }
-
- private async initialize() {
- const manifestFile = path.join(this.directory, MANIFEST_FILE);
- try {
- const manifest = await readJson(manifestFile) as { version?: unknown };
- if (manifest.version !== STORAGE_VERSION) throw unsupportedVersion(manifest.version);
- return;
- } catch (error) {
- if (!isMissing(error)) throw error;
- }
-
- if (await exists(this.directory)) throw new Error(`Message metadata store is missing ${MANIFEST_FILE}; refusing to overwrite it.`);
- await createStorage(this.directory);
- }
-
- private pendingFile(clientMessageId: string) {
- return path.join(this.directory, "pending", `${storageKey(clientMessageId)}.json`);
- }
-
- private threadDirectory(threadId: string) {
- return path.join(this.directory, "threads", storageKey(threadId));
- }
-
- private threadFile(threadId: string, clientMessageId: string) {
- return path.join(this.threadDirectory(threadId), `${storageKey(clientMessageId)}.json`);
- }
-
- private assetDirectory(clientMessageId: string) {
- return path.join(this.directory, "assets", storageKey(clientMessageId));
- }
-}
-
-export const messageMetadataStore = new MessageMetadataStore();
-
-async function createStorage(directory: string) {
- const temporaryDirectory = `${directory}.${process.pid}.${Date.now()}.tmp`;
- try {
- await fs.mkdir(path.join(temporaryDirectory, "pending"), { recursive: true });
- await fs.mkdir(path.join(temporaryDirectory, "threads"), { recursive: true });
- await fs.mkdir(path.join(temporaryDirectory, "assets"), { recursive: true });
- await writeJson(path.join(temporaryDirectory, MANIFEST_FILE), { version: STORAGE_VERSION });
- await fs.rename(temporaryDirectory, directory);
- } finally {
- await fs.rm(temporaryDirectory, { recursive: true, force: true });
- }
-}
-
-async function readRecords(directory: string) {
- let files: string[];
- try {
- files = (await fs.readdir(directory, { withFileTypes: true })).filter((item) => item.isFile() && item.name.endsWith(".json")).map((item) => path.join(directory, item.name));
- } catch (error) {
- if (isMissing(error)) return [];
- throw error;
- }
- return (await Promise.all(files.map(readRecord))).filter((item): item is MessageMetadataRecord => Boolean(item));
-}
-
-async function readRecord(file: string): Promise {
- try {
- const value = await readJson(file) as Partial;
- if (value.version !== STORAGE_VERSION) throw unsupportedVersion(value.version);
- const clientMessageId = text(value.clientMessageId, 200);
- const metadata = normalizeMetadata(value.metadata, true);
- if (!clientMessageId || !metadata) throw new Error(`Message metadata record is invalid: ${file}`);
- const threadId = text(value.threadId, 200);
- const turnId = text(value.turnId, 200);
- return { version: STORAGE_VERSION, clientMessageId, createdAt: Number(value.createdAt) || 0, metadata, ...(threadId ? { threadId } : {}), ...(turnId ? { turnId } : {}) };
- } catch (error) {
- if (isMissing(error)) return undefined;
- throw error;
- }
-}
-
-function normalizeMetadata(value: unknown, allowAsset: boolean): AgentMessageMetadata | undefined {
- if (value === undefined || value === null) return undefined;
- if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Message metadata must be an object.");
- const source = value as Record;
- const attachments = normalizeList(source.attachments, 6, (item) => normalizeAttachment(item, allowAsset), "attachments");
- const canvasReferences = normalizeList(source.canvasReferences, 50, (item) => normalizeCanvasReference(item, allowAsset), "canvas references");
- const skill = normalizeSkill(source.skill);
- if (source.skill !== undefined && !skill) throw new Error("Message skill metadata is invalid.");
- if (!attachments.length && !canvasReferences.length && !skill) return undefined;
- return { ...(attachments.length ? { attachments } : {}), ...(canvasReferences.length ? { canvasReferences } : {}), ...(skill ? { skill } : {}) };
-}
-
-function normalizeList(value: unknown, limit: number, normalize: (item: unknown) => T | undefined, label: string) {
- if (value === undefined) return [];
- if (!Array.isArray(value)) throw new Error(`Message ${label} metadata must be an array.`);
- if (value.length > limit) throw new Error(`Message ${label} metadata exceeds the limit of ${limit}.`);
- const normalized = value.map(normalize).filter((item): item is T => Boolean(item));
- if (normalized.length !== value.length) throw new Error(`Message ${label} metadata is invalid.`);
- return normalized;
-}
-
-function normalizeAttachment(value: unknown, allowAsset: boolean): AgentAttachmentDisplay | undefined {
- if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
- const item = value as Record;
- const id = text(item.id, 200);
- const name = text(item.name, 500);
- const url = imagePreview(item.url, allowAsset);
- if (!id || !name || !url) return undefined;
- return { id, name, url, ...numberFields(item) };
-}
-
-function normalizeCanvasReference(value: unknown, allowAsset: boolean): AgentCanvasReference | undefined {
- if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
- const item = value as Record;
- const nodeId = text(item.nodeId, 200);
- const label = text(item.label, 200);
- const title = text(item.title, 500);
- const kind = String(item.kind || "");
- if (!nodeId || !label || !title || !["image", "video", "audio", "text"].includes(kind)) return undefined;
- const previewUrl = kind === "image" ? imagePreview(item.previewUrl, allowAsset) : remotePreview(item.previewUrl);
- const referenceText = text(item.text, MAX_TEXT_LENGTH);
- return { nodeId, label, title, kind: kind as AgentCanvasReference["kind"], ...(previewUrl ? { previewUrl } : {}), ...(referenceText ? { text: referenceText } : {}) };
-}
-
-function normalizeSkill(value: unknown): AgentSkillReference | undefined {
- if (value === undefined) return undefined;
- if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
- const item = value as Record;
- const name = text(item.name, 200);
- const skillPath = text(item.path, 2000);
- const displayName = text(item.displayName, 200);
- return name && skillPath ? { name, path: skillPath, ...(displayName ? { displayName } : {}) } : undefined;
-}
-
-function numberFields(item: Record) {
- const fields: Partial> = {};
- const type = text(item.type, 200);
- if (type) fields.type = type;
- for (const key of ["size", "width", "height"] as const) {
- const value = Number(item[key]);
- if (Number.isFinite(value) && value >= 0) fields[key] = value;
- }
- return fields;
-}
-
-function imagePreview(value: unknown, allowAsset = false) {
- if (typeof value !== "string") return "";
- const url = value.trim();
- if (allowAsset && new RegExp(`^${MESSAGE_ASSET_PREFIX}[a-f0-9]{64}/[a-f0-9]{64}\\.(?:gif|jpe?g|png|webp)$`).test(url)) return url;
- if (/^https?:\/\//i.test(url)) return url.length <= 5000 ? url : "";
- return url.length <= MAX_PREVIEW_LENGTH && /^data:image\/[a-z0-9.+-]+;base64,/i.test(url) ? url : "";
-}
-
-function remotePreview(value: unknown) {
- if (typeof value !== "string") return "";
- const url = value.trim();
- return url.length <= 5000 && /^https?:\/\//i.test(url) ? url : "";
-}
-
-function text(value: unknown, limit: number) {
- return typeof value === "string" ? value.trim().slice(0, limit) : "";
-}
-
-function storageKey(value: string) {
- return crypto.createHash("sha256").update(value).digest("hex");
-}
-
-async function persistMetadataPreviews(directory: string, clientMessageId: string, metadata: AgentMessageMetadata) {
- const attachments = await Promise.all((metadata.attachments || []).map(async (item) => ({ ...item, url: await persistImagePreview(directory, clientMessageId, `attachment:${item.id}`, item.url) })));
- const canvasReferences = await Promise.all((metadata.canvasReferences || []).map(async (item) => ({
- ...item,
- ...(item.kind === "image" && item.previewUrl ? { previewUrl: await persistImagePreview(directory, clientMessageId, `reference:${item.nodeId}`, item.previewUrl) } : {}),
- })));
- return {
- ...(attachments.length ? { attachments } : {}),
- ...(canvasReferences.length ? { canvasReferences } : {}),
- ...(metadata.skill ? { skill: metadata.skill } : {}),
- };
-}
-
-async function persistImagePreview(directory: string, clientMessageId: string, key: string, value: string) {
- const match = value.match(/^data:(image\/[a-z0-9.+-]+);base64,(.+)$/i);
- if (!match) return value;
- const extension = imageExtension(match[1]);
- if (!extension) throw new Error(`Unsupported message preview type: ${match[1]}`);
- const messageKey = storageKey(clientMessageId);
- const assetFile = `${storageKey(key)}.${extension}`;
- await writeFile(path.join(directory, "assets", messageKey, assetFile), Buffer.from(match[2], "base64"));
- return `${MESSAGE_ASSET_PREFIX}${messageKey}/${assetFile}`;
-}
-
-function imageExtension(contentType: string) {
- const type = contentType.toLowerCase();
- if (type === "image/jpeg" || type === "image/jpg") return "jpg";
- if (type === "image/png") return "png";
- if (type === "image/webp") return "webp";
- if (type === "image/gif") return "gif";
- return "";
-}
-
-function assetContentType(file: string) {
- const extension = path.extname(file).slice(1).toLowerCase();
- if (extension === "jpg" || extension === "jpeg") return "image/jpeg";
- return `image/${extension}`;
-}
-
-async function readJson(file: string) {
- try {
- return JSON.parse(await fs.readFile(file, "utf8")) as unknown;
- } catch (error) {
- if (error instanceof SyntaxError) throw new Error(`Message metadata JSON is invalid: ${file}`);
- throw error;
- }
-}
-
-async function writeJson(file: string, value: unknown) {
- await writeFile(file, JSON.stringify(value));
-}
-
-async function writeFile(file: string, value: string | Buffer) {
- await fs.mkdir(path.dirname(file), { recursive: true });
- const temporaryFile = `${file}.${process.pid}.${crypto.randomUUID()}.tmp`;
- try {
- await fs.writeFile(temporaryFile, value);
- await fs.rename(temporaryFile, file);
- } finally {
- await removeFile(temporaryFile);
- }
-}
-
-async function removeFile(file: string) {
- await fs.unlink(file).catch((error: NodeJS.ErrnoException) => {
- if (error.code !== "ENOENT") throw error;
- });
-}
-
-async function exists(file: string) {
- try {
- await fs.access(file);
- return true;
- } catch (error) {
- if (isMissing(error)) return false;
- throw error;
- }
-}
-
-function unsupportedVersion(version: unknown) {
- return new Error(`Unsupported message metadata version: ${String(version ?? "missing")}. Refusing to overwrite existing data.`);
-}
-
-function isMissing(error: unknown) {
- return (error as NodeJS.ErrnoException)?.code === "ENOENT";
-}
diff --git a/canvas-agent/src/agent/types.ts b/canvas-agent/src/agent/types.ts
deleted file mode 100644
index 2b29c4a..0000000
--- a/canvas-agent/src/agent/types.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-/** Agent 向网页广播事件的函数类型。 */
-export type AgentEmit = (type: string, payload: unknown) => void;
-
-/** 用户随当前 Agent 消息上传的附件。 */
-export type AgentAttachment = { id?: string; name?: string; type?: string; size?: number; width?: number; height?: number; dataUrl?: string };
-
-/** 用户消息在历史记录中展示所需的附件信息。 */
-export type AgentAttachmentDisplay = { id: string; name: string; type?: string; size?: number; width?: number; height?: number; url: string };
-
-/** 用户消息引用的画布素材。 */
-export type AgentCanvasReference = { nodeId: string; label: string; title: string; kind: "image" | "video" | "audio" | "text"; previewUrl?: string; text?: string };
-
-/** 用户消息调用的 Codex Skill。 */
-export type AgentSkillReference = { name: string; path: string; displayName?: string };
-
-/** 独立于 Codex 原生线程历史保存的用户消息展示元数据。 */
-export type AgentMessageMetadata = { attachments?: AgentAttachmentDisplay[]; canvasReferences?: AgentCanvasReference[]; skill?: AgentSkillReference };
-
-/** Codex 文件、命令和网络权限模式。 */
-export type AgentPermissionMode = "request" | "automatic" | "full";
diff --git a/canvas-agent/src/canvas/operations.ts b/canvas-agent/src/canvas/operations.ts
deleted file mode 100644
index 10a1aab..0000000
--- a/canvas-agent/src/canvas/operations.ts
+++ /dev/null
@@ -1,168 +0,0 @@
-import crypto from "node:crypto";
-
-import type { ToolName } from "./schemas.js";
-import { nextCanvasX } from "./tools.js";
-import type { CanvasNode, CanvasNodeType, CanvasSnapshot } from "./types.js";
-
-export type CanvasToolRequest = { name: "canvas_apply_ops"; input: Record };
-
-/** 将上层画布工具调用转换为前端可执行的批量操作。 */
-export function buildCanvasToolRequest(name: ToolName, input: Record, state: CanvasSnapshot | null): CanvasToolRequest {
- if (name === "canvas_apply_ops") return { name, input };
- if (name === "canvas_create_node") {
- const data = input as { nodeType: CanvasNodeType; title?: string; x?: number; y?: number; width?: number; height?: number; metadata?: Record };
- return applyOps([{ type: "add_node", nodeType: data.nodeType, title: data.title, position: { x: data.x ?? nextCanvasX(state), y: data.y ?? 0 }, width: data.width, height: data.height, metadata: data.metadata }]);
- }
- if (name === "canvas_create_text_node") {
- const data = input as { text?: string; x?: number; y?: number; title?: string; width?: number; height?: number };
- return applyOps([textNodeOp(data, data.x ?? nextCanvasX(state), data.y ?? 0)]);
- }
- if (name === "canvas_create_text_nodes") {
- const data = input as { items: Array<{ text: string; title?: string; x?: number; y?: number; width?: number; height?: number }>; x?: number; y?: number; gap?: number; direction?: "row" | "column" };
- const x = Number(data.x ?? nextCanvasX(state));
- const y = Number(data.y ?? 0);
- const gap = Number(data.gap ?? 40);
- return applyOps(data.items.map((item, index) => textNodeOp(item, item.x ?? (data.direction === "row" ? x + index * (340 + gap) : x), item.y ?? (data.direction === "row" ? y : y + index * (240 + gap)))));
- }
- if (name === "canvas_create_image_prompt_flow") return applyOps(generationFlowOps({ ...input, mode: "image" }, state));
- if (name === "canvas_create_config_node") {
- const x = Number(input.x ?? nextCanvasX(state));
- const y = Number(input.y ?? 0);
- const configId = `config-${crypto.randomUUID()}`;
- const mode = generationMode(input.mode);
- const prompt = String(input.prompt || "");
- return applyOps([configNodeOp(configId, input, x, y), ...(input.autoRun ? [runGenerationOp(configId, mode, prompt)] : [])]);
- }
- if (name === "canvas_create_generation_flow") return applyOps(generationFlowOps(input, state));
- if (name === "canvas_generate_text" || name === "canvas_generate_image" || name === "canvas_generate_video" || name === "canvas_generate_audio") {
- return applyOps(generationFlowOps({ ...input, mode: name.replace("canvas_generate_", ""), autoRun: true }, state));
- }
- if (name === "canvas_update_node") {
- const data = input as { id: string; patch?: Record; metadata?: Record };
- return applyOps([{ type: "update_node", id: data.id, patch: data.patch, metadata: data.metadata }]);
- }
- if (name === "canvas_update_node_text") {
- const data = input as { id: string; text: string; title?: string };
- return applyOps([{ type: "update_node", id: data.id, patch: { ...(data.title ? { title: data.title } : {}) }, metadata: { content: data.text, status: "success" } }]);
- }
- if (name === "canvas_move_nodes") {
- const data = input as { items: Array<{ id: string; x?: number; y?: number; dx?: number; dy?: number }> };
- return applyOps(data.items.map((item) => {
- const current = findNode(state, item.id);
- return { type: "update_node", id: item.id, patch: { position: { x: item.x ?? ((current?.position.x || 0) + (item.dx || 0)), y: item.y ?? ((current?.position.y || 0) + (item.dy || 0)) } } };
- }));
- }
- if (name === "canvas_resize_node") {
- const data = input as { id: string; width: number; height: number; freeResize?: boolean };
- return applyOps([{ type: "update_node", id: data.id, patch: { width: data.width, height: data.height }, metadata: data.freeResize === undefined ? undefined : { freeResize: data.freeResize } }]);
- }
- if (name === "canvas_delete_nodes") return applyOps([{ type: "delete_node", ids: (input as { ids: string[] }).ids }]);
- if (name === "canvas_connect_nodes") {
- const data = input as { connections: Array<{ fromNodeId: string; toNodeId: string }> };
- return applyOps(data.connections.map((connection) => ({ type: "connect_nodes", ...connection })));
- }
- if (name === "canvas_select_nodes") return applyOps([{ type: "select_nodes", ids: (input as { ids: string[] }).ids }]);
- if (name === "canvas_set_viewport") return applyOps([{ type: "set_viewport", viewport: (input as { viewport: unknown }).viewport }]);
- if (name === "canvas_run_generation") {
- const data = input as { nodeId: string; mode?: string; prompt?: string };
- return applyOps([runGenerationOp(data.nodeId, generationMode(data.mode), data.prompt)]);
- }
- throw new Error(`未知工具:${name}`);
-}
-
-/** 按最大边限制计算附件图片节点尺寸,并保持原始比例。 */
-export function fitAttachmentNodeSize(width: number, height: number) {
- const scale = Math.min(1, 640 / width, 640 / height);
- return { width: width * scale, height: height * scale };
-}
-
-/** 创建统一的批量画布操作请求。 */
-function applyOps(ops: unknown[]): CanvasToolRequest {
- return { name: "canvas_apply_ops", input: { ops } };
-}
-
-/** 创建文本节点操作。 */
-function textNodeOp(input: { id?: string; text?: string; title?: string; width?: number; height?: number }, x: number, y: number) {
- return { type: "add_node", id: input.id, nodeType: "text", title: input.title, position: { x, y }, width: input.width, height: input.height, metadata: { content: input.text || "", status: "success", fontSize: 14 } };
-}
-
-/** 创建生成配置节点操作。 */
-function configNodeOp(id: string, input: Record, x: number, y: number) {
- const mode = generationMode(input.mode);
- const prompt = String(input.prompt || "");
- return {
- type: "add_node",
- id,
- nodeType: "config",
- title: String(input.title || generationTitle(mode)),
- position: { x, y },
- width: typeof input.width === "number" ? input.width : undefined,
- height: typeof input.height === "number" ? input.height : undefined,
- metadata: cleanRecord({
- generationMode: mode,
- composerContent: prompt,
- prompt,
- status: "idle",
- model: input.model,
- size: input.size,
- quality: input.quality,
- count: input.count,
- seconds: input.seconds,
- vquality: input.vquality,
- generateAudio: input.generateAudio,
- watermark: input.watermark,
- audioVoice: input.audioVoice,
- audioFormat: input.audioFormat,
- audioSpeed: input.audioSpeed,
- audioInstructions: input.audioInstructions,
- }),
- };
-}
-
-/** 创建包含提示词、配置节点和引用连线的生成流程。 */
-function generationFlowOps(input: Record, state: CanvasSnapshot | null) {
- const mode = generationMode(input.mode);
- const prompt = String(input.prompt || "");
- const x = Number(input.x ?? nextCanvasX(state));
- const y = Number(input.y ?? 0);
- const textId = `text-${crypto.randomUUID()}`;
- const configId = `config-${crypto.randomUUID()}`;
- const referenceNodeIds = Array.isArray(input.referenceNodeIds) ? input.referenceNodeIds.filter((id): id is string => typeof id === "string") : [];
- const tokens = [`@[node:${textId}]`, ...referenceNodeIds.map((id) => `@[node:${id}]`)];
- return [
- textNodeOp({ id: textId, text: prompt, title: String(input.title || "提示词") }, x, y),
- configNodeOp(configId, { ...input, prompt: tokens.join("\n") }, x + 420, y),
- { type: "connect_nodes", fromNodeId: textId, toNodeId: configId },
- ...referenceNodeIds.map((fromNodeId) => ({ type: "connect_nodes", fromNodeId, toNodeId: configId })),
- { type: "select_nodes", ids: [configId] },
- ...(input.autoRun ? [runGenerationOp(configId, mode, tokens.join("\n"))] : []),
- ];
-}
-
-/** 创建触发节点生成的画布操作。 */
-function runGenerationOp(nodeId: string, mode: "text" | "image" | "video" | "audio", prompt?: string) {
- return { type: "run_generation", nodeId, mode, prompt };
-}
-
-/** 将未知生成模式归一为画布支持的模式。 */
-function generationMode(value: unknown): "text" | "image" | "video" | "audio" {
- return value === "text" || value === "video" || value === "audio" ? value : "image";
-}
-
-/** 获取生成模式对应的默认节点标题。 */
-function generationTitle(mode: "text" | "image" | "video" | "audio") {
- if (mode === "text") return "文本生成";
- if (mode === "video") return "视频生成";
- if (mode === "audio") return "音频生成";
- return "图片生成";
-}
-
-/** 按节点 ID 查找当前画布节点。 */
-function findNode(state: CanvasSnapshot | null, id: string): CanvasNode | undefined {
- return (state?.nodes || []).find((node) => node.id === id);
-}
-
-/** 移除对象中未设置的生成参数。 */
-function cleanRecord(value: Record) {
- return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined && item !== ""));
-}
diff --git a/canvas-agent/src/canvas/schemas.ts b/canvas-agent/src/canvas/schemas.ts
deleted file mode 100644
index 6267968..0000000
--- a/canvas-agent/src/canvas/schemas.ts
+++ /dev/null
@@ -1,163 +0,0 @@
-import { z } from "zod";
-
-const recordSchema = z.record(z.unknown());
-const positionSchema = z.object({ x: z.number(), y: z.number() });
-const viewportSchema = z.object({ x: z.number(), y: z.number(), k: z.number() });
-const nodeTypeSchema = z.enum(["image", "text", "config", "video", "audio"]);
-const generationModeSchema = z.enum(["text", "image", "video", "audio"]);
-
-/** Canvas Agent 对外提供的工具名称。 */
-export const toolNames = [
- "site_navigate",
- "canvas_list_projects",
- "canvas_get_state",
- "canvas_get_selection",
- "canvas_export_snapshot",
- "canvas_apply_ops",
- "canvas_create_node",
- "canvas_create_attachment_nodes",
- "canvas_create_text_node",
- "canvas_create_text_nodes",
- "canvas_create_config_node",
- "canvas_create_image_prompt_flow",
- "canvas_create_generation_flow",
- "canvas_generate_text",
- "canvas_generate_image",
- "canvas_generate_video",
- "canvas_generate_audio",
- "canvas_update_node",
- "canvas_update_node_text",
- "canvas_move_nodes",
- "canvas_resize_node",
- "canvas_delete_nodes",
- "canvas_connect_nodes",
- "canvas_select_nodes",
- "canvas_set_viewport",
- "canvas_run_generation",
- "generation_get_status",
- "workbench_image_get_config",
- "workbench_image_generate",
- "workbench_video_get_config",
- "workbench_video_generate",
- "prompts_search",
- "assets_list",
- "assets_add",
-] as const;
-export type ToolName = (typeof toolNames)[number];
-
-export const canvasOpSchema = z.discriminatedUnion("type", [
- z.object({ type: z.literal("add_node"), nodeType: nodeTypeSchema.optional(), id: z.string().optional(), title: z.string().optional(), x: z.number().optional(), y: z.number().optional(), width: z.number().optional(), height: z.number().optional(), position: positionSchema.optional(), metadata: recordSchema.optional() }).passthrough(),
- z.object({ type: z.literal("update_node"), id: z.string(), patch: recordSchema.optional(), metadata: recordSchema.optional() }).passthrough(),
- z.object({ type: z.literal("delete_node"), id: z.string().optional(), ids: z.array(z.string()).optional() }).passthrough(),
- z.object({ type: z.literal("delete_connections"), id: z.string().optional(), ids: z.array(z.string()).optional(), all: z.boolean().optional() }).passthrough(),
- z.object({ type: z.literal("connect_nodes"), id: z.string().optional(), fromNodeId: z.string(), toNodeId: z.string() }).passthrough(),
- z.object({ type: z.literal("set_viewport"), viewport: viewportSchema }).passthrough(),
- z.object({ type: z.literal("select_nodes"), ids: z.array(z.string()) }).passthrough(),
- z.object({ type: z.literal("run_generation"), nodeId: z.string(), mode: generationModeSchema.optional(), prompt: z.string().optional() }).passthrough(),
-]);
-
-const textNodeSchema = z.object({
- text: z.string(),
- title: z.string().optional(),
- x: z.number().optional(),
- y: z.number().optional(),
- width: z.number().optional(),
- height: z.number().optional(),
-});
-
-const generationOptionsSchema = z.object({
- model: z.string().optional(),
- size: z.string().optional(),
- quality: z.string().optional(),
- count: z.number().optional(),
- seconds: z.string().optional(),
- vquality: z.string().optional(),
- generateAudio: z.string().optional(),
- watermark: z.string().optional(),
- audioVoice: z.string().optional(),
- audioFormat: z.string().optional(),
- audioSpeed: z.string().optional(),
- audioInstructions: z.string().optional(),
-});
-
-const generationFlowSchema = z.object({
- prompt: z.string(),
- title: z.string().optional(),
- x: z.number().optional(),
- y: z.number().optional(),
- referenceNodeIds: z.array(z.string()).optional(),
-});
-
-export const toolInputSchemas = {
- site_navigate: z.object({ path: z.string() }),
- canvas_list_projects: z.object({ keyword: z.string().optional(), page: z.number().optional(), pageSize: z.number().optional() }),
- canvas_get_state: z.object({}).passthrough(),
- canvas_get_selection: z.object({}).passthrough(),
- canvas_export_snapshot: z.object({}).passthrough(),
- canvas_apply_ops: z.object({ ops: z.array(canvasOpSchema) }),
- canvas_create_node: z.object({ nodeType: nodeTypeSchema, title: z.string().optional(), x: z.number().optional(), y: z.number().optional(), width: z.number().optional(), height: z.number().optional(), metadata: recordSchema.optional() }),
- canvas_create_attachment_nodes: z.object({ attachmentIds: z.array(z.string()).min(1), x: z.number().optional(), y: z.number().optional(), gap: z.number().optional(), direction: z.enum(["row", "column"]).optional() }),
- canvas_create_text_node: z.object({ text: z.string().optional(), x: z.number().optional(), y: z.number().optional(), title: z.string().optional(), width: z.number().optional(), height: z.number().optional() }),
- canvas_create_text_nodes: z.object({ items: z.array(textNodeSchema).min(1), x: z.number().optional(), y: z.number().optional(), gap: z.number().optional(), direction: z.enum(["row", "column"]).optional() }),
- canvas_create_config_node: z.object({ prompt: z.string().optional(), mode: generationModeSchema.optional(), title: z.string().optional(), x: z.number().optional(), y: z.number().optional(), width: z.number().optional(), height: z.number().optional(), autoRun: z.boolean().optional() }).merge(generationOptionsSchema),
- canvas_create_image_prompt_flow: z.object({ prompt: z.string(), x: z.number().optional(), y: z.number().optional(), autoRun: z.boolean().optional() }).merge(generationOptionsSchema),
- canvas_create_generation_flow: generationFlowSchema.extend({ mode: generationModeSchema.optional(), autoRun: z.boolean().optional() }).merge(generationOptionsSchema),
- canvas_generate_text: generationFlowSchema.merge(generationOptionsSchema),
- canvas_generate_image: generationFlowSchema.merge(generationOptionsSchema),
- canvas_generate_video: generationFlowSchema.merge(generationOptionsSchema),
- canvas_generate_audio: generationFlowSchema.merge(generationOptionsSchema),
- canvas_update_node: z.object({ id: z.string(), patch: recordSchema.optional(), metadata: recordSchema.optional() }),
- canvas_update_node_text: z.object({ id: z.string(), text: z.string(), title: z.string().optional() }),
- canvas_move_nodes: z.object({ items: z.array(z.object({ id: z.string(), x: z.number().optional(), y: z.number().optional(), dx: z.number().optional(), dy: z.number().optional() })).min(1) }),
- canvas_resize_node: z.object({ id: z.string(), width: z.number(), height: z.number(), freeResize: z.boolean().optional() }),
- canvas_delete_nodes: z.object({ ids: z.array(z.string()).min(1) }),
- canvas_connect_nodes: z.object({ connections: z.array(z.object({ fromNodeId: z.string(), toNodeId: z.string() })).min(1) }),
- canvas_select_nodes: z.object({ ids: z.array(z.string()) }),
- canvas_set_viewport: z.object({ viewport: viewportSchema }),
- canvas_run_generation: z.object({ nodeId: z.string(), mode: generationModeSchema.optional(), prompt: z.string().optional() }),
- generation_get_status: z.object({ scope: z.enum(["all", "canvas", "image", "video"]).optional(), taskId: z.string().optional(), nodeIds: z.array(z.string()).optional(), limit: z.number().optional() }),
- workbench_image_get_config: z.object({}).passthrough(),
- workbench_image_generate: z.object({ prompt: z.string(), model: z.string().optional(), quality: z.string().optional(), size: z.string().optional(), count: z.number().optional(), run: z.boolean().optional() }),
- workbench_video_get_config: z.object({}).passthrough(),
- workbench_video_generate: z.object({ prompt: z.string(), model: z.string().optional(), size: z.string().optional(), seconds: z.string().optional(), resolution: z.string().optional(), generateAudio: z.boolean().optional(), watermark: z.boolean().optional(), run: z.boolean().optional() }),
- prompts_search: z.object({ keyword: z.string().optional(), category: z.string().optional(), tags: z.array(z.string()).optional(), page: z.number().optional(), pageSize: z.number().optional() }),
- assets_list: z.object({ kind: z.enum(["all", "text", "image", "video"]).optional(), keyword: z.string().optional(), page: z.number().optional(), pageSize: z.number().optional() }),
- assets_add: z.object({ kind: z.enum(["text", "image"]), title: z.string(), content: z.string().optional(), imageUrl: z.string().optional(), tags: z.array(z.string()).optional(), source: z.string().optional(), note: z.string().optional() }),
-} satisfies Record;
-
-export const toolDescriptions: Record = {
- site_navigate: "跳转网站页面。path 可为 / (首页)、/canvas (我的画布)、/canvas/:id (指定画布)、/image (生图工作台)、/video (视频创作台)、/prompts (提示词库)、/assets (我的素材)、/config (配置)。操作画布前若不在画布页,先用本工具打开画布。",
- canvas_list_projects: "列出用户全部画布(仅标题、创建/更新时间、节点数、连线数,不含完整数据),支持 keyword 搜索和 page/pageSize 分页。返回的 id 可配合 site_navigate 跳转到 /canvas/:id 打开对应画布。",
- canvas_get_state: "读取当前网页画布的节点、连线、选区和视口。",
- canvas_get_selection: "读取当前网页画布选中的节点。",
- canvas_export_snapshot: "导出当前画布快照,用于理解布局。",
- canvas_apply_ops: "批量操作当前网页画布。ops 支持 add_node、update_node、delete_node、delete_connections、connect_nodes、set_viewport、select_nodes、run_generation。",
- canvas_create_node: "创建任意类型节点:text、image、config、video、audio。适合创建占位图、媒体占位、配置节点或自定义 metadata 节点。",
- canvas_create_attachment_nodes: "把当前对话中用户上传的图片附件创建成真实画布图片节点。attachmentIds 使用本轮附件清单中的 ID;返回的节点 ID 可传给 canvas_create_generation_flow.referenceNodeIds 作为生成参考图。",
- canvas_create_text_node: "在当前画布创建单个文本节点。",
- canvas_create_text_nodes: "批量创建文本节点,适合生成标题、段落、脚本、说明等内容块。",
- canvas_create_config_node: "创建生成配置节点,可指定 text/image/video/audio 模式和生成参数,可选择立即触发生成。",
- canvas_create_image_prompt_flow: "创建提示词文本节点和图片生成配置节点,并自动连线,可选择立即触发生图。",
- canvas_create_generation_flow: "创建通用生成流程:提示词文本节点、生成配置节点、参考节点连线,可用于文案、生图、视频或音频。",
- canvas_generate_text: "创建通用文本生成流程并立即触发生成。",
- canvas_generate_image: "创建通用图片生成流程并立即触发生成。",
- canvas_generate_video: "创建通用视频生成流程并立即触发生成。",
- canvas_generate_audio: "创建通用音频生成流程并立即触发生成。",
- canvas_update_node: "更新节点基础字段或 metadata。",
- canvas_update_node_text: "更新文本节点内容和标题。",
- canvas_move_nodes: "移动一个或多个节点,支持绝对坐标或 dx/dy 偏移。",
- canvas_resize_node: "调整节点尺寸。",
- canvas_delete_nodes: "删除指定节点及相关连线。",
- canvas_connect_nodes: "批量连接节点。",
- canvas_select_nodes: "设置当前选中节点。",
- canvas_set_viewport: "调整画布视口。",
- canvas_run_generation: "触发指定节点生成,通常用于配置节点或文本/图片/视频/音频节点。",
- generation_get_status: "查询当前活动网页的生成任务状态。默认返回画布、生图工作台和视频工作台最近任务;可用 scope 过滤来源,用 taskId 查询工作台任务,用 nodeIds 查询画布节点。",
- workbench_image_get_config: "读取生图工作台的当前参数和可选项(可用模型、质量、尺寸/宽高比、张数范围),在调用 workbench_image_generate 前先了解可选值。",
- workbench_image_generate: "在生图工作台填入提示词并按需设置 model、quality、size(如 1:1 或 1024x1024)、count,run 默认 true 会自动点击生成按钮。会自动跳转到生图工作台。生成为异步过程,提交后返回 taskId,可用 generation_get_status 查询状态。",
- workbench_video_get_config: "读取视频创作台的当前参数和可选项(可用模型、尺寸/比例、时长、清晰度/分辨率、是否生成声音与水印)。",
- workbench_video_generate: "在视频创作台填入提示词并按需设置 model、size、seconds、resolution、generateAudio、watermark,run 默认 true 会自动点击生成按钮。会自动跳转到视频创作台。生成为异步过程,提交后返回 taskId,可用 generation_get_status 查询状态。",
- prompts_search: "搜索提示词库(第三方提示词合集),支持 keyword、category、tags 过滤和 page/pageSize 分页,返回标题、提示词、分类、标签、封面等。",
- assets_list: "列出用户「我的素材」,支持 kind(text/image/video)过滤、keyword 搜索和 page/pageSize 分页。为控制体积不返回图片/视频原始 data,仅返回封面与元信息。",
- assets_add: "向「我的素材」新增素材。kind=text 时用 content 传文本内容;kind=image 时用 imageUrl 传图片地址或 dataURL。可附带 title、tags、source、note。",
-};
diff --git a/canvas-agent/src/canvas/session.test.ts b/canvas-agent/src/canvas/session.test.ts
deleted file mode 100644
index 7c4dd55..0000000
--- a/canvas-agent/src/canvas/session.test.ts
+++ /dev/null
@@ -1,583 +0,0 @@
-import { EventEmitter } from "node:events";
-import type { ServerResponse } from "node:http";
-import assert from "node:assert/strict";
-import test from "node:test";
-
-import { CanvasSession } from "./session.js";
-
-test("MCP 读取当前激活网页的画布", async (t) => {
- const session = new CanvasSession();
- const first = connect(session, "first");
- const second = connect(session, "second");
- t.after(() => {
- first.close();
- second.close();
- });
- session.updateState(snapshot("canvas-first"), "first");
- session.updateState(snapshot("canvas-second"), "second");
-
- session.activateClient("first");
- assert.equal(field(await session.callTool("canvas_get_state", {}), "projectId"), "canvas-first");
-
- session.activateClient("second");
- assert.equal(field(await session.callTool("canvas_get_state", {}), "projectId"), "canvas-second");
-});
-
-test("按精确 clientId 读取画布快照,不受当前焦点影响", (t) => {
- const session = new CanvasSession();
- const first = connect(session, "first");
- const second = connect(session, "second");
- t.after(() => {
- first.close();
- second.close();
- });
- session.updateState(snapshot("canvas-first"), "first");
- session.updateState(snapshot("canvas-second"), "second");
- session.activateClient("second");
-
- assert.equal(field(session.canvasStateForClient("first"), "projectId"), "canvas-first");
- assert.equal(field(session.canvasStateForClient("second"), "projectId"), "canvas-second");
- assert.equal(session.canvasStateForClient("missing"), null);
- first.close();
- assert.equal(session.canvasStateForClient("first"), null);
-});
-
-test("画布写操作只发送给当前激活网页", async (t) => {
- const session = new CanvasSession();
- const first = connect(session, "first");
- const second = connect(session, "second");
- t.after(() => {
- first.close();
- second.close();
- });
- session.updateState(snapshot("canvas-first"), "first");
- session.updateState(snapshot("canvas-second"), "second");
- session.activateClient("second");
-
- const result = session.callTool("canvas_create_text_node", { text: "只写入第二个画布" });
- const call = second.event("tool_call");
- assert.equal(first.event("tool_call"), undefined);
- assert.equal(field(call, "name"), "canvas_apply_ops");
- session.resolveResult("second", { requestId: String(field(call, "requestId")), result: { ok: true } });
- assert.deepEqual(await result, { ok: true });
-});
-
-test("当前 turn 的图片附件可在发起标签页画布创建图片节点", async (t) => {
- const session = new CanvasSession();
- const first = connect(session, "first");
- t.after(() => first.close());
- const dataUrl = "data:image/png;base64,aW1hZ2U=";
- session.setTurnAttachments("first", [{ id: "attachment-1", name: "商品.png", type: "image/png", size: 5, width: 1200, height: 600, dataUrl }]);
- session.bindClient("first");
-
- const result = session.callTool("canvas_create_attachment_nodes", { attachmentIds: ["attachment-1"], x: 100, y: 200 });
- const call = first.event("tool_call");
- const input = field(call, "input") as Record;
- const nodes = input.nodes as Array>;
- assert.equal(field(call, "name"), "canvas_create_attachment_nodes");
- assert.equal(nodes.length, 1);
- assert.equal(nodes[0].attachmentId, "attachment-1");
- assert.equal(nodes[0].title, "商品.png");
- assert.deepEqual(nodes[0].position, { x: 100, y: 200 });
- assert.equal(nodes[0].width, 640);
- assert.equal(nodes[0].height, 320);
- assert.equal("dataUrl" in nodes[0], false);
- assert.equal(session.getTurnAttachment("first", "attachment-1").dataUrl, dataUrl);
-
- session.resolveResult("first", { requestId: String(field(call, "requestId")), result: { ok: true } });
- const created = (await result) as { nodes: Array<{ id: string; attachmentId: string; title: string }> };
- assert.equal(created.nodes[0].id, nodes[0].id);
- assert.equal(created.nodes[0].attachmentId, "attachment-1");
- session.clearTurnAttachments("first");
- assert.throws(() => session.getTurnAttachment("first", "attachment-1"), /找不到/);
-});
-
-test("图片附件只允许发起 turn 的标签页读取和落入画布", async (t) => {
- const session = new CanvasSession();
- const first = connect(session, "first");
- const second = connect(session, "second");
- t.after(() => {
- first.close();
- second.close();
- });
- session.setTurnAttachments("first", [{ id: "attachment-1", name: "商品.png", type: "image/png", dataUrl: "data:image/png;base64,aW1hZ2U=" }]);
- session.bindClient("second");
-
- await assert.rejects(session.callTool("canvas_create_attachment_nodes", { attachmentIds: ["attachment-1"] }), /发起标签页/);
- assert.throws(() => session.getTurnAttachment("second", "attachment-1"), /发起标签页/);
- assert.equal(first.event("tool_call"), undefined);
- assert.equal(second.event("tool_call"), undefined);
-});
-
-test("tool result is accepted only from the request client", async (t) => {
- const session = new CanvasSession();
- const first = connect(session, "first");
- const second = connect(session, "second");
- t.after(() => {
- first.close();
- second.close();
- });
- session.activateClient("first");
-
- const result = session.callTool("canvas_create_text_node", { text: "first only" });
- const call = first.event("tool_call");
- const requestId = String(field(call, "requestId"));
-
- assert.equal(session.resolveResult("second", { requestId, result: { client: "second" } }), false);
- assert.equal(session.resolveResult("first", { requestId, result: { client: "first" } }), true);
- assert.deepEqual(await result, { client: "first" });
-});
-
-test("生成状态查询由当前激活网页返回", async (t) => {
- const session = new CanvasSession();
- const first = connect(session, "first");
- const second = connect(session, "second");
- t.after(() => {
- first.close();
- second.close();
- });
- session.activateClient("second");
-
- const result = session.callTool("generation_get_status", { scope: "all" });
- const call = second.event("tool_call");
- assert.equal(first.event("tool_call"), undefined);
- assert.equal(field(call, "name"), "generation_get_status");
- session.resolveResult("second", { requestId: String(field(call, "requestId")), result: { total: 1, tasks: [{ id: "image-1", status: "running" }] } });
- assert.deepEqual(await result, { total: 1, tasks: [{ id: "image-1", status: "running" }] });
-});
-
-test("活动网页关闭后回退到仍连接的画布", async (t) => {
- const session = new CanvasSession();
- const first = connect(session, "first");
- const second = connect(session, "second");
- t.after(() => {
- first.close();
- second.close();
- });
- session.updateState(snapshot("canvas-first"), "first");
- session.updateState(snapshot("canvas-second"), "second");
- session.activateClient("second");
- second.close();
-
- assert.equal(field(await session.callTool("canvas_get_state", {}), "projectId"), "canvas-first");
-});
-
-test("closing the active client falls back to the most recently focused client", async (t) => {
- const session = new CanvasSession();
- const first = connect(session, "first");
- const second = connect(session, "second");
- const third = connect(session, "third");
- t.after(() => {
- first.close();
- second.close();
- third.close();
- });
- session.updateState(snapshot("canvas-first"), "first");
- session.updateState(snapshot("canvas-second"), "second");
- session.updateState(snapshot("canvas-third"), "third");
- session.activateClient("third");
- session.activateClient("second");
- second.close();
-
- assert.equal(field(await session.callTool("canvas_get_state", {}), "projectId"), "canvas-third");
-});
-
-test("closing a client rejects its pending tool requests", async () => {
- const session = new CanvasSession();
- const first = connect(session, "first");
- const result = session.callTool("canvas_create_text_node", { text: "pending" });
- const call = first.event("tool_call");
- const requestId = String(field(call, "requestId"));
- first.close();
-
- const outcome = await Promise.race([
- result.then(() => "resolved", (error) => error instanceof Error ? error.message : String(error)),
- new Promise((resolve) => setTimeout(() => resolve("pending"), 20)),
- ]);
- if (outcome === "pending") session.resolveResult("first", { requestId, result: null });
- assert.match(outcome, /断开/);
-});
-
-test("shared thread events are broadcast with the active thread id", (t) => {
- const session = new CanvasSession();
- const first = connect(session, "first");
- const second = connect(session, "second");
- t.after(() => {
- first.close();
- second.close();
- });
-
- session.emitThread("workspace_changed", "thread-2", { activeThreadId: "thread-2" });
-
- assert.deepEqual(first.event("workspace_changed"), { activeThreadId: "thread-2", threadId: "thread-2" });
- assert.deepEqual(second.event("workspace_changed"), { activeThreadId: "thread-2", threadId: "thread-2" });
-});
-
-test("new clients receive the current Codex state and later updates", (t) => {
- const session = new CanvasSession("thread-2");
- session.setCodexState({ busy: true, threadId: "thread-2", turnId: "turn-1" });
- session.trackCodexEvent("codex_approval", { requestId: "approval-1", threadId: "thread-2" });
- const client = connect(session, "first", "thread-2");
- t.after(() => client.close());
-
- const hello = client.event("hello");
- assert.equal(field(hello, "protocolVersion"), 6);
- assert.deepEqual(field(hello, "workspace"), { activeThreadId: "thread-2" });
- assert.deepEqual(field(hello, "conversation"), { revision: 1, conversationId: "thread-2", threadId: "thread-2", status: "ready", mcpStatuses: {} });
- assert.deepEqual(field(hello, "codex"), { busy: true, threadId: "thread-2", turnId: "turn-1" });
- assert.deepEqual(field(hello, "pendingApprovals"), [{ requestId: "approval-1", threadId: "thread-2" }]);
-
- session.trackCodexEvent("codex_approval_resolved", { requestId: "approval-1" });
- assert.deepEqual(session.codexPendingApprovals, []);
- session.trackCodexEvent("codex_approval", { requestId: "approval-2", threadId: "thread-2" });
- session.setCodexState({ busy: false });
- assert.deepEqual(session.codexPendingApprovals, [{ requestId: "approval-2", threadId: "thread-2" }]);
- session.trackCodexEvent("agent_error", { message: "app-server exited" });
- assert.deepEqual(session.codexPendingApprovals, []);
- assert.deepEqual(client.event("codex_state"), { busy: false, threadId: "thread-2", turnId: "turn-1" });
-});
-
-test("对话 revision 单调递增且 MCP 全部进入终态前保持 preparing", () => {
- const session = new CanvasSession();
- const revisions = [session.conversationStateSnapshot.revision];
-
- revisions.push(session.beginConversation({ sourceClientId: "first" }).revision);
- revisions.push(session.updateConversationMcp("late-service", "starting").revision);
- revisions.push(session.completeConversationMcpInventory([{ name: "infinite-canvas", authStatus: "unsupported" }]).revision);
- const pending = session.completeConversationPreparation("thread-1");
- revisions.push(pending.revision);
- assert.equal(pending.status, "preparing");
-
- const ready = session.updateConversationMcp("late-service", "ready");
- revisions.push(ready.revision);
- assert.equal(ready.status, "ready");
- assert.equal(ready.threadId, "thread-1");
- revisions.slice(1).forEach((revision, index) => assert.ok(revision > revisions[index]));
-});
-
-test("可选 MCP 失败进入 warning,画布 MCP 失败进入 failed", () => {
- const optionalFailure = new CanvasSession();
- optionalFailure.beginConversation();
- optionalFailure.completeConversationMcpInventory([
- { name: "infinite-canvas", authStatus: "unsupported" },
- { name: "notion", authStatus: "notLoggedIn" },
- ]);
- const warning = optionalFailure.completeConversationPreparation("thread-1");
- assert.equal(warning.status, "warning");
- assert.equal(warning.mcpStatuses.notion.status, "failed");
-
- const requiredFailure = new CanvasSession();
- requiredFailure.beginConversation();
- requiredFailure.completeConversationMcpInventory([{ name: "infinite-canvas", authStatus: "notLoggedIn" }]);
- const failed = requiredFailure.completeConversationPreparation("thread-2");
- assert.equal(failed.status, "failed");
- assert.match(failed.error || "", /Infinite Canvas MCP/);
-
- const requiredMissing = new CanvasSession();
- requiredMissing.beginConversation();
- requiredMissing.completeConversationMcpInventory([{ name: "notion", authStatus: "unsupported" }]);
- const missing = requiredMissing.completeConversationPreparation("thread-3");
- assert.equal(missing.status, "failed");
- assert.match(missing.error || "", /Infinite Canvas MCP/);
-});
-
-test("Codex 写操作在多窗口之间互斥且不能与运行 turn 并发", () => {
- const session = new CanvasSession();
- assert.equal(session.beginCodexMutation(), true);
- assert.equal(session.beginCodexMutation(), false);
- session.endCodexMutation();
- assert.equal(session.beginCodexMutation(), true);
- session.endCodexMutation();
- session.setCodexState({ busy: true, threadId: "thread-1", turnId: "turn-1" });
- assert.equal(session.beginCodexMutation(), false);
-});
-
-test("Skill draft generation broadcasts shared busy state and restores the previous thread", (t) => {
- const session = new CanvasSession();
- const first = connect(session, "first");
- const second = connect(session, "second");
- t.after(() => {
- first.close();
- second.close();
- });
- session.setCodexState({ threadId: "thread-1", turnId: "turn-previous" });
- const previous = session.codexStateSnapshot;
-
- assert.equal(session.beginCodexMutation(), true);
- session.setCodexState({ busy: true, threadId: previous.threadId, turnId: "" }, { preserveReplay: true });
- assert.deepEqual(first.events("codex_state").at(-1), { busy: true, threadId: "thread-1", turnId: "" });
- assert.deepEqual(second.events("codex_state").at(-1), { busy: true, threadId: "thread-1", turnId: "" });
- assert.equal(session.beginCodexMutation(), false);
-
- session.setCodexState(previous, { preserveReplay: true });
- session.endCodexMutation();
- assert.deepEqual(first.events("codex_state").at(-1), previous);
- assert.deepEqual(second.events("codex_state").at(-1), previous);
- assert.equal(session.beginCodexMutation(), true);
- session.endCodexMutation();
-});
-
-test("Skill draft busy state preserves the previous turn replay until history acknowledges it", (t) => {
- const session = new CanvasSession();
- session.setCodexState({ busy: true, threadId: "thread-1", turnId: "turn-1" });
- session.emitThread("agent_event", "thread-1", { turnId: "turn-1", type: "item.updated", item: { id: "assistant-1", type: "agent_message", text: "回答" } });
- session.setCodexState({ busy: false });
- const previous = session.codexStateSnapshot;
-
- session.setCodexState({ busy: true, threadId: "thread-1", turnId: "" }, { preserveReplay: true });
- session.setCodexState(previous, { preserveReplay: true });
-
- const client = connect(session, "first", "thread-1");
- t.after(() => client.close());
- assert.equal(client.events("agent_event").length, 1);
-});
-
-test("a bound client remains the tool target while focus changes", async (t) => {
- const session = new CanvasSession();
- const first = connect(session, "first");
- const second = connect(session, "second");
- t.after(() => {
- first.close();
- second.close();
- });
- session.updateState(snapshot("canvas-first"), "first");
- session.updateState(snapshot("canvas-second"), "second");
- session.bindClient("first");
- session.activateClient("second");
-
- assert.equal(field(await session.callTool("canvas_get_state", {}), "projectId"), "canvas-first");
- const result = session.callTool("canvas_create_text_node", { text: "bound" });
- const call = first.event("tool_call");
- assert.equal(second.event("tool_call"), undefined);
- session.resolveResult("first", { requestId: String(field(call, "requestId")), result: { ok: true } });
- assert.deepEqual(await result, { ok: true });
-
- session.releaseClient("first");
- assert.equal(field(await session.callTool("canvas_get_state", {}), "projectId"), "canvas-second");
-});
-
-test("a disconnected bound client never falls back and can resume with the same client id", async (t) => {
- const session = new CanvasSession();
- const first = connect(session, "first");
- const second = connect(session, "second");
- t.after(() => {
- first.close();
- second.close();
- });
- session.updateState(snapshot("canvas-first"), "first");
- session.updateState(snapshot("canvas-second"), "second");
- session.bindClient("first");
- session.activateClient("second");
- first.close();
-
- await assert.rejects(session.callTool("canvas_get_state", {}), /当前没有已连接画布/);
- assert.equal(second.event("tool_call"), undefined);
-
- const reconnected = connect(session, "first");
- t.after(() => reconnected.close());
- session.updateState(snapshot("canvas-first-reconnected"), "first");
- assert.equal(field(await session.callTool("canvas_get_state", {}), "projectId"), "canvas-first-reconnected");
-
- const result = session.callTool("canvas_create_text_node", { text: "reconnected" });
- const call = reconnected.event("tool_call");
- assert.equal(second.event("tool_call"), undefined);
- session.resolveResult("first", { requestId: String(field(call, "requestId")), result: { ok: true } });
- assert.deepEqual(await result, { ok: true });
-});
-
-test("新连接会回放当前运行 turn 的最新事件快照", (t) => {
- const session = new CanvasSession();
- session.setCodexState({ busy: true, threadId: "thread-1", turnId: "turn-1" });
- session.emitThread("chat_message", "thread-1", { turnId: "turn-1", message: { id: "thread-1:turn-1:synthetic:user", itemId: "synthetic:user", clientMessageId: "local-message-1", role: "user", text: "问题" } });
- session.emitThread("agent_event", "thread-1", { turnId: "turn-1", type: "item.updated", item: { id: "reasoning-1", type: "reasoning", text: "分析中" } });
-
- const client = connect(session, "first", "thread-1");
- t.after(() => client.close());
-
- assert.deepEqual(client.events("chat_message"), [{ threadId: "thread-1", turnId: "turn-1", message: { id: "thread-1:turn-1:synthetic:user", itemId: "synthetic:user", clientMessageId: "local-message-1", role: "user", text: "问题" }, replayed: true }]);
- assert.deepEqual(client.events("agent_event"), [{ threadId: "thread-1", turnId: "turn-1", type: "item.updated", item: { id: "reasoning-1", type: "reasoning", text: "分析中" }, replayed: true }]);
-});
-
-test("同一 item 的多次增量只回放最新内容", (t) => {
- const session = new CanvasSession();
- session.setCodexState({ busy: true, threadId: "thread-1", turnId: "turn-1" });
- session.emitThread("agent_event", "thread-1", { turnId: "turn-1", type: "item.updated", item: { id: "assistant-1", type: "agent_message", text: "第一段" } });
- session.emitThread("agent_event", "thread-1", { turnId: "turn-1", type: "item.updated", item: { id: "assistant-1", type: "agent_message", text: "第一段和第二段" } });
-
- const client = connect(session, "first", "thread-1");
- t.after(() => client.close());
-
- const events = client.events("agent_event") as Array>;
- assert.equal(events.length, 1);
- assert.equal(field(field(events[0], "item"), "text"), "第一段和第二段");
-});
-
-test("增量事件重放时转换为完整文本快照", (t) => {
- const session = new CanvasSession();
- session.setCodexState({ busy: true, threadId: "thread-1", turnId: "turn-1" });
- session.emitThread("agent_event", "thread-1", { turnId: "turn-1", type: "item.updated", item: { id: "assistant-1", type: "agent_message", delta: "第一段" } });
- session.emitThread("agent_event", "thread-1", { turnId: "turn-1", type: "item.updated", item: { id: "assistant-1", type: "agent_message", delta: "第二段" } });
-
- const client = connect(session, "first", "thread-1");
- t.after(() => client.close());
- const [event] = client.events("agent_event") as Array>;
- assert.deepEqual(field(event, "item"), { id: "assistant-1", type: "agent_message", text: "第一段第二段" });
-});
-
-test("并行 item 更新后重放仍保留开始顺序和命令字段", (t) => {
- const session = new CanvasSession();
- session.setCodexState({ busy: true, threadId: "thread-1", turnId: "turn-1" });
- session.emitThread("agent_event", "thread-1", { turnId: "turn-1", type: "item.started", item: { id: "first", type: "command_execution", command: "first", cwd: "D:\\infinite-canvas" } });
- session.emitThread("agent_event", "thread-1", { turnId: "turn-1", type: "item.started", item: { id: "second", type: "command_execution", command: "second" } });
- session.emitThread("agent_event", "thread-1", { turnId: "turn-1", type: "item.updated", item: { id: "first", type: "command_execution", delta: "output" } });
-
- const client = connect(session, "first", "thread-1");
- t.after(() => client.close());
- const events = client.events("agent_event") as Array>;
-
- assert.deepEqual(events.map((event) => field(field(event, "item"), "id")), ["first", "second"]);
- assert.deepEqual(field(events[0], "item"), { id: "first", type: "command_execution", command: "first", cwd: "D:\\infinite-canvas", text: "output" });
-});
-
-test("长 turn 不会淘汰仍在更新的活动条目快照", (t) => {
- const session = new CanvasSession();
- session.setCodexState({ busy: true, threadId: "thread-1", turnId: "turn-1" });
- session.emitThread("agent_event", "thread-1", { turnId: "turn-1", type: "item.started", item: { id: "active-item", type: "command_execution", command: "long-running" } });
- for (let index = 0; index < 260; index += 1) {
- session.emitThread("agent_event", "thread-1", { turnId: "turn-1", type: "item.completed", item: { id: `completed-${index}`, type: "command_execution", command: `command-${index}` } });
- }
-
- const client = connect(session, "first", "thread-1");
- t.after(() => client.close());
- assert.equal((client.events("agent_event") as Array>).some((event) => field(field(event, "item"), "id") === "active-item"), true);
-});
-
-test("内置生图事件会回放展示但标记为不可重复执行", (t) => {
- const session = new CanvasSession();
- session.setCodexState({ busy: true, threadId: "thread-1", turnId: "turn-1" });
- session.emitThread("agent_event", "thread-1", { turnId: "turn-1", type: "item.completed", item: { id: "image-1", type: "image_generation", savedPath: "D:/image.png" } });
-
- const client = connect(session, "first", "thread-1");
- t.after(() => client.close());
-
- assert.deepEqual(client.events("agent_event"), [{
- threadId: "thread-1",
- turnId: "turn-1",
- type: "item.completed",
- item: { id: "image-1", type: "image_generation", savedPath: "D:/image.png" },
- replayed: true,
- }]);
-});
-
-test("turn 结束后保留实时快照,直到网页确认权威历史", (t) => {
- const session = new CanvasSession();
- session.setCodexState({ busy: true, threadId: "thread-1", turnId: "turn-1" });
- session.emitThread("agent_event", "thread-1", { turnId: "turn-1", type: "item.updated", item: { id: "assistant-1", type: "agent_message", text: "回答" } });
- session.setCodexState({ busy: false });
-
- const client = connect(session, "first", "thread-1");
- t.after(() => client.close());
- assert.equal(client.events("agent_event").length, 1);
-
- session.acknowledgeCodexHistory("thread-1", ["turn-1"]);
- const next = connect(session, "second", "thread-1");
- t.after(() => next.close());
- assert.deepEqual(next.events("agent_event"), []);
-});
-
-test("开始下一 turn 时只回放当前 turn 的事件", (t) => {
- const session = new CanvasSession();
- session.setCodexState({ busy: true, threadId: "thread-1", turnId: "turn-1" });
- session.emitThread("agent_event", "thread-1", { turnId: "turn-1", type: "item.updated", item: { id: "assistant", type: "agent_message", text: "第一轮" } });
- session.setCodexState({ busy: false, turnId: "turn-1" });
- session.setCodexState({ busy: true, turnId: "turn-2" });
- session.emitThread("agent_event", "thread-1", { turnId: "turn-2", type: "item.updated", item: { id: "assistant", type: "agent_message", text: "第二轮" } });
-
- const client = connect(session, "first", "thread-1");
- t.after(() => client.close());
- const events = client.events("agent_event") as Array>;
- assert.equal(events.length, 1);
- assert.deepEqual(events.map((event) => field(field(event, "item"), "text")), ["第二轮"]);
-});
-
-test("同一用户消息从 pending 绑定 turn 后只回放最终版本", (t) => {
- const session = new CanvasSession();
- session.setCodexState({ busy: true, threadId: "thread-1", turnId: "" });
- session.emitThread("chat_message", "thread-1", { message: { id: "pending", itemId: "synthetic:user", clientMessageId: "message-1", role: "user", text: "问题" } });
- session.setCodexState({ turnId: "turn-1" });
- session.emitThread("chat_message", "thread-1", { turnId: "turn-1", message: { id: "final", itemId: "synthetic:user", clientMessageId: "message-1", role: "user", text: "问题" } });
-
- const client = connect(session, "first", "thread-1");
- t.after(() => client.close());
- assert.deepEqual(client.events("chat_message"), [{
- threadId: "thread-1",
- turnId: "turn-1",
- message: { id: "final", itemId: "synthetic:user", clientMessageId: "message-1", role: "user", text: "问题" },
- replayed: true,
- }]);
-});
-
-test("切换活动线程会清除上一线程的实时快照", (t) => {
- const session = new CanvasSession();
- session.setCodexState({ busy: true, threadId: "thread-1", turnId: "turn-1" });
- session.emitThread("agent_event", "thread-1", { turnId: "turn-1", type: "item.updated", item: { id: "assistant-1", type: "agent_message", text: "回答" } });
- session.setCodexState({ busy: false, threadId: "thread-1", turnId: "turn-1" });
- session.setCodexState({ threadId: "thread-2", turnId: "" });
- session.setCodexState({ threadId: "thread-1", turnId: "" });
-
- const client = connect(session, "first", "thread-1");
- t.after(() => client.close());
- assert.deepEqual(client.events("agent_event"), []);
-});
-
-/** 创建用于测试的画布 SSE 连接。 */
-function connect(session: CanvasSession, clientId: string, activeThreadId = "") {
- const response = new FakeSseResponse();
- session.openEvents(new URL(`http://127.0.0.1/events?clientId=${clientId}`), response as unknown as ServerResponse, activeThreadId);
- return response;
-}
-
-/** 创建最小画布快照。 */
-function snapshot(projectId: string) {
- return { projectId, title: projectId, nodes: [], connections: [], selectedNodeIds: [], viewport: { x: 0, y: 0, k: 1 } };
-}
-
-/** 安全读取测试对象字段。 */
-function field(value: unknown, key: string) {
- return value && typeof value === "object" ? (value as Record)[key] : undefined;
-}
-
-/** 模拟 Node SSE 响应并提供事件读取能力。 */
-class FakeSseResponse extends EventEmitter {
- private chunks: string[] = [];
-
- /** 模拟写入响应头。 */
- writeHead() {
- return this;
- }
-
- /** 保存写入的 SSE 文本块。 */
- write(chunk: string) {
- this.chunks.push(chunk);
- return true;
- }
-
- /** 读取指定类型的首个 SSE 事件数据。 */
- event(type: string) {
- return this.events(type)[0];
- }
-
- /** 读取指定类型的全部 SSE 事件数据。 */
- events(type: string) {
- return this.chunks.flatMap((chunk) => {
- if (!chunk.startsWith(`event: ${type}\n`)) return [];
- const data = chunk.split("\n").find((line) => line.startsWith("data: "))?.slice(6);
- return data ? [JSON.parse(data) as unknown] : [];
- });
- }
-
- /** 触发连接关闭事件。 */
- close() {
- this.emit("close");
- }
-}
diff --git a/canvas-agent/src/canvas/session.ts b/canvas-agent/src/canvas/session.ts
deleted file mode 100644
index e1376a2..0000000
--- a/canvas-agent/src/canvas/session.ts
+++ /dev/null
@@ -1,540 +0,0 @@
-import crypto from "node:crypto";
-import type { ServerResponse } from "node:http";
-
-import type { AgentAttachment } from "../agent/types.js";
-import { logger } from "../utils/logger.js";
-import { buildCanvasToolRequest, fitAttachmentNodeSize } from "./operations.js";
-import type { ToolName } from "./schemas.js";
-import { compactCanvasState, compactNode, isToolName, nextCanvasX, parseToolInput } from "./tools.js";
-import type { CanvasSnapshot } from "./types.js";
-
-type PendingRequest = { clientId: string; resolve: (value: unknown) => void; reject: (error: Error) => void };
-type TurnAttachment = { clientId: string; id: string; name: string; type: string; size: number; width: number; height: number; dataUrl: string };
-type ReplayEvent = { type: string; payload: Record };
-export type CodexState = { busy: boolean; threadId: string; turnId: string };
-export type McpStartupState = "starting" | "ready" | "failed" | "cancelled";
-export type ConversationState = {
- revision: number;
- conversationId: string;
- threadId: string;
- status: "idle" | "preparing" | "ready" | "warning" | "running" | "failed";
- mcpStatuses: Record;
- sourceClientId?: string;
- error?: string;
-};
-type McpInventoryItem = { name: string; authStatus?: string };
-export const AGENT_PROTOCOL_VERSION = 6;
-
-const SITE_TOOLS = new Set([
- "site_navigate",
- "canvas_list_projects",
- "workbench_image_get_config",
- "workbench_image_generate",
- "workbench_video_get_config",
- "workbench_video_generate",
- "prompts_search",
- "assets_list",
- "assets_add",
- "generation_get_status",
-]);
-
-/** 管理网页画布连接、状态、附件和工具请求。 */
-export class CanvasSession {
- private clients = new Map();
- private clientFocusOrder = new Map();
- private pending = new Map();
- private pendingApprovals = new Map>();
- private canvasStates = new Map();
- private turnAttachments = new Map();
- private codexReplayEvents = new Map();
- private codexReplayActiveItems = new Set();
- private codexMutationBusy = false;
- private activeClientId = "";
- private boundClientId = "";
- private focusSequence = 0;
- private codexState: CodexState = { busy: false, threadId: "", turnId: "" };
- private conversationState: ConversationState;
- private conversationInventoryComplete = false;
- private preparedConversationThreadId = "";
-
- constructor(activeThreadId = "") {
- this.conversationState = {
- revision: 1,
- conversationId: activeThreadId || crypto.randomUUID(),
- threadId: activeThreadId,
- status: activeThreadId ? "ready" : "idle",
- mcpStatuses: {},
- };
- }
-
- /** 获取当前目标网页的画布状态。 */
- private get canvasState() {
- return this.clients.has(this.targetClientId) ? this.canvasStates.get(this.targetClientId) || null : null;
- }
-
- /** 获取当前 turn 绑定或最近激活的网页客户端。 */
- private get targetClientId() {
- return this.boundClientId || this.activeClientId;
- }
-
- /** 返回 Canvas Agent 当前连接状态。 */
- health() {
- return { ok: true, protocolVersion: AGENT_PROTOCOL_VERSION, hasCanvas: Boolean(this.canvasState), clients: this.clients.size, codexBusy: this.codexState.busy, conversation: this.conversationStateSnapshot };
- }
-
- /** 返回 Codex 是否正在执行任务。 */
- get codexBusy() {
- return this.codexState.busy;
- }
-
- get codexThreadId() {
- return this.codexState.threadId;
- }
-
- /** Return a copy that callers can restore after a temporary Codex operation. */
- get codexStateSnapshot(): CodexState {
- return { ...this.codexState };
- }
-
- /** 返回站点级对话的权威快照。 */
- get conversationStateSnapshot(): ConversationState {
- return { ...this.conversationState, mcpStatuses: { ...this.conversationState.mcpStatuses } };
- }
-
- /** 原子开始一次新建或恢复对话流程。 */
- beginConversation(options: { threadId?: string; conversationId?: string; sourceClientId?: string } = {}) {
- this.conversationInventoryComplete = false;
- this.preparedConversationThreadId = "";
- return this.updateConversation({
- conversationId: options.conversationId || options.threadId || crypto.randomUUID(),
- threadId: options.threadId || "",
- status: "preparing",
- mcpStatuses: {},
- sourceClientId: options.sourceClientId,
- error: undefined,
- });
- }
-
- /** 记录预热阶段单个 MCP 服务的启动状态。 */
- updateConversationMcp(name: string, status: McpStartupState, error?: string | null, failureReason?: string | null) {
- if (!name || this.conversationState.status !== "preparing") return this.conversationStateSnapshot;
- const snapshot = this.updateConversation({
- mcpStatuses: { ...this.conversationState.mcpStatuses, [name]: { status, error, failureReason } },
- });
- return this.conversationInventoryComplete && this.preparedConversationThreadId
- ? this.completeConversationPreparation(this.preparedConversationThreadId)
- : snapshot;
- }
-
- /** 用 app-server 的完整 MCP 清单补齐未发送逐项通知的服务。 */
- completeConversationMcpInventory(services: McpInventoryItem[]) {
- if (this.conversationState.status !== "preparing") return this.conversationStateSnapshot;
- const mcpStatuses = { ...this.conversationState.mcpStatuses };
- services.filter((item) => item.name).forEach((item) => {
- const current = mcpStatuses[item.name];
- if (current?.status === "failed" || current?.status === "cancelled") return;
- mcpStatuses[item.name] = item.authStatus === "notLoggedIn"
- ? { status: "failed", error: "MCP 服务未登录", failureReason: "reauthenticationRequired" }
- : { status: "ready" };
- });
- this.conversationInventoryComplete = true;
- return this.updateConversation({ mcpStatuses });
- }
-
- /** MCP 清单读取结束后提交线程和最终可发送状态。 */
- completeConversationPreparation(threadId: string) {
- this.preparedConversationThreadId = threadId;
- const statuses = this.conversationState.mcpStatuses;
- const hasPending = !this.conversationInventoryComplete || Object.values(statuses).some((item) => item.status === "starting");
- const requiredFailure = statuses["infinite-canvas"]?.status !== "ready";
- const hasFailure = Object.values(statuses).some((item) => item.status === "failed" || item.status === "cancelled");
- const requiredFailureDetail = statuses["infinite-canvas"]?.error;
- return this.updateConversation({
- threadId,
- status: hasPending ? "preparing" : requiredFailure ? "failed" : hasFailure ? "warning" : "ready",
- error: requiredFailure ? `Infinite Canvas MCP 初始化失败${requiredFailureDetail ? `:${requiredFailureDetail}` : ""}` : undefined,
- });
- }
-
- /** 将创建线程或读取 MCP 清单的失败保存为不可发送状态。 */
- failConversationPreparation(error: string) {
- this.conversationInventoryComplete = false;
- this.preparedConversationThreadId = "";
- return this.updateConversation({ status: "failed", error });
- }
-
- /** 切换到无需重新预热的既有状态,例如删除当前对话后的空状态。 */
- activateConversation(threadId: string, sourceClientId?: string) {
- this.conversationInventoryComplete = false;
- this.preparedConversationThreadId = "";
- return this.updateConversation({
- conversationId: threadId || crypto.randomUUID(),
- threadId,
- status: threadId ? "ready" : "idle",
- mcpStatuses: {},
- sourceClientId,
- error: undefined,
- });
- }
-
- markConversationRunning(threadId: string) {
- if (!threadId || threadId !== this.conversationState.threadId || !["ready", "warning"].includes(this.conversationState.status)) return this.conversationStateSnapshot;
- return this.updateConversation({ status: "running" });
- }
-
- finishConversationRun(threadId: string) {
- if (!threadId || threadId !== this.conversationState.threadId) return this.conversationStateSnapshot;
- const hasFailure = Object.values(this.conversationState.mcpStatuses).some((item) => item.status === "failed" || item.status === "cancelled");
- return this.updateConversation({ status: hasFailure ? "warning" : "ready" });
- }
-
- /** 判断网页客户端是否仍连接到当前 Agent。 */
- hasClient(clientId: string) {
- return this.clients.has(clientId);
- }
-
- /** 读取指定网页上报的画布,避免提炼时受最近焦点或其他标签页影响。 */
- canvasStateForClient(clientId: string) {
- return this.clients.has(clientId) ? this.canvasStates.get(clientId) || null : null;
- }
-
- /** 原子取得 Codex 写操作权限,避免多个网页并发切换或修改会话。 */
- beginCodexMutation() {
- if (this.codexState.busy || this.codexMutationBusy) return false;
- this.codexMutationBusy = true;
- return true;
- }
-
- /** 释放 Codex 写操作权限。 */
- endCodexMutation() {
- this.codexMutationBusy = false;
- }
-
- /** 返回当前 Codex turn 的线程、turn 和发起网页。 */
- get codexEventScope() {
- return {
- threadId: this.codexState.threadId,
- turnId: this.codexState.busy ? this.codexState.turnId : "",
- sourceClientId: this.codexState.busy ? this.boundClientId : "",
- };
- }
-
- /** 返回刷新后仍需展示的 Codex 权限请求。 */
- get codexPendingApprovals() {
- return [...this.pendingApprovals.values()];
- }
-
- /** 跟踪需要跨页面重连恢复的 Codex 权限请求。 */
- trackCodexEvent(type: string, payload: Record) {
- const requestId = String(payload.requestId || "");
- if (type === "codex_approval" && requestId) this.pendingApprovals.set(requestId, payload);
- if (type === "codex_approval_resolved" && requestId) this.pendingApprovals.delete(requestId);
- if (type === "agent_error") this.pendingApprovals.clear();
- }
-
- /** 更新并广播 Codex 运行状态;静默后台活动可保留上一 turn 的断线重放。 */
- setCodexState(patch: Partial, options: { preserveReplay?: boolean } = {}) {
- const next = { ...this.codexState, ...patch };
- const threadChanged = next.threadId !== this.codexState.threadId;
- const turnChanged = Boolean(this.codexState.turnId && next.turnId && next.turnId !== this.codexState.turnId);
- const nextTurnStarted = !this.codexState.busy && next.busy;
- if (!options.preserveReplay && (threadChanged || turnChanged || nextTurnStarted)) {
- this.codexReplayEvents.clear();
- this.codexReplayActiveItems.clear();
- }
- if (!next.busy) {
- if (this.boundClientId && !this.clients.has(this.boundClientId)) this.boundClientId = "";
- }
- if (next.busy === this.codexState.busy && next.threadId === this.codexState.threadId && next.turnId === this.codexState.turnId) return;
- this.codexState = next;
- logger.debug("Codex state changed", this.codexState);
- this.emitAll("codex_state", this.codexState);
- }
-
- /** 权威历史已覆盖指定 turn 后,清理其断线重放事件。 */
- acknowledgeCodexHistory(threadId: string, turnIds: string[]) {
- const acknowledged = new Set(turnIds.filter(Boolean));
- if (!threadId || !acknowledged.size) return;
- this.codexReplayEvents.forEach((event, key) => {
- const eventThreadId = String(event.payload.threadId || event.payload.thread_id || "");
- const eventTurnId = String(event.payload.turnId || event.payload.turn_id || "");
- if (eventThreadId === threadId && acknowledged.has(eventTurnId)) {
- this.codexReplayEvents.delete(key);
- this.codexReplayActiveItems.delete(key);
- }
- });
- }
-
- /** 建立网页与 Canvas Agent 之间的 SSE 连接。 */
- openEvents(url: URL, res: ServerResponse, activeThreadId = "") {
- const clientId = url.searchParams.get("clientId") || crypto.randomUUID();
- const statusOnly = url.searchParams.get("role") === "status";
- logger.info("SSE client connected", { clientId, statusOnly });
- res.writeHead(200, { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive" });
- if (!statusOnly) {
- this.clients.set(clientId, res);
- if (!this.clientFocusOrder.has(clientId)) this.clientFocusOrder.set(clientId, 0);
- if (!this.activeClientId) {
- this.activeClientId = clientId;
- this.clientFocusOrder.set(clientId, ++this.focusSequence);
- }
- }
- sendEvent(res, "hello", { ok: true, protocolVersion: AGENT_PROTOCOL_VERSION, clientId, workspace: { activeThreadId }, conversation: this.conversationStateSnapshot, codex: this.codexState, pendingApprovals: this.codexPendingApprovals });
- if (!statusOnly && activeThreadId && this.codexState.threadId === activeThreadId) this.codexReplayEvents.forEach((event) => sendEvent(res, event.type, event.payload));
- const timer = setInterval(() => sendEvent(res, "ping", { time: Date.now() }), 15000);
- res.on("close", () => {
- clearInterval(timer);
- logger.info("SSE client disconnected", { clientId, statusOnly });
- if (statusOnly || this.clients.get(clientId) !== res) return;
- this.clients.delete(clientId);
- this.clientFocusOrder.delete(clientId);
- this.canvasStates.delete(clientId);
- this.pending.forEach((item, requestId) => {
- if (item.clientId !== clientId) return;
- this.pending.delete(requestId);
- item.reject(new Error("请求页面已断开"));
- });
- if (this.activeClientId === clientId) this.activeClientId = [...this.clients.keys()].sort((a, b) => (this.clientFocusOrder.get(b) || 0) - (this.clientFocusOrder.get(a) || 0))[0] || "";
- });
- }
-
- private updateConversation(patch: Partial>) {
- this.conversationState = { ...this.conversationState, ...patch, revision: this.conversationState.revision + 1 };
- const snapshot = this.conversationStateSnapshot;
- this.emitAll("conversation_changed", snapshot);
- return snapshot;
- }
-
- /** 保存指定网页上报的最新画布快照。 */
- updateState(body: unknown, clientId?: string) {
- const targetClientId = clientId || this.activeClientId;
- if (!targetClientId || !this.clients.has(targetClientId)) return;
- const state = { ...((body && typeof body === "object" && !Array.isArray(body) ? body : {}) as Record), clientId: targetClientId } as CanvasSnapshot;
- this.canvasStates.set(targetClientId, state);
- logger.debug("Canvas state updated", { clientId: targetClientId, nodes: state.nodes?.length || 0, connections: state.connections?.length || 0 });
- }
-
- /** 将指定网页设为最近激活的工具目标。 */
- activateClient(clientId: string) {
- if (!this.clients.has(clientId)) throw new Error("当前网页未连接");
- this.activeClientId = clientId;
- this.clientFocusOrder.set(clientId, ++this.focusSequence);
- logger.debug("Canvas client activated", { clientId });
- }
-
- /** 将当前 Agent turn 固定绑定到指定网页。 */
- bindClient(clientId: string) {
- if (!this.clients.has(clientId)) throw new Error("当前网页未连接");
- this.boundClientId = clientId;
- logger.debug("Canvas client bound to turn", { clientId });
- }
-
- /** 解除当前 Agent turn 的网页绑定。 */
- releaseClient(clientId: string) {
- if (this.boundClientId === clientId) this.boundClientId = "";
- logger.debug("Canvas client released from turn", { clientId });
- }
-
- /** 保存当前 turn 可用的图片附件并返回安全引用。 */
- setTurnAttachments(clientId: string, attachments: AgentAttachment[]) {
- this.turnAttachments.clear();
- return attachments.flatMap((item, index) => {
- if (!item.dataUrl?.startsWith("data:image/")) return [];
- const id = item.id?.trim() || `attachment-${crypto.randomUUID()}`;
- const attachment: TurnAttachment = {
- clientId,
- id,
- name: item.name?.trim() || `图片 ${index + 1}`,
- type: item.type?.startsWith("image/") ? item.type : item.dataUrl.match(/^data:([^;]+)/)?.[1] || "image/png",
- size: positiveNumber(item.size, 0),
- width: positiveNumber(item.width, 1024),
- height: positiveNumber(item.height, 1024),
- dataUrl: item.dataUrl,
- };
- this.turnAttachments.set(id, attachment);
- return [{ id, name: attachment.name, type: attachment.type, size: attachment.size, width: attachment.width, height: attachment.height }];
- });
- }
-
- /** 清理指定网页或全部 turn 附件。 */
- clearTurnAttachments(clientId?: string) {
- this.turnAttachments.forEach((item, id) => {
- if (!clientId || item.clientId === clientId) this.turnAttachments.delete(id);
- });
- }
-
- /** 获取属于指定网页 turn 的图片附件。 */
- getTurnAttachment(clientId: string, attachmentId: string) {
- const attachment = this.turnAttachments.get(attachmentId);
- if (!attachment) throw new Error(`找不到本轮图片附件:${attachmentId}`);
- if (attachment.clientId !== clientId) throw new Error("图片附件不属于当前 turn 的发起标签页");
- return attachment;
- }
-
- /** 接收网页返回的工具调用结果。 */
- resolveResult(clientId: string, body: { requestId?: string; error?: string; result?: unknown }) {
- const item = body.requestId ? this.pending.get(body.requestId) : null;
- if (!item || !body.requestId || item.clientId !== clientId) return false;
- this.pending.delete(body.requestId);
- logger.debug("Canvas tool result received", { clientId, requestId: body.requestId, error: body.error, result: body.result });
- body.error ? item.reject(new Error(body.error)) : item.resolve(body.result);
- return true;
- }
-
- /** 向全部已连接网页广播事件。 */
- emitAll(type: string, payload: unknown) {
- this.clients.forEach((client) => sendEvent(client, type, payload));
- }
-
- /** 向全部网页广播带线程归属的事件。 */
- emitThread(type: string, threadId: string, payload: Record = {}) {
- const data: Record = { ...payload, threadId };
- const replayKey = codexReplayKey(type, data);
- const eventTurnId = String(data.turnId || data.turn_id || "");
- const currentScope = threadId === this.codexState.threadId && (!this.codexState.turnId || !eventTurnId || eventTurnId === this.codexState.turnId);
- if (this.codexState.busy && currentScope && replayKey) {
- const item = recordValue(data.item);
- const eventType = String(data.type || "");
- if (type === "agent_event" && item.id && (eventType === "item.started" || eventType === "item.updated")) this.codexReplayActiveItems.add(replayKey);
- if (type === "agent_event" && item.id && eventType === "item.completed") this.codexReplayActiveItems.delete(replayKey);
- if (type === "agent_event" && (eventType === "turn.completed" || eventType === "error")) this.clearReplayActiveTurn(threadId, eventTurnId);
- const replayData = this.replaySnapshot(replayKey, data);
- this.codexReplayEvents.set(replayKey, { type, payload: { ...replayData, replayed: true } });
- while (this.codexReplayEvents.size > 240) {
- const evictable = [...this.codexReplayEvents.keys()].find((key) => !this.codexReplayActiveItems.has(key));
- if (!evictable) break;
- this.codexReplayEvents.delete(evictable);
- }
- }
- this.emitAll(type, data);
- }
-
- /** 为断线重连保存完整的最新文本快照,实时连接仍只接收增量。 */
- private replaySnapshot(replayKey: string, data: Record) {
- if (data.type !== "item.updated" && data.type !== "item.completed") return data;
- const item = recordValue(data.item);
- if (!item.id) return data;
- const previous = recordValue(recordValue(this.codexReplayEvents.get(replayKey)?.payload).item);
- const delta = String(item.delta || "");
- if (!delta) return data;
- const previousText = String(previous.text || "");
- const { delta: _delta, ...snapshotItem } = item;
- return { ...data, item: { ...previous, ...snapshotItem, text: `${previousText}${delta}` } };
- }
-
- private clearReplayActiveTurn(threadId: string, turnId: string) {
- const prefix = `item:${turnId}:`;
- this.codexReplayActiveItems.forEach((key) => {
- if (key.startsWith(prefix)) this.codexReplayActiveItems.delete(key);
- });
- if (!turnId) return;
- this.codexReplayActiveItems.forEach((key) => {
- const event = this.codexReplayEvents.get(key);
- const eventThreadId = String(event?.payload.threadId || event?.payload.thread_id || "");
- const eventTurnId = String(event?.payload.turnId || event?.payload.turn_id || "");
- if (eventThreadId === threadId && eventTurnId === turnId) this.codexReplayActiveItems.delete(key);
- });
- }
-
- /** 校验工具参数并将调用分派到当前目标网页。 */
- async callTool(name: unknown, rawInput: unknown) {
- if (!isToolName(name)) throw new Error(`未知工具:${String(name)}`);
- logger.info("MCP tool called", { name, input: rawInput, targetClientId: this.targetClientId });
- const input = parseToolInput(name, rawInput) as Record;
- if (SITE_TOOLS.has(name)) {
- if (!this.clients.size) throw new Error("当前没有已连接网页");
- return await this.requestCanvasTool(name, input);
- }
- const readTool = ["canvas_get_state", "canvas_get_selection", "canvas_export_snapshot"].includes(name);
- if (readTool && (!this.clients.size || !this.canvasState)) throw new Error("当前没有已连接画布");
- if (name === "canvas_get_state" || name === "canvas_export_snapshot") return compactCanvasState(this.canvasState);
- if (name === "canvas_get_selection") {
- const ids = new Set(this.canvasState?.selectedNodeIds || []);
- return { nodes: (this.canvasState?.nodes || []).filter((node) => ids.has(node.id)).map(compactNode) };
- }
- if (name === "canvas_create_attachment_nodes") return await this.createAttachmentNodes(input as { attachmentIds: string[]; x?: number; y?: number; gap?: number; direction?: "row" | "column" });
- if (!this.clients.size) throw new Error("当前没有已连接画布");
- const request = buildCanvasToolRequest(name, input, this.canvasState);
- return await this.requestCanvasTool(request.name, request.input);
- }
-
- /** 将当前 turn 的附件转换为画布图片节点。 */
- private async createAttachmentNodes(input: { attachmentIds: string[]; x?: number; y?: number; gap?: number; direction?: "row" | "column" }) {
- const clientId = this.targetClientId;
- if (!this.clients.has(clientId)) throw new Error("当前没有已连接画布");
- const attachments = input.attachmentIds.map((id) => this.getTurnAttachment(clientId, id));
- const x = Number(input.x ?? nextCanvasX(this.canvasState));
- const y = Number(input.y ?? 0);
- const gap = Number(input.gap ?? 40);
- const direction = input.direction || "row";
- let offset = 0;
- const nodes = attachments.map((attachment) => {
- const size = fitAttachmentNodeSize(attachment.width, attachment.height);
- const node = {
- id: `image-${crypto.randomUUID()}`,
- attachmentId: attachment.id,
- title: attachment.name,
- position: { x: direction === "row" ? x + offset : x, y: direction === "column" ? y + offset : y },
- width: size.width,
- height: size.height,
- };
- offset += (direction === "row" ? size.width : size.height) + gap;
- return node;
- });
- await this.requestCanvasTool("canvas_create_attachment_nodes", { nodes });
- return { nodes: nodes.map(({ id, attachmentId, title }) => ({ id, attachmentId, title })) };
- }
-
- /** 向目标网页发送工具请求并等待调用结果。 */
- private async requestCanvasTool(name: ToolName, input: Record) {
- const requestId = crypto.randomUUID();
- const clientId = this.targetClientId;
- const client = this.clients.get(clientId);
- if (!client) throw new Error("当前没有已连接画布");
- sendEvent(client, "tool_call", { requestId, name, input });
- logger.debug("Canvas tool request sent", { requestId, name, input, clientId });
- return await new Promise((resolve, reject) => {
- const timer = setTimeout(() => {
- this.pending.delete(requestId);
- logger.warn("Canvas tool request timed out", { requestId, name, clientId });
- reject(new Error("画布操作超时"));
- }, 30000);
- this.pending.set(requestId, { clientId, resolve: (value) => (clearTimeout(timer), resolve(value)), reject: (error) => (clearTimeout(timer), reject(error)) });
- });
- }
-}
-
-/** 为运行中 turn 的可重放事件生成稳定键。 */
-function codexReplayKey(type: string, payload: Record) {
- const turnId = String(payload.turnId || payload.turn_id || "");
- if (type === "chat_message") {
- const message = recordValue(payload.message);
- const clientMessageId = String(message.clientMessageId || "");
- if (clientMessageId) return `chat:${clientMessageId}`;
- const messageId = String(message.itemId || message.id || "");
- return messageId ? `chat:${turnId}:${messageId}` : "";
- }
- if (type === "agent_error") return `error:${turnId}`;
- if (type !== "agent_event") return "";
- const item = recordValue(payload.item);
- if (item.id) return `item:${turnId}:${String(item.id)}`;
- const eventType = String(payload.type || "");
- if (eventType === "plan.updated") return `plan:${turnId}`;
- if (eventType === "usage.updated") return `usage:${turnId}`;
- if (eventType === "turn.completed" || eventType === "error") return `${eventType}:${turnId}`;
- return "";
-}
-
-function recordValue(value: unknown) {
- return value && typeof value === "object" && !Array.isArray(value) ? value as Record : {};
-}
-
-/** 向 SSE 连接写入一个事件。 */
-function sendEvent(res: ServerResponse, type: string, payload: unknown) {
- res.write(`event: ${type}\ndata: ${JSON.stringify(payload)}\n\n`);
-}
-/** 将未知数值转换为正数,否则使用默认值。 */
-function positiveNumber(value: unknown, fallback: number) {
- const number = Number(value);
- return Number.isFinite(number) && number > 0 ? number : fallback;
-}
diff --git a/canvas-agent/src/canvas/tools.ts b/canvas-agent/src/canvas/tools.ts
deleted file mode 100644
index 212cc8e..0000000
--- a/canvas-agent/src/canvas/tools.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { toolInputSchemas, toolNames, type ToolName } from "./schemas.js";
-import type { CanvasNode, CanvasSnapshot } from "./types.js";
-
-/** 判断传入名称是否为已注册的画布工具。 */
-export function isToolName(name: unknown): name is ToolName {
- return typeof name === "string" && toolNames.includes(name as ToolName);
-}
-
-/** 按工具名称校验并解析调用参数。 */
-export function parseToolInput(name: ToolName, input: unknown) {
- return toolInputSchemas[name].parse(input ?? {});
-}
-
-/** 压缩画布快照,避免向 Agent 返回过长的节点内容。 */
-export function compactCanvasState(state: CanvasSnapshot | null) {
- if (!state) throw new Error("当前没有已连接画布");
- return { ...state, nodes: (state.nodes || []).map(compactNode) };
-}
-
-/** 压缩单个画布节点的元数据内容。 */
-export function compactNode(node: CanvasNode) {
- const metadata = { ...(node.metadata || {}) };
- if (typeof metadata.content === "string" && metadata.content.length > 240) metadata.content = `${metadata.content.slice(0, 120)}...`;
- return { id: node.id, type: node.type, title: node.title, position: node.position, width: node.width, height: node.height, metadata };
-}
-
-/** 计算新节点在当前画布右侧的默认横坐标。 */
-export function nextCanvasX(state: CanvasSnapshot | null) {
- const nodes = state?.nodes || [];
- return nodes.length ? Math.max(...nodes.map((node) => node.position.x + node.width)) + 80 : 0;
-}
diff --git a/canvas-agent/src/canvas/types.ts b/canvas-agent/src/canvas/types.ts
deleted file mode 100644
index f252152..0000000
--- a/canvas-agent/src/canvas/types.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-/** 画布坐标。 */
-export type Position = { x: number; y: number };
-export type Viewport = { x: number; y: number; k: number };
-export type CanvasNodeType = "image" | "text" | "config" | "video" | "audio";
-export type CanvasNode = { id: string; type: CanvasNodeType; title?: string; position: Position; width: number; height: number; metadata?: Record };
-export type CanvasConnection = { id: string; fromNodeId: string; toNodeId: string };
-export type CanvasSnapshot = { projectId?: string; title?: string; nodes?: CanvasNode[]; connections?: CanvasConnection[]; selectedNodeIds?: string[]; viewport?: Viewport; clientId?: string };
diff --git a/canvas-agent/src/config.ts b/canvas-agent/src/config.ts
deleted file mode 100644
index e294431..0000000
--- a/canvas-agent/src/config.ts
+++ /dev/null
@@ -1,84 +0,0 @@
-import crypto from "node:crypto";
-import fs from "node:fs";
-import os from "node:os";
-import path from "node:path";
-
-export const DEFAULT_PORT = 17371;
-export const CONFIG_DIR = path.join(os.homedir(), ".infinite-canvas");
-export const CONFIG_FILE = path.join(CONFIG_DIR, "canvas-agent.json");
-export const VERSION = readPackageVersion();
-export const AGENT_PROMPT = fs.readFileSync(new URL("../agent-instructions.md", import.meta.url), "utf8");
-const initializedWorkspaces = new Set();
-
-export type SiteWorkspaceConfig = { workspacePath: string; activeThreadId?: string; pinnedThreadIds?: string[] };
-export type CanvasAgentConfig = { url: string; token: string; origins?: string[]; workspace?: SiteWorkspaceConfig };
-
-/** 读取本地 Canvas Agent 配置,不存在时生成默认配置。 */
-export function loadConfig(create = false): CanvasAgentConfig {
- try {
- return JSON.parse(fs.readFileSync(CONFIG_FILE, "utf8")) as CanvasAgentConfig;
- } catch {
- const config = { url: `http://127.0.0.1:${Number(process.env.PORT) || DEFAULT_PORT}`, token: crypto.randomBytes(18).toString("hex") };
- if (create) saveConfig(config);
- return config;
- }
-}
-
-/** 将 Canvas Agent 配置写入用户配置目录。 */
-export function saveConfig(config: CanvasAgentConfig) {
- fs.mkdirSync(CONFIG_DIR, { recursive: true });
- fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
-}
-
-/** 确保站点级 Codex 工作空间存在并已初始化。 */
-export function ensureSiteWorkspace(config: CanvasAgentConfig) {
- const current = config.workspace;
- if (current?.workspacePath) {
- const workspacePath = resolveWorkspacePath(current.workspacePath);
- initializeWorkspace(workspacePath);
- return { ...current, workspacePath };
- }
- const workspacePath = path.join(CONFIG_DIR, "codex-workspaces", "site");
- config.workspace = { workspacePath };
- initializeWorkspace(workspacePath);
- saveConfig(config);
- return { workspacePath };
-}
-
-/** 更新站点级 Codex 工作空间配置。 */
-export function updateSiteWorkspace(config: CanvasAgentConfig, patch: Partial) {
- const current = ensureSiteWorkspace(config);
- const workspacePath = patch.workspacePath ? resolveWorkspacePath(patch.workspacePath) : current.workspacePath;
- const next = { ...current, ...patch, workspacePath };
- config.workspace = { workspacePath: next.workspacePath, activeThreadId: next.activeThreadId, pinnedThreadIds: next.pinnedThreadIds };
- initializeWorkspace(workspacePath);
- saveConfig(config);
- return config.workspace;
-}
-
-/** 创建工作空间目录并写入默认 AGENTS.md。 */
-function initializeWorkspace(workspacePath: string) {
- if (initializedWorkspaces.has(workspacePath)) return;
- fs.mkdirSync(workspacePath, { recursive: true });
- const instructionsFile = path.join(workspacePath, "AGENTS.md");
- const current = fs.existsSync(instructionsFile) ? fs.readFileSync(instructionsFile, "utf8") : "";
- if (!current || current.startsWith("# Infinite Canvas Agent")) fs.writeFileSync(instructionsFile, AGENT_PROMPT);
- initializedWorkspaces.add(workspacePath);
-}
-
-/** 将用户输入的工作空间路径解析为绝对路径。 */
-function resolveWorkspacePath(value: string) {
- if (value === "~") return os.homedir();
- if (value.startsWith("~/")) return path.join(os.homedir(), value.slice(2));
- return path.resolve(value);
-}
-
-/** 从当前包信息中读取 Canvas Agent 版本号。 */
-function readPackageVersion() {
- try {
- const pkg = JSON.parse(fs.readFileSync(new URL("../package.json", import.meta.url), "utf8")) as { version?: string };
- return pkg.version || "0.0.0";
- } catch {
- return "0.0.0";
- }
-}
diff --git a/canvas-agent/src/index.ts b/canvas-agent/src/index.ts
deleted file mode 100644
index 291ee65..0000000
--- a/canvas-agent/src/index.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/usr/bin/env node
-import { startHttpServer } from "./server/http.js";
-import { startMcpServer } from "./server/mcp.js";
-
-if (process.argv[2] === "mcp") await startMcpServer();
-else startHttpServer();
diff --git a/canvas-agent/src/server/http.ts b/canvas-agent/src/server/http.ts
deleted file mode 100644
index e7279b9..0000000
--- a/canvas-agent/src/server/http.ts
+++ /dev/null
@@ -1,548 +0,0 @@
-import { spawn } from "node:child_process";
-import { readFile, stat } from "node:fs/promises";
-import path from "node:path";
-import express, { type NextFunction, type Request, type Response } from "express";
-
-import { runClaudeTurn } from "../agent/claude.js";
-import { archiveCodexThread, CodexSkillLookupError, configureCodexSkill, generateCodexSkillDraft, interruptCodexTurn, isRecoverableThreadError, listCodexModels, listCodexSkills, listCodexThreads, readCodexThread, resolveCodexApproval, resolveCodexSkill, resumeCodexThread, runCodexTurn, startCodexThread, summarizeCodexThread } from "../agent/codex.js";
-import type { CodexReasoningEffort, CodexSkillSelector } from "../agent/codex-protocol.js";
-import { messageMetadataStore } from "../agent/message-metadata.js";
-import type { AgentAttachment, AgentPermissionMode } from "../agent/types.js";
-import { AGENT_PROTOCOL_VERSION, CanvasSession } from "../canvas/session.js";
-import { DEFAULT_PORT, ensureSiteWorkspace, loadConfig, saveConfig, updateSiteWorkspace, type CanvasAgentConfig } from "../config.js";
-import { logger } from "../utils/logger.js";
-import { checkVersions } from "../version-check.js";
-import { SkillStore, SkillStoreError } from "../skills/store.js";
-
-/** 启动仅监听本机的 Canvas Agent HTTP 服务。 */
-export function startHttpServer() {
- const config = loadConfig(true);
- const port = Number(process.env.PORT) || Number(new URL(config.url).port) || DEFAULT_PORT;
- config.url = `http://127.0.0.1:${port}`;
- saveConfig(config);
-
- const initialWorkspace = ensureSiteWorkspace(config);
- const session = new CanvasSession(initialWorkspace.activeThreadId || "");
- const skillStore = new SkillStore(initialWorkspace.workspacePath);
- /** 将 Agent 事件广播到所属线程或全部网页。 */
- const emit = (type: string, payload: unknown) => {
- const value = payload && typeof payload === "object" && !Array.isArray(payload) ? payload as Record