Compare commits

...

65 Commits

Author SHA1 Message Date
yu 49851f2b5f feat(prompts): unify sources and add personal library 2026-07-21 01:08:11 +08:00
yu c81bb8b651 style(agent): distinguish chat participants 2026-07-20 12:14:36 +08:00
yu b7d0fe10bc fix(agent): improve chat navigation 2026-07-20 12:13:33 +08:00
HouYunFei d91e67a5ad feat(canvas): retain prompt input after node generation completion 2026-07-20 11:34:46 +08:00
HouYunFei d566d5cc46 Merge remote-tracking branch 'origin/main'
# Conflicts:
#	CHANGELOG.md
2026-07-20 07:55:56 +08:00
yu 8fb8d0b224 fix(agent): add uploaded images to canvas flows 2026-07-19 18:17:57 +08:00
yu 5e1fd7a825 fix(agent): synchronize shared Codex sessions 2026-07-18 15:29:58 +08:00
yu 062e4569aa feat(agent): add task status and client-scoped operations 2026-07-18 15:29:58 +08:00
HouYunFei b5dc9c8a87 feat(canvas): optimize CanvasAssetsTab and CanvasPromptsTab with memoization to improve performance 2026-07-18 08:27:20 +08:00
HouYunFei 75c22c2f9f feat(canvas): add "prompts" tab to the left panel with collapsible prompt sources 2026-07-18 08:19:59 +08:00
HouYunFei bdca6b0a5c feat(prompt-sources): add davidwu-gpt-image2-prompts to default prompt sources 2026-07-17 17:45:50 +08:00
HouYunFei d4130bbb79 feat(prompt-sources): add custom script fetching functionality for prompt sources 2026-07-17 17:36:42 +08:00
HouYunFei 0e1edd1484 chore(release): bump version to v0.9.0 and update changelog 2026-07-17 16:43:01 +08:00
HouYunFei 5107be0acf feat(canvas): 资产卡增删改重做 + 节点名称默认隐藏
- 资产 Tab 卡片重做:视频取首帧封面、文本铺内容、去标题成矩形,hover 居中显示插入/删除两个 icon 按钮
- 修复删除确认框「取消」经 React Portal 事件冒泡误触插入
- 资产 Tab 支持上传添加图片/视频、卡片移除资产
- 画布节点名称默认不再显示,仅在选中/悬停/编辑时出现
- 操作按钮改用扁平无灰底样式,并写入 AGENTS.md 规范

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 16:42:08 +08:00
HouYunFei 219b3e84db feat(canvas): 左侧面板可调宽收起 + 元素多选导出与跳转动画
- 左侧画布面板支持拖拽调整宽度、展开/收起(带动画),状态与宽度持久化
- 顶栏菜单左侧新增面板开关按钮,并缩小菜单/开关按钮
- 顶栏菜单新增「导出当前画布」为压缩包
- 画布元素列表支持多选并批量导出:图片/视频/音频为文件、文本为 txt、其余各自为 json
- 画布/资产切换改为滑动下划线动画,点击元素跳转带缓动动画
- 移除文本/配置/视频等非图片元素图标的灰色底色,并写入 AGENTS.md 规范

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 16:21:26 +08:00
HouYunFei a3411cf9d9 docs(changelog): note the drag-resize React #185 crash fix
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 15:17:57 +08:00
HouYunFei 430d1bd956 refactor(canvas-project): extract plugin host into usePluginHost hook
Move the plugin node host capabilities (pluginAi generation bridge, pluginHost
canvas accessors, renderPluginPanel, buildNodeToolbarItems) and the plugin-load
effect out of InfiniteCanvasPage into usePluginHost. The hook receives the
shared config/refs/setters explicitly and keeps identical dependency arrays.

This also relocates the AI-service and plugin imports (requestVideoGeneration,
selectableModelsByCapability, buildNodeContext, ensurePluginsLoaded, the
canvas-plugin types, etc.) that were only used by this cluster.

project.tsx is now ~2950 lines, down from 3536 at the start of the refactor.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 14:19:00 +08:00
HouYunFei cf0561d12a refactor(canvas-project): extract agent bridge into useAgentBridge hook
Move the canvas<->local-Agent bridge (agentSnapshot memo, applyAgentOps /
undoAgentOps, agentUndoSnapshot state, and the publish-to-agent-store effect)
out of InfiniteCanvasPage into a useAgentBridge hook. Only applyAgentOps is
consumed outside (by the plugin host); the rest is fully encapsulated.

The hook receives the shared canvas state/refs/setters explicitly and keeps the
exact same dependency arrays, so behavior is unchanged. project.tsx drops the
now-unused agent-ops imports and the setCanvasContext selector.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 14:14:10 +08:00
HouYunFei e6dcd7a832 refactor(canvas-project): extract CanvasTopBar into its own component
Move CanvasTopBar and its private helpers (MenuLabel, CompactAgentStatus,
Shortcut) out of project.tsx into canvas-top-bar.tsx. The component already took
all data via explicit props, so the move is mechanical with no behavior change.
Drop the imports (DOCS_URL, UserStatusActions, Dropdown, several icons) that
were only used by the top bar.

project.tsx is now ~3080 lines, down from 3536 at the start of the refactor.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 14:04:25 +08:00
HouYunFei 187c499ef9 refactor(canvas-project): extract refresh shell and create menus into components
Move the module-scope presentational subcomponents out of project.tsx:
- canvas-refresh-shell.tsx: CanvasRefreshShell (pre-load placeholder).
- canvas-create-menus.tsx: ConnectionCreateMenu, ConnectionCreateOption,
  NodeCreateMenu, plus the PendingConnectionCreate type they define.

They already communicated via explicit props, so the move is mechanical. No
behavior change; project.tsx imports them back and drops now-unused icon and
node-registry imports.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 14:00:53 +08:00
HouYunFei cf1ea2646f refactor(canvas-project): extract pure helpers into @/lib/canvas modules
Move the ~30 module-scope pure functions out of the 3.5k-line project.tsx into
three focused lib modules, with identical names and signatures. No behavior
change; project.tsx imports them back.

- canvas-node-factory.ts: createCanvasNode, *Metadata builders, referenceUrl,
  applyNodeConfigPatch.
- canvas-node-geometry.ts: nodeBounds, group drop/snap, connection anchors,
  normalizeConnection, hidden-batch checks.
- canvas-generation-helpers.ts: generation config/inputs, reference hydration,
  retry-source lookup, angle prompt builders, misc pure helpers.

project.tsx drops from 3536 to ~3230 lines. Also removes imports that were only
used by the moved helpers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 13:46:07 +08:00
HouYunFei 57db0fe752 feat(canvas-local-agent-panel): optimize state management and rendering performance 2026-07-17 13:38:55 +08:00
HouYunFei c5f53454aa feat(changelog): add support for Google Analytics 4 and Baidu statistics 2026-07-17 11:32:11 +08:00
HouYunFei ca6efdf0c3 feat(analytics): implement runtime configuration and analytics tracking for GA4 and Baidu 2026-07-17 11:24:22 +08:00
HouYunFei a2586ffe34 feat(canvas-node-manual): enhance input box to support resource references with thumbnail display 2026-07-17 11:19:34 +08:00
HouYunFei 5a27aae99e feat(changelog): update entries for canvas node enhancements and image reference display 2026-07-17 10:15:16 +08:00
HouYunFei 66961ce10f feat(canvas-prompt-chip-input): implement new component for enhanced prompt input with reference chips 2026-07-17 10:06:57 +08:00
HouYunFei 0c4288b832 chore: update changelog for v0.8.2 and bump version to v0.8.2 2026-07-16 17:44:20 +08:00
HouYunFei 093260f7b2 feat(image-settings-panel): clarify transparent background feature availability in tooltip 2026-07-16 17:44:05 +08:00
HouYunFei 805573b980 feat(prompt-chip-input): implement contentEditable input for image references with thumbnail display 2026-07-16 17:32:43 +08:00
HouYunFei e6829333c5 feat(canvas-node): enhance input area styling and improve cursor visibility in prompt panel 2026-07-16 17:28:30 +08:00
HouYunFei 2959b5d340 feat(image-settings): add transparent background toggle for image generation 2026-07-16 15:45:02 +08:00
HouYunFei 2f9776bee3 chore: bump version to v0.8.1 2026-07-16 15:12:02 +08:00
HouYunFei 4beadc6161 feat(markdown-node): improve rendering performance and user interaction by caching parsed Markdown and optimizing DOM updates 2026-07-16 15:11:48 +08:00
HouYunFei 13c508bc34 feat(svg-node): enhance SVG node with transparent background rendering and improved editing experience 2026-07-16 14:13:43 +08:00
HouYunFei 5f627d5baf feat(sticky-note): update sticky note plugin with customizable colors, improved editing experience, and enhanced drag-and-drop functionality 2026-07-16 13:57:47 +08:00
HouYunFei 3f179a607a feat(panorama): enhance 3D panorama node with AI generation support and unified interaction toggle 2026-07-16 13:21:25 +08:00
HouYunFei 2d2c7e3d5e feat(plugin-manager): add upgrade notification for installed plugins and improve version handling 2026-07-16 10:36:41 +08:00
HouYunFei 3f623ea70c feat(html-node): enhance HTML node plugin with interactive toolbar and improved editor functionality 2026-07-15 17:51:39 +08:00
HouYunFei 7ac2ab351d chore(release): bump version to v0.8.0 and update CHANGELOG 2026-07-15 16:00:17 +08:00
HouYunFei b6e75c225e Merge branch 'dev'
# Conflicts:
#	.gitignore
#	CHANGELOG.md
#	web/bun.lock
2026-07-15 15:32:32 +08:00
HouYunFei 75aafc115f feat(plugin-sdk): add TypeScript SDK for Infinite Canvas plugins with automatic JSX and build support 2026-07-15 15:31:48 +08:00
HouYunFei 8d3f244524 feat(toolbar): add extension node management and close on outside click 2026-07-15 13:10:53 +08:00
HouYunFei 88510223eb feat(plugin): add local plugin discovery and management with cache busting 2026-07-15 12:55:59 +08:00
HouYunFei 3a4a4a6772 fix(deps): sync bun.lock with package.json for streamdown transitive deps 2026-07-15 11:59:59 +08:00
HouYunFei ee8f126da7 feat(id): replace crypto.randomUUID with nanoid for unique ID generation 2026-07-15 11:55:36 +08:00
HouYunFei eef4d96787 feat(plugin): implement canvas node plugin system with dynamic registration and remote installation 2026-07-15 11:47:25 +08:00
HouYunFei f0db29d8e3 feat(channel): add customizable JS model-call scripts with split-panel editor
- Run user-authored async scripts per model with flat locals (prompt,
  images, messages, params, model, baseUrl, apiKey, http, request, poll,
  sleep, signal, onDelta); empty script falls back to system default.
- Wire image/video/audio/text request entrypoints through the plugin
  runtime, keeping default handlers unchanged.
- Split-panel script editor: variable/return docs on the left, CodeMirror
  JS editor on the right; title shows capability - model.
- Provide OpenAI and Gemini template buttons per capability; text template
  uses the /responses API; image template branches text2img vs img2img.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 11:24:34 +08:00
HouYunFei 3963c6eebf feat(model): enhance model configuration and script handling for audio and video capabilities 2026-07-15 10:13:37 +08:00
HouYunFei c57f7d61a7 feat(model): enhance model configuration and script handling for audio and video capabilities 2026-07-15 10:13:32 +08:00
HouYunFei a4dcc679c9 feat(version): bump version to 0.7.0 2026-07-14 17:37:57 +08:00
HouYunFei b16263eb43 feat(version): bump version to 0.2.0 and update image ratio parsing functions 2026-07-14 17:37:51 +08:00
HouYunFei a5824f509e feat(agent): enhance local Agent connection handling with silent mode and update UI instructions 2026-07-14 17:08:20 +08:00
HouYunFei 7e0288d461 feat(docs): refine Infinite Canvas usage instructions and clarify local Agent connection process 2026-07-14 16:24:22 +08:00
HouYunFei 228ca29c82 feat(docs): update Infinite Canvas usage instructions and streamline local Agent connection process 2026-07-13 17:23:36 +08:00
HouYunFei 301fbce472 feat(canvas): add double-click functionality to create nodes from blank canvas area 2026-07-13 10:43:37 +08:00
HouYunFei ebd8ae2de7 feat(agent): clarify local Agent connection instructions for Codex plugin and manual MCP usage 2026-07-09 17:49:40 +08:00
HouYunFei 864ce4101a feat(agent): implement streamdown for real-time message rendering in chat 2026-07-09 17:16:35 +08:00
HouYunFei 25e1d173e8 feat(docs): add local development and canvas data structure documentation 2026-07-09 17:14:46 +08:00
HouYunFei 52a5e9e33a feat(agent): update documentation for new site tools and changelog guidelines 2026-07-09 16:54:48 +08:00
HouYunFei 2adbe9e43a feat(agent): add site tools support for canvas project listing and media generation 2026-07-09 16:53:09 +08:00
HouYunFei 7b347729ec feat(agent): implement global Agent panel with site navigation and interrupt functionality 2026-07-09 16:19:32 +08:00
HouYunFei 387d8be961 feat(canvas): enhance group node functionality with improved click and drag behavior 2026-07-09 13:31:13 +08:00
HouYunFei ec0e8c4e24 feat(canvas): add group node support with drag-and-drop functionality 2026-07-09 12:54:36 +08:00
HouYunFei abe3be58a6 feat(canvas): add group node support with drag-and-drop functionality 2026-07-09 11:48:23 +08:00
199 changed files with 30953 additions and 3623 deletions
+1 -1
View File
@@ -24,7 +24,7 @@ jobs:
- name: Install dependencies - name: Install dependencies
working-directory: web working-directory: web
run: bun install --frozen-lockfile run: bun install
- name: Build - name: Build
working-directory: web working-directory: web
+47
View File
@@ -0,0 +1,47 @@
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
+7 -1
View File
@@ -8,4 +8,10 @@ data/
*.tsbuildinfo *.tsbuildinfo
.DS_Store .DS_Store
.idea .idea
web/dist web/dist
**/dist
web/public/plugins
plugins/canvas/*/dist
plugins/canvas/*/node_modules
web/public/plugins
+5 -15
View File
@@ -8,7 +8,7 @@
- 写代码保持最少行数,能简单实现就不要引入复杂抽象。 - 写代码保持最少行数,能简单实现就不要引入复杂抽象。
- 标准格式、协议、解析、压缩、加密、日期等通用能力优先使用成熟稳定的库,不要手写底层实现,除非用户明确要求或项目已有实现必须沿用。 - 标准格式、协议、解析、压缩、加密、日期等通用能力优先使用成熟稳定的库,不要手写底层实现,除非用户明确要求或项目已有实现必须沿用。
- 不要为了“兼容更多场景”写大量分支,只实现当前明确需要的功能。 - 不要为了“兼容更多场景”写大量分支,只实现当前明确需要的功能。
- 项目尚未上线,不需要兼容旧数据;表结构或字段调整时直接按新设计修改,不写旧字段兼容数据迁移兜底或删除旧表的清理逻辑,除非用户明确要求。 - 项目尚未上线,不需要兼容旧数据;本地存储结构调整时直接按新设计修改,不写旧字段兼容数据迁移兜底,除非用户明确要求。
- 每次写完代码,不需要检查语法,不需要执行构建,用户会自己做。 - 每次写完代码,不需要检查语法,不需要执行构建,用户会自己做。
- 不要改无关文件,不要顺手重构。 - 不要改无关文件,不要顺手重构。
- 如果工作区已有用户改动,不要回滚,不要覆盖;只在必要范围内追加修改。 - 如果工作区已有用户改动,不要回滚,不要覆盖;只在必要范围内追加修改。
@@ -19,22 +19,11 @@
- 补充时写成明确、可执行的规则,避免只写模糊描述。 - 补充时写成明确、可执行的规则,避免只写模糊描述。
- 新规则应放到最相关的章节;找不到合适章节时放到“项目注意事项”。 - 新规则应放到最相关的章节;找不到合适章节时放到“项目注意事项”。
## 后端规范
- 后端使用 Go + Gin + GORM。
- `handler/` 只处理 HTTP 入参、调用 service、返回 `OK` / `Fail`
- `service/` 放业务逻辑、默认值、校验、时间、ID、鉴权等处理。
- `repository/` 只做数据库访问和 GORM 查询。
- `model/` 只定义数据结构、枚举和简单模型方法。
- 列表接口优先沿用 `model.Query``Normalize`、分页和标签筛选方式。
- 业务接口保持 `{ code, data, msg }` 的响应结构。
- 新增数据表时同步更新 `docs/backend-database.md`
## 前端规范 ## 前端规范
- 前端使用 Vite、React、React Router、TypeScript、Ant Design、Tailwind、Zustand。 - 前端使用 Vite、React、React Router、TypeScript、Ant Design、Tailwind、Zustand。
- 编写 Ant Design 相关代码时,参考 https://ant.design/llms-full.txt 理解组件 API、示例和设计规范,并优先结合项目当前 antd 版本与既有写法。 - 编写 Ant Design 相关代码时,参考 https://ant.design/llms-full.txt 理解组件 API、示例和设计规范,并优先结合项目当前 antd 版本与既有写法。
- API 请求统一放在 `web/src/services/api/` - 外部服务请求统一放在 `web/src/services/api/`,由浏览器前端直连,不假设存在项目后端
- 全局或跨页面状态优先放在 `web/src/stores/` - 全局或跨页面状态优先放在 `web/src/stores/`
- 已经放在全局 store 或全局 hook 中的状态/动作,组件需要时直接使用对应 store/hook,不要为了“纯组件”层层透传 props;避免一个组件传递过多参数。 - 已经放在全局 store 或全局 hook 中的状态/动作,组件需要时直接使用对应 store/hook,不要为了“纯组件”层层透传 props;避免一个组件传递过多参数。
- 全局组件、全局常量、全局配置等全局性质的内容不要作为 props 或参数层层传递;哪里需要就在哪里直接从对应全局入口获取。 - 全局组件、全局常量、全局配置等全局性质的内容不要作为 props 或参数层层传递;哪里需要就在哪里直接从对应全局入口获取。
@@ -62,6 +51,8 @@
- 不要硬编码黑白、stone、slate 等颜色导致浅色/深色主题不一致。 - 不要硬编码黑白、stone、slate 等颜色导致浅色/深色主题不一致。
- 新增画布按钮、弹窗、浮层时,尽量复用已有工具栏、节点面板、Modal 的视觉风格。 - 新增画布按钮、弹窗、浮层时,尽量复用已有工具栏、节点面板、Modal 的视觉风格。
- 画布顶部工具栏和状态信息优先采用极简扁平风格:无边框、无阴影、无胶囊背景,融入整体背景,弱化按钮感,仅保留轻微 hover 反馈,保持简洁现代、低视觉重量。 - 画布顶部工具栏和状态信息优先采用极简扁平风格:无边框、无阴影、无胶囊背景,融入整体背景,弱化按钮感,仅保留轻微 hover 反馈,保持简洁现代、低视觉重量。
- 左侧画布面板等列表里的节点/元素缩略图容器,非图片类型(文本、配置、视频、音频等)不要使用 `theme.node.fill``#e7e5df`/`#292524`)这类灰色背景,图标直接无背景展示,尽量不要给多余底色,保持干净。
- 画布内的操作按钮(如面板里的「添加」「导出」「选择」等)默认用扁平无底色样式:透明背景、仅 `hover:bg-black/5 dark:hover:bg-white/10` 轻微反馈,靠图标+文字表达,不要用 `theme.toolbar.activeBg``#e7e5df`/`#3a3631`)或 `theme.node.fill` 之类的灰色作为按钮填充底色。灰色 `activeBg` 只允许用于「选中态」等需要表达状态的高亮,不要当普通装饰底色。
- 图片节点尺寸逻辑要尊重原始比例,除非功能明确要求自由变形。 - 图片节点尺寸逻辑要尊重原始比例,除非功能明确要求自由变形。
- 批量生成、多图展示、助手面板等画布交互要尽量简洁,不要占用过多画布空间。 - 批量生成、多图展示、助手面板等画布交互要尽量简洁,不要占用过多画布空间。
@@ -73,10 +64,9 @@
- 后续待办写到 `docs/content/docs/progress/todo.mdx` - 后续待办写到 `docs/content/docs/progress/todo.mdx`
- 已实现但还需要用户测试确认的事项写到 `docs/content/docs/progress/pending-test.mdx` - 已实现但还需要用户测试确认的事项写到 `docs/content/docs/progress/pending-test.mdx`
- `docs/content/docs/progress/pending-test.mdx` 用来记录这个版本实际做了哪些可测试变更;`CHANGELOG.md``Unreleased` 只保留对这些变更的版本级归纳,避免逐条照搬实现细节。 - `docs/content/docs/progress/pending-test.mdx` 用来记录这个版本实际做了哪些可测试变更;`CHANGELOG.md``Unreleased` 只保留对这些变更的版本级归纳,避免逐条照搬实现细节。
- 每次重大改动(新增/调整/删除功能、接口或工具,影响用户可感知行为)完成后,都要在 `CHANGELOG.md``Unreleased` 追加一条记录,按 `[新增]` / `[调整]` / `[修复]` / `[优化]` 前缀分类,用一句中文归纳;纯内部重构、格式化、无用户可感知影响的小改动可不记。
- 每次 todo 事项完成后,先从 `docs/content/docs/progress/todo.mdx` 移到 `docs/content/docs/progress/pending-test.mdx`,不要直接写进正式功能说明;用户确认测试通过后再更新 `docs/content/docs/overview/features.mdx` - 每次 todo 事项完成后,先从 `docs/content/docs/progress/todo.mdx` 移到 `docs/content/docs/progress/pending-test.mdx`,不要直接写进正式功能说明;用户确认测试通过后再更新 `docs/content/docs/overview/features.mdx`
- 每次任务完成前,都要根据实际变更检查并更新 `docs/content/docs/progress/todo.mdx``docs/content/docs/progress/pending-test.mdx`;如果功能或待办没有变化,也要确认无需修改。 - 每次任务完成前,都要根据实际变更检查并更新 `docs/content/docs/progress/todo.mdx``docs/content/docs/progress/pending-test.mdx`;如果功能或待办没有变化,也要确认无需修改。
- 接口响应规则写到 `docs/content/docs/backend/api-response.mdx`
- 数据库结构写到 `docs/content/docs/backend/backend-database.mdx`
- 文档不要写过期日期;除非用户明确要求记录具体时间。 - 文档不要写过期日期;除非用户明确要求记录具体时间。
## 发版本流程 ## 发版本流程
+72
View File
@@ -2,6 +2,78 @@
## Unreleased ## Unreleased
+ [新增] 提示词来源新增 Banana Prompt Quicker,并支持添加自定义标准 JSON 来源。
+ [新增] 提示词中心新增「我的提示词」,支持本地管理、从公共库保存,并可在画布提示词库中直接使用。
+ [新增] Agent 新增统一生成任务状态查询,支持查看画布、生图工作台和视频工作台任务进度。
+ [调整] 内置提示词来源改为读取 Image Prompts 统一 JSON 数据,不再由画布端分别解析。
+ [调整] 提示词来源配置保留卡片式交互,并展示来源数量、同步状态和上次成功时间。
+ [优化] 提示词来源支持独立缓存和更新,更新失败时保留上一次成功内容。
+ [优化] Agent 对话区分用户与 AI 消息,并优化会话切换和回到最新消息的交互。
+ [优化] 画布生成完成后保留提示词输入,并改善提示词库和资产面板的交互性能。
+ [修复] 本地 Agent 完善多标签页请求隔离、结果归属、焦点回退和 Codex 会话状态同步。
+ [修复] 本地 Agent 上传的图片附件可正确创建画布图片节点并连接生成流程。
+ [修复] 画布提示词库支持跨来源搜索,插入节点时保留提示词标题。
## v0.9.0 - 2026-07-17
+ [新增] 左侧面板「资产」Tab 支持上传添加图片/视频资产、卡片悬停移除资产。
+ [新增] 左侧画布面板支持拖拽调整宽度、展开/收起(带动画),顶栏菜单左侧新增面板开关按钮。
+ [新增] 顶栏菜单新增「导出当前画布」,导出为包含全部资源的压缩包。
+ [新增] 左侧画布元素列表支持多选并批量导出选中元素为压缩包。
+ [新增] 可选的网站统计分析:支持 Google Analytics 4 与百度统计。
+ [调整] 画布节点名称默认不再显示,仅在选中/悬停/编辑时出现,画布更简洁。
+ [优化] 左侧面板「画布/资产」切换改为带滑动下划线的动画,移除非图片元素图标的灰色底色。
+ [优化] 画布节点提示词面板 `@` 引用图片时,输入框内直接显示真实缩略图。
+ [优化] 移除画布节点右上角的「图片1/文本1」资源角标,引用改在对话面板 `@` 直接选取。
+ [修复] 连接本地 Codex Agent 后,拖拽画布节点边框缩放等高频编辑导致页面崩溃。
## v0.8.2 - 2026-07-16
+ [新增] 图像设置新增「透明背景」开关,开启后生成无背景的透明图像。
+ [优化] 画布节点输入区域移除灰色底色与边框、美化样式。
+ [修复] 画布节点提示词输入框补上悬停文本光标。
## v0.8.1 - 2026-07-16
+ [新增] 插件 SDK 扩展:AI 生成能力、面板控制能力。
+ [优化] 3D 全景(1.1.0)支持上传与 AI 生成并升级查看器;
+ [优化] HTML 节点(1.2.0)迁移到统一交互开关;
+ [优化] 便利贴(1.1.0)可拖动移动、自选颜色、移除资源角标与衍生功能;
+ [优化] SVG 节点(1.1.0)透明背景融入画布、可拖动、去除默认值;
+ [优化] 画布节点渲染性能:memo 化节点回调,交互时不再全量重渲染;
+ [修复] 修复 Markdown 节点在点击/移动视角时重复渲染导致图片反复请求的问题;
+ [修复] 修复插件版本号显示不更新,插件面板新增可升级绿点提醒。
## v0.8.0 - 2026-07-15
+ [新增] 画布节点插件系统:支持通过 URL 动态安装/启用/更新/卸载远程节点插件。
+ [新增] 插件开发 SDK,可用 TypeScript 开发画布节点插件。
+ [新增] 新增 Markdown、SVG、HTML、3D 全景、便利贴等示例插件。
+ [新增] 官方插件注册表:节点插件面板可从项目仓库读取官方插件列表并一键安装。
+ [新增] 在画布右上角工具栏新增「节点插件」入口。
+ [新增] 支持自定义生图/视频接口调用方式以适配不同中转站。
## v0.7.1 - 2026-07-15
+ [修复] 修复页面白屏报错的问题。
## v0.7.0 - 2026-07-14
+ [新增] Agent 对话消息改用 streamdown 流式渲染,提升Markdown 内容展示效果。
+ [新增] Agent 新增画布、工作台、提示词库和素材等站点级工具。
+ [新增] Agent 面板改为全站常驻右侧栏,开关时同步推动顶栏和页面内容。
+ [新增] Agent 新增 `site_navigate` 工具,支持页面跳转。
+ [新增] Agent 对话运行中支持一键停止,中断当前 Codex turn。
+ [新增] 画布节点支持统一维护名称字段,默认显示在节点上方,并可直接双击名称编辑。
+ [新增] 画布新增组节点,支持节点拖入/拆出分组、拖拽高亮吸附和移动组时带动子节点。
+ [新增] 画布空白区域支持双击打开节点选择菜单,并在点击位置创建节点。
+ [调整] Codex 会话改为站点级连续线程,跨页面和跨画布保持同一上下文。
+ [调整] 移除仅前端调用 OpenAI responses 接口,统一走 MCP + 本地 Codex 链路。
+ [调整] 画布节点顶部工具条改为点击选中节点后显示,避免鼠标经过节点时频繁弹出。
+ [优化] 本地 Agent 连接说明明确区分插件 / 手动 MCP 才会增加 Codex token 消耗。
+ [优化] 优化本地 Agent 连接说明,区分 Codex 插件启动和直接运行 Agent 两种方式。
+ [修复] 修复 Gemini 格式生图时因内置比例列表触发误报导致生成失败的问题。
## v0.6.0 - 2026-07-09 ## v0.6.0 - 2026-07-09
+ [新增] 新增Codex App插件支持。 + [新增] 新增Codex App插件支持。
+2
View File
@@ -14,5 +14,7 @@ FROM nginx:1.27-alpine
COPY --from=web-build /app/web/dist /usr/share/nginx/html COPY --from=web-build /app/web/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf 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
EXPOSE 3000 EXPOSE 3000
+7 -25
View File
@@ -25,17 +25,19 @@
无限画布是一款面向图片创作的开源工作台。它把画布编排、AI 图片生成、参考图编辑、对话助手、提示词库和素材沉淀放在同一个界面里,适合用来探索视觉方案并连续迭代图片结果。 无限画布是一款面向图片创作的开源工作台。它把画布编排、AI 图片生成、参考图编辑、对话助手、提示词库和素材沉淀放在同一个界面里,适合用来探索视觉方案并连续迭代图片结果。
> [!CAUTION] > [!CAUTION]
> 项目目前处于开发阶段,不保证历史数据兼容。各种数据库结构和存储格式都可能直接调整,欢迎关注后续更新,当前更适合个人/本地部署,不建议直接公网多人共用。 > 项目目前处于开发阶段,不保证历史数据兼容。各种本地存储格式都可能直接调整,欢迎关注后续更新,当前更适合个人/本地部署,不建议直接公网多人共用。
> >
> 如果你需要稳定维护自己的分支,建议自行 fork 后独立开发。二次开发与 PR 请保留原作者信息和前端页面标识。 > 如果你需要稳定维护自己的分支,建议自行 fork 后独立开发。二次开发与 PR 请保留原作者信息和前端页面标识。
## 核心功能 ## 核心功能
- 无限画布:多画布项目、节点拖拽缩放、连线、小地图、撤销重做、导入导出。 - 无限画布:多画布项目、节点拖拽缩放、连线、小地图、撤销重做、导入导出。
- AI 创作:浏览器前台直连你配置的 OpenAI 兼容接口,支持文生图、图生图、参考图编辑、文本问答、音频和视频生成Seedance 2.0 可通过火山方舟 Agent Plan 接入 - AI 创作:浏览器前台直连你配置的 OpenAI 兼容接口,支持文生图、图生图、参考图编辑、文本问答、音频和视频生成。
- 画布助手:围绕选中节点和上游节点对话、生图,并把结果插回画布。 - 画布助手:围绕选中节点和上游节点对话、生图,并把结果插回画布。
- 本地 Agent:通过本机 Canvas Agent 连接 Codex / Claude Code,让 Agent 通过 MCP 操作当前画布; - 本地 Agent:通过本机 Canvas Agent 连接 Codex / Claude Code,让 Agent 通过 MCP 操作当前画布;
- Codex App 插件:提供 Codex app 插件,安装后会自动注册 MCP 并尝试拉起本地 Agent。 - Codex App 插件:提供 Codex app 插件,安装后会自动注册 MCP 并尝试拉起本地 Agent。
- 插件系统:支持通过 URL 动态安装 / 启用 / 更新 / 卸载远程节点插件,并提供 TypeScript SDK 自行开发画布节点插件。
- 自定义接口调用:可自定义生图 / 视频接口的调用方式,灵活适配各类中转站与自建服务。
- 提示词库:浏览器前端直连多个 GitHub 开源项目,并缓存到 IndexedDB。 - 提示词库:浏览器前端直连多个 GitHub 开源项目,并缓存到 IndexedDB。
完整功能说明见 [功能介绍](docs/content/docs/overview/features.mdx)。 完整功能说明见 [功能介绍](docs/content/docs/overview/features.mdx)。
@@ -68,16 +70,7 @@ docker compose up -d
首次打开后进入右上角配置,填入自己的 OpenAI 兼容 `Base URL``API Key` 首次打开后进入右上角配置,填入自己的 OpenAI 兼容 `Base URL``API Key`
## New API 自动配置 如果默认的OpenAI接口调用方式与您的API不同,可自定义生图/视频脚本调用。
如果使用 New API,可在 `系统设置 -> 聊天方式 -> 添加聊天设置` 中填入:
```text
https://canvas.best?apiKey={key}&baseUrl={address}
```
跳转后会自动打开配置弹窗并填入 API Key 和 Base URL。
如果自己部署了,可以把 `https://canvas.best` 替换成你部署的地址。
## 效果展示 ## 效果展示
@@ -108,20 +101,9 @@ https://canvas.best?apiKey={key}&baseUrl={address}
## 赞助支持 ## 赞助支持
<div align="center"> 本项目长期开放广告赞助合作,欢迎品牌 / 产品投放,你的支持是持续更新的动力!
如果这个项目对你有帮助,欢迎通过爱发电赞助支持,你的每一份鼓励都是持续更新的动力! 有广告赞助意向请通过上方联系方式沟通。
<br>
<a href="https://ifdian.net/a/basketikun">
<img src="https://img.shields.io/badge/%E7%88%B1%E5%8F%91%E7%94%B5-%E8%B5%9E%E5%8A%A9%E4%BD%9C%E8%80%85-946ce6?style=for-the-badge&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0id2hpdGUiPjxwYXRoIGQ9Ik0xMiAyMS4zNWwtMS40NS0xLjMyQzUuNCAxNS4zNiAyIDEyLjI4IDIgOC41IDIgNS40MiA0LjQyIDMgNy41IDNjMS43NCAwIDMuNDEuODEgNC41IDIuMDlDMTMuMDkgMy44MSAxNC43NiAzIDE2LjUgMyAxOS41OCAzIDIyIDUuNDIgMjIgOC41YzAgMy43OC0zLjQgNi44Ni04LjU1IDExLjU0TDEyIDIxLjM1eiIvPjwvc3ZnPg==&logoColor=white" alt="爱发电赞助" />
</a>
<br>
<br>
</div>
## 社区支持 ## 社区支持
+16
View File
@@ -31,6 +31,22 @@ Please include:
## Scope ## Scope
### Canvas node plugins
The canvas supports third-party node plugins loaded from a remote URL. By
design, an installed plugin's code runs directly inside the web app with full
access to the page, including locally stored data such as AI API keys. This is
an intentional trade-off for extensibility, and the installer shows a warning
before installing. Therefore:
- Only install plugins from sources you trust.
- Reports that a *malicious plugin* can access page data or API keys are **out
of scope** — that is the documented behavior of the trust model.
- Reports **in scope** include: the app loading/executing plugin code without
the install confirmation, a plugin escaping its declared node type to break
core app integrity in ways not implied by "runs in the page", or the plugin
source cache being writable by an unrelated origin.
Examples of in-scope reports: Examples of in-scope reports:
- Cross-site scripting or token exfiltration in the web app. - Cross-site scripting or token exfiltration in the web app.
+1 -1
View File
@@ -1 +1 @@
v0.6.0 v0.9.0
+16 -2
View File
@@ -40,6 +40,20 @@ Canvas Agent 默认只监听 `127.0.0.1`。网页第一次带正确 token 连接
如果希望 Codex 终端能直接操作画布,需要先把 Canvas Agent 注册成 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 插件
仓库内提供了 Codex app 插件:`plugins/infinite-canvas`。在 Codex app 中添加本仓库的 marketplace 后,可以安装 `Infinite Canvas` 插件;插件会注册同一个 `infinite-canvas` MCP,并带上画布操作说明。 仓库内提供了 Codex app 插件:`plugins/infinite-canvas`。在 Codex app 中添加本仓库的 marketplace 后,可以安装 `Infinite Canvas` 插件;插件会注册同一个 `infinite-canvas` MCP,并带上画布操作说明。
@@ -52,13 +66,13 @@ codex plugin marketplace add "$(pwd)"
codex plugin add infinite-canvas@infinite-canvas-local codex plugin add infinite-canvas@infinite-canvas-local
``` ```
插件默认通过 npm 启动 MCP 插件默认通过 npm 启动 MCP;这个命令只提供 MCP 工具,不会把 MCP 写入全局配置,也不会在退出时自动卸载
```bash ```bash
npx -y @basketikun/canvas-agent mcp npx -y @basketikun/canvas-agent mcp
``` ```
使用时可以直接在 Codex 里说“打开 Infinite Canvas”,插件会优先启动本地画布和本地 Agent,读取 Local URL 和 Connect token,然后直接打开画布网页地址新建并连接画布。如果自动连接失败,再检查本地画布服务和 Canvas Agent 是否都已启动 使用时可以直接在 Codex 里说“打开 Infinite Canvas”,插件会启动本地 Agent,读取 Local URL 和 Connect token,然后在右侧打开 `https://canvas.best/` 并自动新建、连接画布;只有明确要求使用本地项目时才会启动本地前端
Canvas Agent 启动后,给 Codex 添加 MCP Canvas Agent 启动后,给 Codex 添加 MCP
+2 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@basketikun/canvas-agent", "name": "@basketikun/canvas-agent",
"version": "0.1.0", "version": "0.2.0",
"type": "module", "type": "module",
"main": "./dist/index.js", "main": "./dist/index.js",
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
@@ -13,6 +13,7 @@
], ],
"scripts": { "scripts": {
"dev": "tsx src/index.ts", "dev": "tsx src/index.ts",
"test": "tsx --test src/canvas-session.test.ts",
"build": "tsc -p tsconfig.json", "build": "tsc -p tsconfig.json",
"start": "node dist/index.js", "start": "node dist/index.js",
"prepack": "npm run build" "prepack": "npm run build"
+67 -27
View File
@@ -11,11 +11,12 @@ import type { AgentAttachment, AgentEmit } from "./types.js";
type Json = Record<string, unknown>; type Json = Record<string, unknown>;
type AgentEvent = Json & { type: string; usage?: unknown }; type AgentEvent = Json & { type: string; usage?: unknown };
type PendingRequest = { resolve: (value: unknown) => void; reject: (error: Error) => void }; type PendingRequest = { resolve: (value: unknown) => void; reject: (error: Error) => void };
type CodexRunOptions = { threadId?: string; cwd?: string }; type CodexRunOptions = { threadId?: string; cwd?: string; appEmit?: AgentEmit; onStart?: () => void; onThread?: (threadId: string) => void; onTurn?: (turnId: string) => void; onFinish?: () => void };
type AgentHistoryMessage = { id: string; role: "user" | "assistant" | "tool" | "error"; title?: string; text: string; detail?: unknown; streamId?: string }; type AgentHistoryMessage = { id: string; role: "user" | "assistant" | "tool" | "error"; title?: string; text: string; detail?: unknown; streamId?: string };
let codexQueue: Promise<unknown> = Promise.resolve(); let codexQueue: Promise<unknown> = Promise.resolve();
let codexApp: CodexAppClient | null = null; let codexApp: CodexAppClient | null = null;
let codexAppStart: Promise<CodexAppClient> | null = null;
let codexThreadId = ""; let codexThreadId = "";
const canvasAgentMcp = canvasAgentMcpCommand(); const canvasAgentMcp = canvasAgentMcpCommand();
const require = createRequire(import.meta.url); const require = createRequire(import.meta.url);
@@ -30,47 +31,56 @@ export async function runCodexTurn(prompt: string, emit: AgentEmit, attachments:
await codexQueue; await codexQueue;
} }
export function interruptCodexTurn(threadId?: string) {
if (!codexApp || (threadId && threadId !== codexThreadId)) return false;
return codexApp.interruptCurrentTurn();
}
async function runCodexTurnNow(prompt: string, emit: AgentEmit, attachments: AgentAttachment[], options: CodexRunOptions) { async function runCodexTurnNow(prompt: string, emit: AgentEmit, attachments: AgentAttachment[], options: CodexRunOptions) {
let files: string[] = []; let files: string[] = [];
try { try {
options.onStart?.();
files = await writeAttachmentFiles(attachments); files = await writeAttachmentFiles(attachments);
codexApp ||= await CodexAppClient.start(emit); const app = await getCodexApp(options.appEmit || emit);
let threadId = await ensureCodexThread(codexApp, options, emit); let threadId = await ensureCodexThread(app, options, emit);
options.onThread?.(threadId);
try { try {
await codexApp.startTurn(threadId, prompt, files); await app.startTurn(threadId, prompt, files, options.onTurn);
} catch (error) { } catch (error) {
if (!isRecoverableThreadError(error)) throw error; if (!isRecoverableThreadError(error)) throw error;
emit("agent_log", { text: `Codex thread unavailable, starting a new thread: ${errorMessage(error)}` }); emit("agent_log", { text: `Codex thread unavailable, starting a new thread: ${errorMessage(error)}` });
codexThreadId = ""; codexThreadId = "";
threadId = await ensureCodexThread(codexApp, { cwd: options.cwd }, emit); threadId = await ensureCodexThread(app, { cwd: options.cwd }, emit);
await codexApp.startTurn(threadId, prompt, files); options.onThread?.(threadId);
await app.startTurn(threadId, prompt, files, options.onTurn);
} }
} catch (error) { } catch (error) {
emit("agent_error", { message: errorMessage(error) }); emit("agent_error", { message: errorMessage(error) });
} finally { } finally {
options.onFinish?.();
await Promise.all(files.map((file) => fs.unlink(file).catch(() => undefined))); await Promise.all(files.map((file) => fs.unlink(file).catch(() => undefined)));
} }
} }
export async function startCodexThread(emit: AgentEmit, cwd?: string) { export async function startCodexThread(emit: AgentEmit, cwd?: string) {
codexApp ||= await CodexAppClient.start(emit); const app = await getCodexApp(emit);
const thread = await codexApp.startThread(cwd); const thread = await app.startThread(cwd);
codexThreadId = String(field(thread, "id") || ""); codexThreadId = String(field(thread, "id") || "");
return thread; return thread;
} }
export async function resumeCodexThread(emit: AgentEmit, threadId: string, cwd?: string) { export async function resumeCodexThread(emit: AgentEmit, threadId: string, cwd?: string) {
codexApp ||= await CodexAppClient.start(emit); const app = await getCodexApp(emit);
await loadCodexThread(emit, threadId, cwd, false); await loadCodexThread(emit, threadId, cwd, false);
const thread = await codexApp.resumeThread(threadId, cwd); const thread = await app.resumeThread(threadId, cwd);
assertThreadWorkspace(thread, cwd); assertThreadWorkspace(thread, cwd);
codexThreadId = String(field(thread, "id") || threadId); codexThreadId = String(field(thread, "id") || threadId);
return { thread, messages: threadMessages(thread) }; return { thread, messages: threadMessages(thread) };
} }
export async function listCodexThreads(emit: AgentEmit, options: { cwd: string; searchTerm?: string; limit?: number }) { export async function listCodexThreads(emit: AgentEmit, options: { cwd: string; searchTerm?: string; limit?: number }) {
codexApp ||= await CodexAppClient.start(emit); const app = await getCodexApp(emit);
const result = await codexApp.listThreads({ const result = await app.listThreads({
limit: options.limit || 40, limit: options.limit || 40,
sortKey: "updated_at", sortKey: "updated_at",
sortDirection: "desc", sortDirection: "desc",
@@ -92,9 +102,9 @@ export async function verifyCodexThreadWorkspace(emit: AgentEmit, threadId: stri
} }
export async function archiveCodexThread(emit: AgentEmit, threadId: string, cwd?: string) { export async function archiveCodexThread(emit: AgentEmit, threadId: string, cwd?: string) {
codexApp ||= await CodexAppClient.start(emit); const app = await getCodexApp(emit);
await loadCodexThread(emit, threadId, cwd, false); await loadCodexThread(emit, threadId, cwd, false);
await codexApp.archiveThread(threadId); await app.archiveThread(threadId);
} }
export function runClaudeTurn(prompt: string, emit: AgentEmit) { export function runClaudeTurn(prompt: string, emit: AgentEmit) {
@@ -126,7 +136,7 @@ async function ensureCodexThread(app: CodexAppClient, options: CodexRunOptions,
return codexThreadId; return codexThreadId;
} }
function isRecoverableThreadError(error: unknown) { export function isRecoverableThreadError(error: unknown) {
return /thread not loaded|no rollout found/i.test(errorMessage(error)); return /thread not loaded|no rollout found/i.test(errorMessage(error));
} }
@@ -187,10 +197,11 @@ class CodexAppClient {
return this.request("thread/archive", { threadId }); return this.request("thread/archive", { threadId });
} }
async startTurn(threadId: string, prompt: string, images: string[]) { async startTurn(threadId: string, prompt: string, images: string[], onTurn?: (turnId: string) => void) {
const result = await this.request("turn/start", { threadId, input: codexInput(prompt, images), approvalPolicy: "never" }); const result = await this.request("turn/start", { threadId, input: codexInput(prompt, images), approvalPolicy: "never" });
const turnId = String(field(field(result, "turn"), "id") || ""); const turnId = String(field(field(result, "turn"), "id") || "");
if (!turnId) throw new Error("Codex app-server 没有返回 turn id"); if (!turnId) throw new Error("Codex app-server 没有返回 turn id");
onTurn?.(turnId);
const completed = this.completedTurns.get(turnId); const completed = this.completedTurns.get(turnId);
if (this.completedTurns.has(turnId)) { if (this.completedTurns.has(turnId)) {
this.completedTurns.delete(turnId); this.completedTurns.delete(turnId);
@@ -200,6 +211,16 @@ class CodexAppClient {
await new Promise((resolve, reject) => this.activeTurns.set(turnId, { resolve, reject })); await new Promise((resolve, reject) => this.activeTurns.set(turnId, { resolve, reject }));
} }
interruptCurrentTurn() {
if (this.activeTurns.size === 0) return false;
try {
this.child.kill("SIGINT");
return true;
} catch {
return false;
}
}
private request(method: string, params: unknown) { private request(method: string, params: unknown) {
const id = this.nextId++; const id = this.nextId++;
this.write({ id, method, params }); this.write({ id, method, params });
@@ -252,9 +273,9 @@ class CodexAppClient {
} else if (turnId) { } else if (turnId) {
this.completedTurns.set(turnId, error ? new Error(String(field(error, "message") || "Codex turn failed")) : null); this.completedTurns.set(turnId, error ? new Error(String(field(error, "message") || "Codex turn failed")) : null);
} }
this.emit("agent_event", { agent: "codex", type: "stream.summary", delta_count: this.deltaCount }); this.emit("agent_event", { agent: "codex", type: "stream.summary", delta_count: this.deltaCount, ...codexEventScope(params) });
this.deltaCount = 0; this.deltaCount = 0;
this.emit("agent_done", { agent: "codex", usage: event.usage }); this.emit("agent_done", { agent: "codex", usage: event.usage, ...codexEventScope(params) });
} }
} }
@@ -263,7 +284,7 @@ class CodexAppClient {
const text = `${this.textByItem.get(id) || ""}${String(field(params, "delta") || "")}`; const text = `${this.textByItem.get(id) || ""}${String(field(params, "delta") || "")}`;
this.deltaCount += 1; this.deltaCount += 1;
this.textByItem.set(id, text); this.textByItem.set(id, text);
this.emit("agent_event", { agent: "codex", type: "item.updated", item: { id, type: "agent_message", text } }); this.emit("agent_event", { agent: "codex", type: "item.updated", item: { id, type: "agent_message", text }, ...codexEventScope(params) });
} }
private answerServerRequest(message: Json) { private answerServerRequest(message: Json) {
@@ -306,23 +327,41 @@ function codexInput(prompt: string, images: string[]) {
} }
function normalizeCodexNotification(method: string, params: Json): AgentEvent | null { function normalizeCodexNotification(method: string, params: Json): AgentEvent | null {
if (method === "thread/started") return { type: "thread.started", thread_id: field(field(params, "thread"), "id") }; const scope = codexEventScope(params);
if (method === "turn/started") return { type: "turn.started" }; if (method === "thread/started") return { type: "thread.started", ...scope };
if (method === "turn/completed") return { type: "turn.completed", usage: null }; if (method === "turn/started") return { type: "turn.started", ...scope };
if (method === "item/started") return { type: "item.started", item: normalizeItem(field(params, "item")) }; if (method === "turn/completed") return { type: "turn.completed", usage: null, ...scope };
if (method === "item/completed") return { type: "item.completed", item: normalizeItem(field(params, "item")) }; if (method === "item/started") return { type: "item.started", item: normalizeItem(field(params, "item")), ...scope };
if (method === "error") return { type: "error", message: field(params, "message") }; if (method === "item/completed") return { type: "item.completed", item: normalizeItem(field(params, "item")), ...scope };
if (method === "error") return { type: "error", message: field(params, "message"), ...scope };
return null; return null;
} }
function codexEventScope(params: Json) {
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 } : {}) };
}
async function loadCodexThread(emit: AgentEmit, threadId: string, cwd: string | undefined, includeTurns: boolean) { async function loadCodexThread(emit: AgentEmit, threadId: string, cwd: string | undefined, includeTurns: boolean) {
codexApp ||= await CodexAppClient.start(emit); const app = await getCodexApp(emit);
const result = await codexApp.readThread(threadId, includeTurns); const result = await app.readThread(threadId, includeTurns);
const thread = field(result, "thread") || {}; const thread = field(result, "thread") || {};
assertThreadWorkspace(thread, cwd); assertThreadWorkspace(thread, cwd);
return thread; return thread;
} }
async function getCodexApp(emit: AgentEmit) {
if (codexApp) return codexApp;
codexAppStart ||= CodexAppClient.start(emit);
try {
codexApp = await codexAppStart;
return codexApp;
} finally {
codexAppStart = null;
}
}
function assertThreadWorkspace(thread: unknown, cwd?: string) { function assertThreadWorkspace(thread: unknown, cwd?: string) {
if (!cwd || threadInWorkspace(thread, cwd)) return; if (!cwd || threadInWorkspace(thread, cwd)) return;
throw new Error("该 Codex 会话不属于当前画布工作空间"); throw new Error("该 Codex 会话不属于当前画布工作空间");
@@ -443,6 +482,7 @@ function toolName(name: string) {
if (name === "canvas_get_state") return "读取画布"; if (name === "canvas_get_state") return "读取画布";
if (name === "canvas_get_selection") return "读取选区"; if (name === "canvas_get_selection") return "读取选区";
if (name === "canvas_export_snapshot") return "导出快照"; if (name === "canvas_export_snapshot") return "导出快照";
if (name === "canvas_create_attachment_nodes") return "添加附件图片";
if (name === "canvas_create_text_node") return "创建文本"; if (name === "canvas_create_text_node") return "创建文本";
if (name === "canvas_create_image_prompt_flow") return "创建生图流程"; if (name === "canvas_create_image_prompt_flow") return "创建生图流程";
if (name === "canvas_create_generation_flow") return "创建生成流程"; if (name === "canvas_create_generation_flow") return "创建生成流程";
+289
View File
@@ -0,0 +1,289 @@
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 "./canvas-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("画布写操作只发送给当前激活网页", 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<string, unknown>;
const nodes = input.nodes as Array<Record<string, unknown>>;
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<string>((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();
session.setCodexState({ busy: true, threadId: "thread-2", turnId: "turn-1" });
const client = connect(session, "first");
t.after(() => client.close());
assert.deepEqual(field(client.event("hello"), "codex"), { busy: true, threadId: "thread-2", turnId: "turn-1" });
session.setCodexState({ busy: false });
assert.deepEqual(client.event("codex_state"), { busy: false, threadId: "thread-2", turnId: "turn-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("closing the bound client falls back to the active client", 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();
assert.equal(field(await session.callTool("canvas_get_state", {}), "projectId"), "canvas-second");
const result = session.callTool("canvas_create_text_node", { text: "fallback" });
const call = second.event("tool_call");
session.resolveResult("second", { requestId: String(field(call, "requestId")), result: { ok: true } });
assert.deepEqual(await result, { ok: true });
});
function connect(session: CanvasSession, clientId: string) {
const response = new FakeSseResponse();
session.openEvents(new URL(`http://127.0.0.1/events?clientId=${clientId}`), response as unknown as ServerResponse);
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<string, unknown>)[key] : undefined;
}
class FakeSseResponse extends EventEmitter {
private chunks: string[] = [];
writeHead() {
return this;
}
write(chunk: string) {
this.chunks.push(chunk);
return true;
}
event(type: string) {
const chunk = this.chunks.find((item) => item.startsWith(`event: ${type}\n`));
const data = chunk?.split("\n").find((line) => line.startsWith("data: "))?.slice(6);
return data ? (JSON.parse(data) as unknown) : undefined;
}
close() {
this.emit("close");
}
}
+164 -13
View File
@@ -3,52 +3,165 @@ import type { ServerResponse } from "node:http";
import { type ToolName } from "./schemas.js"; import { type ToolName } from "./schemas.js";
import { compactCanvasState, compactNode, isToolName, nextCanvasX, parseToolInput } from "./tools.js"; import { compactCanvasState, compactNode, isToolName, nextCanvasX, parseToolInput } from "./tools.js";
import type { CanvasNode, CanvasNodeType, CanvasSnapshot } from "./types.js"; import type { AgentAttachment, CanvasNode, CanvasNodeType, CanvasSnapshot } from "./types.js";
type PendingRequest = { resolve: (value: unknown) => void; reject: (error: Error) => void }; 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 };
export type CodexState = { busy: boolean; threadId: string; turnId: string };
const SITE_TOOLS = new Set<ToolName>([
"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 { export class CanvasSession {
private clients = new Map<string, ServerResponse>(); private clients = new Map<string, ServerResponse>();
private clientFocusOrder = new Map<string, number>();
private pending = new Map<string, PendingRequest>(); private pending = new Map<string, PendingRequest>();
private canvasState: CanvasSnapshot | null = null; private canvasStates = new Map<string, CanvasSnapshot>();
private turnAttachments = new Map<string, TurnAttachment>();
private activeClientId = "";
private boundClientId = "";
private focusSequence = 0;
private codexState: CodexState = { busy: false, threadId: "", turnId: "" };
private get canvasState() {
return this.canvasStates.get(this.targetClientId) || null;
}
private get targetClientId() {
return this.boundClientId || this.activeClientId;
}
health() { health() {
return { ok: true, hasCanvas: Boolean(this.canvasState), clients: this.clients.size }; return { ok: true, hasCanvas: Boolean(this.canvasState), clients: this.clients.size, codexBusy: this.codexState.busy };
}
get codexBusy() {
return this.codexState.busy;
}
setCodexState(patch: Partial<CodexState>) {
this.codexState = { ...this.codexState, ...patch };
this.emitAll("codex_state", this.codexState);
} }
openEvents(url: URL, res: ServerResponse) { openEvents(url: URL, res: ServerResponse) {
const clientId = url.searchParams.get("clientId") || crypto.randomUUID(); const clientId = url.searchParams.get("clientId") || crypto.randomUUID();
const statusOnly = url.searchParams.get("role") === "status"; const statusOnly = url.searchParams.get("role") === "status";
res.writeHead(200, { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive" }); res.writeHead(200, { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive" });
if (!statusOnly) this.clients.set(clientId, res); if (!statusOnly) {
sendEvent(res, "hello", { ok: true, clientId }); 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, clientId, codex: this.codexState });
const timer = setInterval(() => sendEvent(res, "ping", { time: Date.now() }), 15000); const timer = setInterval(() => sendEvent(res, "ping", { time: Date.now() }), 15000);
res.on("close", () => { res.on("close", () => {
clearInterval(timer); clearInterval(timer);
if (!statusOnly) this.clients.delete(clientId); if (statusOnly || this.clients.get(clientId) !== res) return;
if (this.canvasState?.clientId === clientId) this.canvasState = null; this.clients.delete(clientId);
this.clientFocusOrder.delete(clientId);
this.canvasStates.delete(clientId);
if (this.boundClientId === clientId) this.boundClientId = "";
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] || "";
}); });
} }
updateState(body: unknown, clientId?: string) { updateState(body: unknown, clientId?: string) {
this.canvasState = { ...((body && typeof body === "object" && !Array.isArray(body) ? body : {}) as Record<string, unknown>), clientId } as CanvasSnapshot; const targetClientId = clientId || this.activeClientId;
if (!targetClientId) return;
this.canvasStates.set(targetClientId, { ...((body && typeof body === "object" && !Array.isArray(body) ? body : {}) as Record<string, unknown>), clientId: targetClientId } as CanvasSnapshot);
} }
resolveResult(body: { requestId?: string; error?: string; result?: unknown }) { activateClient(clientId: string) {
if (!this.clients.has(clientId)) throw new Error("当前网页未连接");
this.activeClientId = clientId;
this.clientFocusOrder.set(clientId, ++this.focusSequence);
}
bindClient(clientId: string) {
if (!this.clients.has(clientId)) throw new Error("当前网页未连接");
this.boundClientId = clientId;
}
releaseClient(clientId: string) {
if (this.boundClientId === clientId) this.boundClientId = "";
}
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 }];
});
}
clearTurnAttachments(clientId?: string) {
this.turnAttachments.forEach((item, id) => {
if (!clientId || item.clientId === clientId) this.turnAttachments.delete(id);
});
}
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; const item = body.requestId ? this.pending.get(body.requestId) : null;
if (!item || !body.requestId) return; if (!item || !body.requestId || item.clientId !== clientId) return false;
this.pending.delete(body.requestId); this.pending.delete(body.requestId);
body.error ? item.reject(new Error(body.error)) : item.resolve(body.result); body.error ? item.reject(new Error(body.error)) : item.resolve(body.result);
return true;
} }
emitAll(type: string, payload: unknown) { emitAll(type: string, payload: unknown) {
this.clients.forEach((client) => sendEvent(client, type, payload)); this.clients.forEach((client) => sendEvent(client, type, payload));
} }
emitThread(type: string, threadId: string, payload: Record<string, unknown> = {}) {
this.emitAll(type, { ...payload, threadId });
}
async callTool(name: unknown, rawInput: unknown) { async callTool(name: unknown, rawInput: unknown) {
if (!isToolName(name)) throw new Error(`未知工具:${String(name)}`); if (!isToolName(name)) throw new Error(`未知工具:${String(name)}`);
let tool: ToolName = name; let tool: ToolName = name;
let input = parseToolInput(tool, rawInput) as Record<string, unknown>; let input = parseToolInput(tool, rawInput) as Record<string, unknown>;
if (SITE_TOOLS.has(tool)) {
if (!this.clients.size) throw new Error("当前没有已连接网页");
return await this.requestCanvasTool(tool, input);
}
const readTool = ["canvas_get_state", "canvas_get_selection", "canvas_export_snapshot"].includes(tool); const readTool = ["canvas_get_state", "canvas_get_selection", "canvas_export_snapshot"].includes(tool);
if (readTool && (!this.clients.size || !this.canvasState)) throw new Error("当前没有已连接画布"); if (readTool && (!this.clients.size || !this.canvasState)) throw new Error("当前没有已连接画布");
if (tool === "canvas_get_state" || tool === "canvas_export_snapshot") return compactCanvasState(this.canvasState); if (tool === "canvas_get_state" || tool === "canvas_export_snapshot") return compactCanvasState(this.canvasState);
@@ -56,6 +169,7 @@ export class CanvasSession {
const ids = new Set(this.canvasState?.selectedNodeIds || []); const ids = new Set(this.canvasState?.selectedNodeIds || []);
return { nodes: (this.canvasState?.nodes || []).filter((node) => ids.has(node.id)).map(compactNode) }; return { nodes: (this.canvasState?.nodes || []).filter((node) => ids.has(node.id)).map(compactNode) };
} }
if (tool === "canvas_create_attachment_nodes") return await this.createAttachmentNodes(input as { attachmentIds: string[]; x?: number; y?: number; gap?: number; direction?: "row" | "column" });
if (tool === "canvas_create_node") { if (tool === "canvas_create_node") {
const data = input as { nodeType: CanvasNodeType; title?: string; x?: number; y?: number; width?: number; height?: number; metadata?: Record<string, unknown> }; const data = input as { nodeType: CanvasNodeType; title?: string; x?: number; y?: number; width?: number; height?: number; metadata?: Record<string, unknown> };
input = { ops: [{ type: "add_node", nodeType: data.nodeType, title: data.title, position: { x: data.x ?? nextCanvasX(this.canvasState), y: data.y ?? 0 }, width: data.width, height: data.height, metadata: data.metadata }] }; input = { ops: [{ type: "add_node", nodeType: data.nodeType, title: data.title, position: { x: data.x ?? nextCanvasX(this.canvasState), y: data.y ?? 0 }, width: data.width, height: data.height, metadata: data.metadata }] };
@@ -150,9 +264,36 @@ export class CanvasSession {
return await this.requestCanvasTool(tool, input); return await this.requestCanvasTool(tool, input);
} }
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<string, unknown>) { private async requestCanvasTool(name: ToolName, input: Record<string, unknown>) {
const requestId = crypto.randomUUID(); const requestId = crypto.randomUUID();
const client = this.clients.get(this.canvasState?.clientId || "") || this.clients.values().next().value; const clientId = this.targetClientId;
const client = this.clients.get(clientId);
if (!client) throw new Error("当前没有已连接画布"); if (!client) throw new Error("当前没有已连接画布");
sendEvent(client, "tool_call", { requestId, name, input }); sendEvent(client, "tool_call", { requestId, name, input });
return await new Promise((resolve, reject) => { return await new Promise((resolve, reject) => {
@@ -160,7 +301,7 @@ export class CanvasSession {
this.pending.delete(requestId); this.pending.delete(requestId);
reject(new Error("画布操作超时")); reject(new Error("画布操作超时"));
}, 30000); }, 30000);
this.pending.set(requestId, { resolve: (value) => (clearTimeout(timer), resolve(value)), reject: (error) => (clearTimeout(timer), reject(error)) }); this.pending.set(requestId, { clientId, resolve: (value) => (clearTimeout(timer), resolve(value)), reject: (error) => (clearTimeout(timer), reject(error)) });
}); });
} }
} }
@@ -247,3 +388,13 @@ function findNode(state: CanvasSnapshot | null, id: string): CanvasNode | undefi
function cleanRecord(value: Record<string, unknown>) { function cleanRecord(value: Record<string, unknown>) {
return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined && item !== "")); return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined && item !== ""));
} }
function positiveNumber(value: unknown, fallback: number) {
const number = Number(value);
return Number.isFinite(number) && number > 0 ? number : fallback;
}
function fitAttachmentNodeSize(width: number, height: number) {
const scale = Math.min(1, 640 / width, 640 / height);
return { width: width * scale, height: height * scale };
}
+15 -21
View File
@@ -7,10 +7,10 @@ export const DEFAULT_PORT = 17371;
export const CONFIG_DIR = path.join(os.homedir(), ".infinite-canvas"); export const CONFIG_DIR = path.join(os.homedir(), ".infinite-canvas");
export const CONFIG_FILE = path.join(CONFIG_DIR, "canvas-agent.json"); export const CONFIG_FILE = path.join(CONFIG_DIR, "canvas-agent.json");
export const VERSION = readPackageVersion(); export const VERSION = readPackageVersion();
export const AGENT_PROMPT = "你正在帮助用户操作 Infinite Canvas 网页画布。需要改动画布时优先使用已配置的 infinite-canvas MCP 工具:先 canvas_get_state 读取当前画布,再根据任务使用 canvas_create_text_node、canvas_generate_text、canvas_generate_image、canvas_generate_video、canvas_generate_audio、canvas_create_generation_flow、canvas_create_config_node、canvas_run_generation、canvas_update_node、canvas_connect_nodes 等通用工具;复杂批量改动再用 canvas_apply_ops,删除连线可用 delete_connections。需要生成内容时直接调用对应生成工具,不要绑定特定业务场景。不要模拟鼠标点击,不要要求用户手动复制 JSON。"; export const AGENT_PROMPT = "你正在帮助用户操作 Infinite Canvas 网站。切换网站页面用 site_navigate,可跳 / (首页)、/canvas (我的画布)、/canvas/:id (指定画布)、/image、/video、/prompts、/assets、/config。需要改动画布时优先使用已配置的 infinite-canvas MCP 工具:先 canvas_get_state 读取当前画布,再根据任务使用 canvas_create_text_node、canvas_generate_text、canvas_generate_image、canvas_generate_video、canvas_generate_audio、canvas_create_generation_flow、canvas_create_config_node、canvas_run_generation、canvas_update_node、canvas_connect_nodes 等通用工具;复杂批量改动再用 canvas_apply_ops,删除连线可用 delete_connections。本轮若有用户上传的图片附件,会同时给出 attachmentId;用户要求把附件放入画布或作为生成参考图时,必须先用 canvas_create_attachment_nodes 创建真实图片节点,再把返回的节点 ID 传给 canvas_create_generation_flow.referenceNodeIds,不要创建空图片占位节点。若当前不在画布页,画布工具会报错,需先用 site_navigate 打开画布。想了解或打开用户已有画布,用 canvas_list_projects 获取画布清单和 id,再用 site_navigate 跳 /canvas/:id 打开。生图工作台可用 workbench_image_get_config 看可选项、workbench_image_generate 填提示词并生成;视频创作台对应 workbench_video_get_config 与 workbench_video_generate;用 prompts_search 分页搜索提示词库;用 assets_list 查看「我的素材」、assets_add 新增文本或图片素材。需要生成内容时直接调用对应生成工具,不要绑定特定业务场景。不要模拟鼠标点击,不要要求用户手动复制 JSON。";
export type CanvasWorkspaceConfig = { workspacePath: string; activeThreadId?: string; pinnedThreadIds?: string[] }; export type SiteWorkspaceConfig = { workspacePath: string; activeThreadId?: string; pinnedThreadIds?: string[] };
export type CanvasAgentConfig = { url: string; token: string; origins?: string[]; canvases?: Record<string, CanvasWorkspaceConfig> }; export type CanvasAgentConfig = { url: string; token: string; origins?: string[]; workspace?: SiteWorkspaceConfig };
export function loadConfig(create = false): CanvasAgentConfig { export function loadConfig(create = false): CanvasAgentConfig {
try { try {
@@ -27,30 +27,28 @@ export function saveConfig(config: CanvasAgentConfig) {
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2)); fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
} }
export function ensureCanvasWorkspace(config: CanvasAgentConfig, canvasId: string) { export function ensureSiteWorkspace(config: CanvasAgentConfig) {
const id = safeSegment(canvasId || "default"); const current = config.workspace;
config.canvases ||= {};
const current = config.canvases[id];
if (current?.workspacePath) { if (current?.workspacePath) {
fs.mkdirSync(resolveWorkspacePath(current.workspacePath), { recursive: true }); const workspacePath = resolveWorkspacePath(current.workspacePath);
return { canvasId: id, ...current, workspacePath: resolveWorkspacePath(current.workspacePath) }; fs.mkdirSync(workspacePath, { recursive: true });
return { ...current, workspacePath };
} }
const workspacePath = path.join(CONFIG_DIR, "codex-workspaces", id); const workspacePath = path.join(CONFIG_DIR, "codex-workspaces", "site");
config.canvases[id] = { workspacePath }; config.workspace = { workspacePath };
fs.mkdirSync(workspacePath, { recursive: true }); fs.mkdirSync(workspacePath, { recursive: true });
saveConfig(config); saveConfig(config);
return { canvasId: id, workspacePath }; return { workspacePath };
} }
export function updateCanvasWorkspace(config: CanvasAgentConfig, canvasId: string, patch: Partial<CanvasWorkspaceConfig>) { export function updateSiteWorkspace(config: CanvasAgentConfig, patch: Partial<SiteWorkspaceConfig>) {
const current = ensureCanvasWorkspace(config, canvasId); const current = ensureSiteWorkspace(config);
const workspacePath = patch.workspacePath ? resolveWorkspacePath(patch.workspacePath) : current.workspacePath; const workspacePath = patch.workspacePath ? resolveWorkspacePath(patch.workspacePath) : current.workspacePath;
const next = { ...current, ...patch, workspacePath }; const next = { ...current, ...patch, workspacePath };
config.canvases ||= {}; config.workspace = { workspacePath: next.workspacePath, activeThreadId: next.activeThreadId, pinnedThreadIds: next.pinnedThreadIds };
config.canvases[current.canvasId] = { workspacePath: next.workspacePath, activeThreadId: next.activeThreadId, pinnedThreadIds: next.pinnedThreadIds };
fs.mkdirSync(workspacePath, { recursive: true }); fs.mkdirSync(workspacePath, { recursive: true });
saveConfig(config); saveConfig(config);
return { canvasId: current.canvasId, ...config.canvases[current.canvasId] }; return config.workspace;
} }
function resolveWorkspacePath(value: string) { function resolveWorkspacePath(value: string) {
@@ -59,10 +57,6 @@ function resolveWorkspacePath(value: string) {
return path.resolve(value); return path.resolve(value);
} }
function safeSegment(value: string) {
return value.replace(/[^a-zA-Z0-9._-]/g, "-").slice(0, 120) || "default";
}
function readPackageVersion() { function readPackageVersion() {
try { try {
const pkg = JSON.parse(fs.readFileSync(new URL("../package.json", import.meta.url), "utf8")) as { version?: string }; const pkg = JSON.parse(fs.readFileSync(new URL("../package.json", import.meta.url), "utf8")) as { version?: string };
+117 -31
View File
@@ -1,8 +1,8 @@
import express, { type NextFunction, type Request, type Response } from "express"; import express, { type NextFunction, type Request, type Response } from "express";
import { DEFAULT_PORT, ensureCanvasWorkspace, loadConfig, saveConfig, updateCanvasWorkspace, type CanvasAgentConfig } from "./config.js"; import { DEFAULT_PORT, ensureSiteWorkspace, loadConfig, saveConfig, updateSiteWorkspace, type CanvasAgentConfig } from "./config.js";
import { CanvasSession } from "./canvas-session.js"; import { CanvasSession } from "./canvas-session.js";
import { archiveCodexThread, listCodexThreads, readCodexThread, resumeCodexThread, runClaudeTurn, runCodexTurn, startCodexThread, summarizeCodexThread, verifyCodexThreadWorkspace, withAgentPrompt } from "./agents.js"; import { archiveCodexThread, interruptCodexTurn, isRecoverableThreadError, listCodexThreads, readCodexThread, resumeCodexThread, runClaudeTurn, runCodexTurn, startCodexThread, summarizeCodexThread, verifyCodexThreadWorkspace, withAgentPrompt } from "./agents.js";
import type { AgentAttachment } from "./types.js"; import type { AgentAttachment } from "./types.js";
export function startHttpServer() { export function startHttpServer() {
@@ -12,7 +12,16 @@ export function startHttpServer() {
saveConfig(config); saveConfig(config);
const session = new CanvasSession(); const session = new CanvasSession();
const emit = (type: string, payload: unknown) => session.emitAll(type, payload); const emit = (type: string, payload: unknown) => {
const data = payload && typeof payload === "object" && !Array.isArray(payload) ? payload as Record<string, unknown> : { value: payload };
const threadId = String(data.threadId || data.thread_id || ensureSiteWorkspace(config).activeThreadId || "");
threadId ? session.emitThread(type, threadId, data) : session.emitAll(type, data);
};
const setActiveThread = (activeThreadId: string, payload: Record<string, unknown> = {}) => {
const workspace = updateSiteWorkspace(config, { activeThreadId: activeThreadId || undefined });
session.emitThread("workspace_changed", activeThreadId, { ...payload, activeThreadId });
return workspace;
};
const app = express(); const app = express();
app.disable("x-powered-by"); app.disable("x-powered-by");
app.use(express.json({ limit: "30mb" })); app.use(express.json({ limit: "30mb" }));
@@ -33,61 +42,130 @@ export function startHttpServer() {
session.updateState(req.body, String(req.query.clientId || "") || undefined); session.updateState(req.body, String(req.query.clientId || "") || undefined);
res.json({ ok: true }); res.json({ ok: true });
}); });
app.post("/canvas/result", (req, res) => { app.post("/canvas/activate", (req, res) => {
session.resolveResult(req.body); session.activateClient(String(req.query.clientId || ""));
res.json({ ok: true }); res.json({ ok: true });
}); });
app.post("/canvas/result", (req, res) => {
const ok = session.resolveResult(String(req.query.clientId || ""), req.body);
res.status(ok ? 200 : 409).json({ ok });
});
app.get("/agent/attachments/:attachmentId", route(async (req, res) => {
const attachment = session.getTurnAttachment(String(req.query.clientId || ""), routeParam(req.params.attachmentId));
const data = attachment.dataUrl.split(",", 2)[1];
if (!data) throw new Error("图片附件内容无效");
res.setHeader("Cache-Control", "no-store");
res.type(attachment.type).send(Buffer.from(data, "base64"));
}));
app.post("/api/tools", route(async (req, res) => res.json({ ok: true, result: await session.callTool(req.body?.name, req.body?.input || {}) }))); app.post("/api/tools", route(async (req, res) => res.json({ ok: true, result: await session.callTool(req.body?.name, req.body?.input || {}) })));
app.get("/agent/codex/workspace", (req, res) => { app.get("/agent/codex/workspace", (_req, res) => {
const workspace = ensureCanvasWorkspace(config, String(req.query.canvasId || "")); const workspace = ensureSiteWorkspace(config);
res.json({ ok: true, workspace }); res.json({ ok: true, workspace });
}); });
app.get("/agent/codex/threads", route(async (req, res) => { app.get("/agent/codex/threads", route(async (req, res) => {
const workspace = ensureCanvasWorkspace(config, String(req.query.canvasId || "")); const workspace = ensureSiteWorkspace(config);
const result = await listCodexThreads(emit, { cwd: workspace.workspacePath, searchTerm: String(req.query.searchTerm || "") }); const result = await listCodexThreads(emit, { cwd: workspace.workspacePath, searchTerm: String(req.query.searchTerm || "") });
res.json({ ok: true, workspace, ...result }); res.json({ ok: true, workspace, ...result });
})); }));
app.post("/agent/codex/threads/new", route(async (req, res) => { app.post("/agent/codex/threads/new", route(async (_req, res) => {
const workspace = ensureCanvasWorkspace(config, String(req.body?.canvasId || "")); if (session.codexBusy) return res.status(409).json({ ok: false, error: "Codex 正在运行,请等待当前任务完成" });
const workspace = ensureSiteWorkspace(config);
const thread = await startCodexThread(emit, workspace.workspacePath); const thread = await startCodexThread(emit, workspace.workspacePath);
const activeThreadId = String((thread as Record<string, unknown>).id || ""); const activeThreadId = String((thread as Record<string, unknown>).id || "");
updateCanvasWorkspace(config, workspace.canvasId, { activeThreadId }); const nextWorkspace = setActiveThread(activeThreadId, { emptyThread: true });
res.json({ ok: true, workspace: { ...workspace, activeThreadId }, thread: summarizeCodexThread(thread), messages: [] }); res.json({ ok: true, workspace: nextWorkspace, thread: summarizeCodexThread(thread), messages: [] });
})); }));
app.get("/agent/codex/threads/:threadId", route(async (req, res) => { app.get("/agent/codex/threads/:threadId", route(async (req, res) => {
const workspace = ensureCanvasWorkspace(config, String(req.query.canvasId || "")); const workspace = ensureSiteWorkspace(config);
const threadId = routeParam(req.params.threadId); const threadId = routeParam(req.params.threadId);
res.json({ ok: true, workspace, ...(await readCodexThread(emit, threadId, workspace.workspacePath)) }); try {
res.json({ ok: true, workspace, ...(await readCodexThread(emit, threadId, workspace.workspacePath)) });
} catch (error) {
if (workspace.activeThreadId !== threadId || !isRecoverableThreadError(error)) throw error;
res.json({ ok: true, workspace, thread: { id: threadId, preview: "", cwd: workspace.workspacePath }, messages: [] });
}
})); }));
app.post("/agent/codex/threads/:threadId/resume", route(async (req, res) => { app.post("/agent/codex/threads/:threadId/resume", route(async (req, res) => {
const workspace = ensureCanvasWorkspace(config, String(req.body?.canvasId || "")); if (session.codexBusy) return res.status(409).json({ ok: false, error: "Codex 正在运行,请等待当前任务完成" });
const workspace = ensureSiteWorkspace(config);
const threadId = routeParam(req.params.threadId); const threadId = routeParam(req.params.threadId);
const result = await resumeCodexThread(emit, threadId, workspace.workspacePath); const result = await resumeCodexThread(emit, threadId, workspace.workspacePath);
updateCanvasWorkspace(config, workspace.canvasId, { activeThreadId: threadId }); const nextWorkspace = setActiveThread(threadId);
res.json({ ok: true, workspace: { ...workspace, activeThreadId: threadId }, ...result }); res.json({ ok: true, workspace: nextWorkspace, ...result });
})); }));
app.post("/agent/codex/threads/:threadId/delete", route(async (req, res) => { app.post("/agent/codex/threads/:threadId/delete", route(async (req, res) => {
const workspace = ensureCanvasWorkspace(config, String(req.body?.canvasId || "")); if (session.codexBusy) return res.status(409).json({ ok: false, error: "Codex 正在运行,请等待当前任务完成" });
const workspace = ensureSiteWorkspace(config);
const threadId = routeParam(req.params.threadId); const threadId = routeParam(req.params.threadId);
await archiveCodexThread(emit, threadId, workspace.workspacePath); await archiveCodexThread(emit, threadId, workspace.workspacePath);
if (workspace.activeThreadId === threadId) updateCanvasWorkspace(config, workspace.canvasId, { activeThreadId: undefined }); setActiveThread(workspace.activeThreadId === threadId ? "" : workspace.activeThreadId || "");
res.json({ ok: true }); res.json({ ok: true });
})); }));
app.post("/agent/codex/turn", route(async (req, res) => { app.post("/agent/codex/turn", route(async (req, res) => {
if (session.codexBusy) return res.status(409).json({ ok: false, error: "Codex 正在运行,请等待当前任务完成" });
const attachments = Array.isArray(req.body?.attachments) ? (req.body.attachments as AgentAttachment[]) : []; const attachments = Array.isArray(req.body?.attachments) ? (req.body.attachments as AgentAttachment[]) : [];
const workspace = ensureCanvasWorkspace(config, String(req.body?.canvasId || "")); const workspace = ensureSiteWorkspace(config);
let threadId = String(req.body?.threadId || workspace.activeThreadId || ""); const prompt = String(req.body?.prompt || "");
if (!threadId) { if (!prompt.trim()) return res.status(400).json({ ok: false, error: "请输入任务内容" });
const thread = await startCodexThread(emit, workspace.workspacePath); const clientId = String(req.body?.clientId || "");
threadId = String((thread as Record<string, unknown>).id || ""); session.setCodexState({ busy: true, threadId: String(req.body?.threadId || workspace.activeThreadId || ""), turnId: "" });
updateCanvasWorkspace(config, workspace.canvasId, { activeThreadId: threadId }); try {
} else if (threadId !== workspace.activeThreadId) { let threadId = String(req.body?.threadId || workspace.activeThreadId || "");
await verifyCodexThreadWorkspace(emit, threadId, workspace.workspacePath); let turnId = "";
updateCanvasWorkspace(config, workspace.canvasId, { activeThreadId: threadId }); if (!threadId) {
const thread = await startCodexThread(emit, workspace.workspacePath);
threadId = String((thread as Record<string, unknown>).id || "");
setActiveThread(threadId, { emptyThread: true });
} else if (threadId !== workspace.activeThreadId) {
await verifyCodexThreadWorkspace(emit, threadId, workspace.workspacePath);
setActiveThread(threadId);
}
const attachmentRefs = session.setTurnAttachments(clientId, attachments);
const chatMessage = {
sourceClientId: clientId,
message: { id: String(req.body?.messageId || Date.now()), role: "user", text: String(req.body?.messageText || prompt || `发送了 ${attachments.length} 张图片`) },
};
let chatThreadId = "";
const turnEmit = (type: string, payload: unknown) => {
const data = payload && typeof payload === "object" && !Array.isArray(payload) ? payload as Record<string, unknown> : { value: payload };
session.emitThread(type, threadId, data);
};
void runCodexTurn(withAgentPrompt(withAttachmentContext(prompt, attachmentRefs)), turnEmit, attachments, {
threadId,
cwd: workspace.workspacePath,
appEmit: emit,
onStart: clientId ? () => session.bindClient(clientId) : undefined,
onThread: (actualThreadId) => {
if (actualThreadId !== threadId) {
threadId = actualThreadId;
setActiveThread(threadId, { emptyThread: true });
}
session.setCodexState({ busy: true, threadId, turnId: "" });
if (chatThreadId !== threadId) {
chatThreadId = threadId;
session.emitThread("chat_message", threadId, chatMessage);
}
},
onTurn: (actualTurnId) => {
turnId = actualTurnId;
session.setCodexState({ busy: true, threadId, turnId });
},
onFinish: () => {
session.clearTurnAttachments(clientId);
if (clientId) session.releaseClient(clientId);
session.setCodexState({ busy: false, threadId, turnId });
},
});
res.json({ ok: true, threadId });
} catch (error) {
session.setCodexState({ busy: false, threadId: String(req.body?.threadId || workspace.activeThreadId || ""), turnId: "" });
throw error;
} }
void runCodexTurn(withAgentPrompt(String(req.body?.prompt || "")), emit, attachments, { threadId, cwd: workspace.workspacePath });
res.json({ ok: true, threadId });
})); }));
app.post("/agent/codex/interrupt", (req, res) => {
const ok = interruptCodexTurn(String(req.body?.threadId || ""));
res.json({ ok });
});
app.post("/agent/claude/turn", (req, res) => { app.post("/agent/claude/turn", (req, res) => {
runClaudeTurn(withAgentPrompt(String(req.body?.prompt || "")), emit); runClaudeTurn(withAgentPrompt(String(req.body?.prompt || "")), emit);
res.json({ ok: true }); res.json({ ok: true });
@@ -99,7 +177,9 @@ export function startHttpServer() {
console.log("Infinite Canvas Agent"); console.log("Infinite Canvas Agent");
console.log(`Local URL: ${config.url}`); console.log(`Local URL: ${config.url}`);
console.log(`Connect token: ${config.token}`); console.log(`Connect token: ${config.token}`);
console.log("Codex MCP: codex mcp add infinite-canvas -- npx -y @basketikun/canvas-agent mcp"); console.log("Codex MCP is not installed by this command.");
console.log("Optional MCP add: codex mcp add infinite-canvas -- npx -y @basketikun/canvas-agent mcp");
console.log("Remove manually added MCP: codex mcp remove infinite-canvas");
}); });
} }
@@ -135,3 +215,9 @@ function validToken(req: Request, url: URL, token: string) {
const header = req.headers["x-canvas-agent-token"]; const header = req.headers["x-canvas-agent-token"];
return url.searchParams.get("token") === token || header === token || (Array.isArray(header) && header.includes(token)); return url.searchParams.get("token") === token || header === token || (Array.isArray(header) && header.includes(token));
} }
function withAttachmentContext(prompt: string, attachments: Array<{ id: string; name: string }>) {
if (!attachments.length) return prompt;
const list = attachments.map((item, index) => `${index + 1}. attachmentId=${item.id}, name=${JSON.stringify(item.name)}`).join("\n");
return `${prompt}\n\n本轮可用图片附件(顺序与图片输入一致):\n${list}\n需要把附件放入画布或作为生成参考图时,先调用 canvas_create_attachment_nodes,再使用返回的画布节点 ID 创建生成流程。`;
}
+33
View File
@@ -7,11 +7,14 @@ const nodeTypeSchema = z.enum(["image", "text", "config", "video", "audio"]);
const generationModeSchema = z.enum(["text", "image", "video", "audio"]); const generationModeSchema = z.enum(["text", "image", "video", "audio"]);
export const toolNames = [ export const toolNames = [
"site_navigate",
"canvas_list_projects",
"canvas_get_state", "canvas_get_state",
"canvas_get_selection", "canvas_get_selection",
"canvas_export_snapshot", "canvas_export_snapshot",
"canvas_apply_ops", "canvas_apply_ops",
"canvas_create_node", "canvas_create_node",
"canvas_create_attachment_nodes",
"canvas_create_text_node", "canvas_create_text_node",
"canvas_create_text_nodes", "canvas_create_text_nodes",
"canvas_create_config_node", "canvas_create_config_node",
@@ -30,6 +33,14 @@ export const toolNames = [
"canvas_select_nodes", "canvas_select_nodes",
"canvas_set_viewport", "canvas_set_viewport",
"canvas_run_generation", "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; ] as const;
export type ToolName = (typeof toolNames)[number]; export type ToolName = (typeof toolNames)[number];
@@ -77,11 +88,14 @@ const generationFlowSchema = z.object({
}); });
export const toolInputSchemas = { 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_state: z.object({}).passthrough(),
canvas_get_selection: z.object({}).passthrough(), canvas_get_selection: z.object({}).passthrough(),
canvas_export_snapshot: z.object({}).passthrough(), canvas_export_snapshot: z.object({}).passthrough(),
canvas_apply_ops: z.object({ ops: z.array(canvasOpSchema) }), 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_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_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_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_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),
@@ -100,14 +114,25 @@ export const toolInputSchemas = {
canvas_select_nodes: z.object({ ids: z.array(z.string()) }), canvas_select_nodes: z.object({ ids: z.array(z.string()) }),
canvas_set_viewport: z.object({ viewport: viewportSchema }), canvas_set_viewport: z.object({ viewport: viewportSchema }),
canvas_run_generation: z.object({ nodeId: z.string(), mode: generationModeSchema.optional(), prompt: z.string().optional() }), 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<ToolName, z.AnyZodObject>; } satisfies Record<ToolName, z.AnyZodObject>;
export const toolDescriptions: Record<ToolName, string> = { export const toolDescriptions: Record<ToolName, string> = {
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_state: "读取当前网页画布的节点、连线、选区和视口。",
canvas_get_selection: "读取当前网页画布选中的节点。", canvas_get_selection: "读取当前网页画布选中的节点。",
canvas_export_snapshot: "导出当前画布快照,用于理解布局。", canvas_export_snapshot: "导出当前画布快照,用于理解布局。",
canvas_apply_ops: "批量操作当前网页画布。ops 支持 add_node、update_node、delete_node、delete_connections、connect_nodes、set_viewport、select_nodes、run_generation。", 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_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_node: "在当前画布创建单个文本节点。",
canvas_create_text_nodes: "批量创建文本节点,适合生成标题、段落、脚本、说明等内容块。", canvas_create_text_nodes: "批量创建文本节点,适合生成标题、段落、脚本、说明等内容块。",
canvas_create_config_node: "创建生成配置节点,可指定 text/image/video/audio 模式和生成参数,可选择立即触发生成。", canvas_create_config_node: "创建生成配置节点,可指定 text/image/video/audio 模式和生成参数,可选择立即触发生成。",
@@ -126,4 +151,12 @@ export const toolDescriptions: Record<ToolName, string> = {
canvas_select_nodes: "设置当前选中节点。", canvas_select_nodes: "设置当前选中节点。",
canvas_set_viewport: "调整画布视口。", canvas_set_viewport: "调整画布视口。",
canvas_run_generation: "触发指定节点生成,通常用于配置节点或文本/图片/视频/音频节点。", 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)、countrun 默认 true 会自动点击生成按钮。会自动跳转到生图工作台。生成为异步过程,提交后返回 taskId,可用 generation_get_status 查询状态。",
workbench_video_get_config: "读取视频创作台的当前参数和可选项(可用模型、尺寸/比例、时长、清晰度/分辨率、是否生成声音与水印)。",
workbench_video_generate: "在视频创作台填入提示词并按需设置 model、size、seconds、resolution、generateAudio、watermarkrun 默认 true 会自动点击生成按钮。会自动跳转到视频创作台。生成为异步过程,提交后返回 taskId,可用 generation_get_status 查询状态。",
prompts_search: "搜索提示词库(第三方提示词合集),支持 keyword、category、tags 过滤和 page/pageSize 分页,返回标题、提示词、分类、标签、封面等。",
assets_list: "列出用户「我的素材」,支持 kindtext/image/video)过滤、keyword 搜索和 page/pageSize 分页。为控制体积不返回图片/视频原始 data,仅返回封面与元信息。",
assets_add: "向「我的素材」新增素材。kind=text 时用 content 传文本内容;kind=image 时用 imageUrl 传图片地址或 dataURL。可附带 title、tags、source、note。",
}; };
+1 -1
View File
@@ -5,4 +5,4 @@ export type CanvasNode = { id: string; type: CanvasNodeType; title?: string; pos
export type CanvasConnection = { id: string; fromNodeId: string; toNodeId: string }; 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 }; export type CanvasSnapshot = { projectId?: string; title?: string; nodes?: CanvasNode[]; connections?: CanvasConnection[]; selectedNodeIds?: string[]; viewport?: Viewport; clientId?: string };
export type AgentEmit = (type: string, payload: unknown) => void; export type AgentEmit = (type: string, payload: unknown) => void;
export type AgentAttachment = { name?: string; type?: string; dataUrl?: string }; export type AgentAttachment = { id?: string; name?: string; type?: string; size?: number; width?: number; height?: number; dataUrl?: string };
+2 -1
View File
@@ -10,5 +10,6 @@
"rootDir": "src", "rootDir": "src",
"skipLibCheck": true "skipLibCheck": true
}, },
"include": ["src/**/*.ts"] "include": ["src/**/*.ts"],
"exclude": ["src/**/*.test.ts"]
} }
+4
View File
@@ -5,3 +5,7 @@ services:
ports: ports:
- "3000:3000" - "3000:3000"
restart: unless-stopped restart: unless-stopped
# 统计分析(可选,默认关闭)。每家一个独立变量,填了谁就启用谁,可同时启用多家:
# environment:
# ANALYTICS_GA4_ID: G-XXXXXXXXXX # Google Analytics 4 衡量 ID
# ANALYTICS_BAIDU_ID: xxxxxxxxxxxxxxxxxxxxxxxxxxxx # 百度统计站点 ID
@@ -24,6 +24,7 @@ description: 当前画布节点的主要用途与操作流程
- 当文本节点为空时,输入框用于填写想生成的文本内容;点击发送后,结果会回填到当前文本节点。 - 当文本节点为空时,输入框用于填写想生成的文本内容;点击发送后,结果会回填到当前文本节点。
- 当文本节点已有内容时,输入框用于填写想把本段文本修改成什么;点击发送后,会在右侧生成新的文本节点,并自动连接原节点和新节点。 - 当文本节点已有内容时,输入框用于填写想把本段文本修改成什么;点击发送后,会在右侧生成新的文本节点,并自动连接原节点和新节点。
- 输入内容可以手写,也可以从提示词库选择。 - 输入内容可以手写,也可以从提示词库选择。
- 输入框支持 `@` 引用已连接的图片、文本、视频、音频资源;`@` 引用图片时会在输入框内直接显示真实缩略图,发送时按当前连接自动编号交给模型。因此不需要再记忆「图片1 / 文本1」编号,画布节点右上角也不再显示资源角标。
- 对话框里的模型下拉来自全局配置里已拉取的模型列表;选择结果只作用于当前节点,不会修改其它节点或全局默认模型。 - 对话框里的模型下拉来自全局配置里已拉取的模型列表;选择结果只作用于当前节点,不会修改其它节点或全局默认模型。
- 如果下拉中没有模型,需要先打开配置弹窗拉取模型列表,并设置默认生图模型和默认文本模型。火山方舟 Agent Plan 若提示不支持 `/models`,请手动填写模型名。 - 如果下拉中没有模型,需要先打开配置弹窗拉取模型列表,并设置默认生图模型和默认文本模型。火山方舟 Agent Plan 若提示不支持 `/models`,请手动填写模型名。
@@ -5,7 +5,7 @@ description: 画布本地存储、节点结构、媒体文件与清理机制
# 画布数据结构 # 画布数据结构
本文档说明当前画布在前端本地保存的数据结构、图片文件的存储和清理方式,以及后续接入后端存储时建议保持的兼容边界 本文档说明当前画布在前端本地保存的数据结构、图片文件的存储和清理方式。
## 当前存储位置 ## 当前存储位置
@@ -4,6 +4,7 @@
"defaultOpen": true, "defaultOpen": true,
"pages": [ "pages": [
"local-development", "local-development",
"canvas-data-structure" "canvas-data-structure",
"local-codex-canvas"
] ]
} }
+1 -1
View File
@@ -4,7 +4,7 @@
"pages": [ "pages": [
"overview", "overview",
"canvas", "canvas",
"backend", "development",
"progress", "progress",
"business", "business",
"support" "support"
@@ -88,7 +88,7 @@ codex plugin add infinite-canvas@infinite-canvas-local
打开 Infinite Canvas 新建画布 并连接好Codex 打开 Infinite Canvas 新建画布 并连接好Codex
``` ```
插件会优先确认当前仓库的本地画布服务是否已经运行。如果端口被占用,它会检查监听进程是否属于当前项目,避免误连到其他项目。确认或启动画布后,插件会启动本地 Canvas Agent,并打开带连接参数的新建画布 URL 插件会启动本地 Canvas Agent,读取连接地址和 token,然后在右侧打开 `https://canvas.best/` 并自动新建、连接画布。只有明确要求使用本地项目时,插件才会启动本地前端
画布打开后可以继续让 Codex 操作画布: 画布打开后可以继续让 Codex 操作画布:
@@ -125,10 +125,10 @@ bun install
bun run dev bun run dev
``` ```
然后启动本地 Agent。端口不是 `3000` 时,把 `CANVAS_URL` 换成真实本地画布地址 然后启动本地 Agent
```bash ```bash
CANVAS_URL=http://localhost:3000 npx -y @basketikun/canvas-agent npx -y @basketikun/canvas-agent
``` ```
从 Agent 输出或 `http://127.0.0.1:17371/config` 读取本地地址和 token 后,打开: 从 Agent 输出或 `http://127.0.0.1:17371/config` 读取本地地址和 token 后,打开:
+39
View File
@@ -50,3 +50,42 @@ docker compose -f docker-compose.local.yml up -d --build
## 数据说明 ## 数据说明
当前主应用镜像只启动 Next.js。画布、我的素材、生成记录和 AI API Key 默认保存在浏览器本地;第三方提示词由浏览器前端直连拉取,不需要额外挂载数据目录。 当前主应用镜像只启动 Next.js。画布、我的素材、生成记录和 AI API Key 默认保存在浏览器本地;第三方提示词由浏览器前端直连拉取,不需要额外挂载数据目录。
## 统计分析(可选)
项目**默认不接入任何统计**:不设置下列环境变量时,页面不会加载任何统计脚本,也不会发送任何外部请求。你在自己的部署上想查看流量时,设置对应环境变量即可,无需修改代码或重新构建镜像——镜像启动时会据此生成运行期配置。
统计按「每家一个独立变量」配置:**填了谁就启用谁,可同时启用多家**(例如 GA4 + 百度一起开)。仅支持 GA4 与百度,两者都只接受 ID,脚本地址由代码固定拼接,不接受任意脚本地址或内联 JS。
| 变量 | 说明 |
| --- | --- |
| `ANALYTICS_GA4_ID` | Google Analytics 4 衡量 ID`G-XXXX` |
| `ANALYTICS_BAIDU_ID` | 百度统计站点 ID`hm.js?` 后面那串 hash |
在 `docker-compose.yml` 的 `environment` 中配置(文件内已附注释示例),下例同时启用 GA4 与百度:
```yaml
services:
app:
image: ghcr.io/basketikun/infinite-canvas:latest
ports:
- "3000:3000"
environment:
ANALYTICS_GA4_ID: G-XXXXXXXXXX
ANALYTICS_BAIDU_ID: e8263761cc5c32f5f7a855b8203b965a
```
或使用 `docker run`
```bash
docker run -d -p 3000:3000 \
-e ANALYTICS_GA4_ID=G-XXXXXXXXXX \
-e ANALYTICS_BAIDU_ID=e8263761cc5c32f5f7a855b8203b965a \
ghcr.io/basketikun/infinite-canvas:latest
```
单页应用的路由切换会自动向所有已启用的统计上报 pageview。
<Callout type="warn">
请只在你自己的站点上配置统计 ID,不要把 ID 提交进仓库;fork 或自托管本项目的用户默认不会产生任何统计。
</Callout>
+15
View File
@@ -85,9 +85,12 @@ Base URL 如果已经以 `/v1`、`/api/v3` 或 `/api/plan/v3` 结尾,系统不
- 生成数量。 - 生成数量。
- 视频模型。 - 视频模型。
- 视频比例、清晰度、时长、生成声音和水印。 - 视频比例、清晰度、时长、生成声音和水印。
- 自定义生图 / 视频接口的调用方式,灵活适配不同中转站与自建服务。
普通图片/文本节点可以直接输入提示词生成结果。生成配置节点可以读取上游节点内容,并按节点自己的配置批量生成多个图片或文本结果。生成配置节点支持预览当前提示词和参考图输入,并调整输入顺序。 普通图片/文本节点可以直接输入提示词生成结果。生成配置节点可以读取上游节点内容,并按节点自己的配置批量生成多个图片或文本结果。生成配置节点支持预览当前提示词和参考图输入,并调整输入顺序。
节点下方对话框和组装提示词输入框都支持 `@` 引用已连接的图片、文本、视频、音频等资源;`@` 引用图片时,输入框内会直接显示该图片的真实缩略图,而不再是「图片1」这类文字编号,发送时会按当前连接自动编号交给模型理解。由于引用改在对话框内直接 `@`,画布节点右上角不再显示「图片1 / 文本1」资源角标。
视频生成可从文本节点读取 prompt,从图片节点读取参考图,从视频节点读取参考视频,从音频节点读取参考音频。Seedance 2.0 支持最多 9 张参考图、3 个参考视频、3 个参考音频;分辨率支持 `480p`、`720p`、`1080p`fast 模型不支持 `1080p`),比例支持 `16:9`、`4:3`、`1:1`、`3:4`、`9:16`、`21:9`、`adaptive`,时长支持 4-15 秒或智能时长。生成成功后会把视频插入画布为视频节点并使用原生播放器预览。参考视频和参考音频优先使用公网可访问 URL;本地素材会以前端可读取的数据传给兼容接口,是否支持取决于具体上游。 视频生成可从文本节点读取 prompt,从图片节点读取参考图,从视频节点读取参考视频,从音频节点读取参考音频。Seedance 2.0 支持最多 9 张参考图、3 个参考视频、3 个参考音频;分辨率支持 `480p`、`720p`、`1080p`fast 模型不支持 `1080p`),比例支持 `16:9`、`4:3`、`1:1`、`3:4`、`9:16`、`21:9`、`adaptive`,时长支持 4-15 秒或智能时长。生成成功后会把视频插入画布为视频节点并使用原生播放器预览。参考视频和参考音频优先使用公网可访问 URL;本地素材会以前端可读取的数据传给兼容接口,是否支持取决于具体上游。
## 画布助手 ## 画布助手
@@ -106,6 +109,18 @@ Base URL 如果已经以 `/v1`、`/api/v3` 或 `/api/plan/v3` 结尾,系统不
- 把助手生成的图片插入画布。 - 把助手生成的图片插入画布。
- 折叠和展开助手面板。 - 折叠和展开助手面板。
## 节点插件系统
画布支持通过节点插件扩展新的节点类型,内置节点之外的能力都可以以插件形式加载:
- 支持通过 URL 动态安装、启用、更新和卸载远程节点插件。
- 提供官方插件注册表,节点插件面板可从项目仓库读取官方插件列表并一键安装。
- 提供 TypeScript 插件开发 SDK,可自行开发画布节点插件,并使用 AI 生成、面板控制等扩展能力。
- 内置 Markdown、SVG、HTML、3D 全景、便利贴等示例插件。
- 插件面板会显示插件版本,并对可升级的插件给出提醒。
Codex App 插件的安装与使用见 [Codex App 插件](/docs/overview/codex-app-plugin)。
## 提示词库 ## 提示词库
前台提示词库支持: 前台提示词库支持:
+1 -2
View File
@@ -9,7 +9,6 @@
"render", "render",
"docker", "docker",
"third-party-prompt-repositories", "third-party-prompt-repositories",
"[在线体验](https://canvas.best/)", "[在线体验](https://canvas.best/)"
"local-codex-canvas"
] ]
} }
@@ -7,7 +7,6 @@ description: 当前已接入同步逻辑的第三方提示词仓库
| 地址 | 状态 | | 地址 | 状态 |
| --- | --- | | --- | --- |
| https://github.com/EvoLinkAI/awesome-gpt-image-2-API-and-Prompts | 已实现同步逻辑 |
| https://github.com/ZeroLu/awesome-gpt-image | 已实现同步逻辑 | | https://github.com/ZeroLu/awesome-gpt-image | 已实现同步逻辑 |
| https://github.com/ImgEdify/Awesome-GPT4o-Image-Prompts | 已实现同步逻辑 | | https://github.com/ImgEdify/Awesome-GPT4o-Image-Prompts | 已实现同步逻辑 |
| https://github.com/YouMind-OpenLab/awesome-gpt-image-2 | 已实现同步逻辑 | | https://github.com/YouMind-OpenLab/awesome-gpt-image-2 | 已实现同步逻辑 |
+1
View File
@@ -5,6 +5,7 @@
"pages": [ "pages": [
"[更新日志](/docs/progress/changelog)", "[更新日志](/docs/progress/changelog)",
"local-agent-integration-plan", "local-agent-integration-plan",
"prompt-chip-input-plan",
"pending-test", "pending-test",
"todo" "todo"
] ]
+11 -26
View File
@@ -5,29 +5,14 @@ description: 当前版本已实现但仍需人工验证的变更项
# 待测试 # 待测试
- GitHub Pages:新增版本 tag 触发的前端静态站点自动发布 workflow,需在仓库 Pages 设置中选择 GitHub Actions 并验证路由刷新回退 - 提示词来源:6 个内置来源应从 Image Prompts 统一仓库读取,更新后数量依次为 323、494、53、76、126、129;提示词仍按 6 个来源分组并可独立启用,来源内可继续按 `tags` 筛选。添加标准 JSON URL 后应能查看内容,填写非数组 JSON 或不可访问地址时应显示失败,并继续保留该来源上一次成功缓存的内容
- 画布图片切图:行数 / 列数会生成可直接拖拽的等分切图线,并支持新增横向 / 纵向线、删除单条线、重置切图线和切片数量预览 - 提示词来源界面:来源应以卡片列表展示,启用开关位于左侧,数量、同步状态和上次成功时间作为次级信息显示,查看、拉取及自定义来源编辑/删除操作使用带文字按钮;底部定时拉取区域应保持独立边框布局
- Codex App 插件文档:新增插件安装教程,区分 AI 自动安装、手动安装和本仓库开发调试安装;同步优化插件 README 与技能说明 - 我的提示词:在提示词中心切换到「我的提示词」,验证新增、编辑、删除和搜索;从提示词库保存后应出现在个人列表、画布左侧提示词面板和提示词选择弹窗中,刷新页面后数据仍保留在本地,Agent 搜索提示词时也应能查到
- 本地 Codex 连接画布文档:新增 Canvas Agent、MCP、SSE 与浏览器画布交互原理说明,需确认文档导航和描述是否清晰 - 画布提示词库:不展开任何公共来源直接搜索其中的提示词,匹配项应自动显示;点击「插入画布」后应创建正文正确且标题保持为提示词标题的文本节点
- 顶部导航:缩小桌面顶部导航栏高度,导航项下划线和内容垂直对齐需确认 - 全站 Agent:新增 `generation_get_status` 工具,画布生成节点可按 `nodeIds` 查询,生图和视频工作台提交后会返回 `taskId` 并可查询排队、运行、成功或失败状态;需验证查询只由当前活动标签页返回
- 配置与用户偏好:新增独立顶部导航页面,并在页面和弹窗中复用同一套配置面板;新增 Codex Tab,展示插件安装和本地 Agent 启动步骤,可配置本地 Agent 地址、Connect token 和工具执行确认开关 - 本地 Agent 多标签页隔离:同时打开两个不同画布并连接同一个 Agent,分别聚焦标签页后通过 MCP 读取和修改画布,操作应只落在当前聚焦页面;网页面板发起的整个 Codex turn 应固定操作发起页面,即使中途聚焦另一标签页也不能切换目标;关闭当前页面后应回退到最近聚焦且仍连接的页面,其他页面回传同一请求结果应被拒绝
- Codex 全局连接:配置页可直接连接本地 Canvas Agent,连接状态同步到画布和顶部 OpenAI 状态图标,刷新后会按本地保存的地址和 token 自动尝试重连;需验证已连接、连接中、连接失败、断开和刷新自动重连状态 - 本地 Agent 多标签页会话同步:所有标签页共享同一个站点级 Codex 活跃线程;任一页面发送消息、新建、恢复或删除会话后,其他页面应同步活跃线程和聊天记录;Agent 输出仅显示在事件所属线程,运行中不能新建、恢复、删除或再次发送任务
- 画布 AgentCodex 旧 thread 在重启后不可用时会自动新建会话并重试,需验证进入画布直接发送消息不会再出现 `thread not loaded` 或 `no rollout found` - 本地 Agent 运行状态同步:在一个标签页运行较长 Codex 任务,等待某张工具卡显示「工具完成」后再打开或刷新第二个标签页;第二个标签页应立即显示 Codex 正在运行并禁用发送,整轮结束后两个标签页同时恢复;工具卡只显示「工具完成」,整轮结束由「本轮完成」表示
- 画布 Agent:切换不同画布时会清空并重新加载当前 canvasId 对应的 Codex 会话,需验证不会继续显示上一个画布的对话 - 本地 Agent 图片附件落画布:在右侧 Agent 上传参考图并要求基于商品信息创建生图流程,附件应创建为保持原比例的真实图片节点,分析提示词应创建为文本节点,二者都应连接到生成配置节点;刷新页面后参考图仍可显示并参与生成。任务中途切换到其他标签页时,附件只能写入发起任务的标签页;若发起标签页关闭,附件读取应失败且不能落入其他画布
- 画布 Agent:右侧 Agent 面板移除网站 / 本机模式切换,固定使用本机 Agent - Agent 对话滚动:从历史或日志切回对话、恢复其他会话时应自动定位到最新消息;手动向上浏览后应显示向下箭头且新消息不强制打断阅读,点击箭头后平滑回到底部并继续跟随新消息
- 配置与用户偏好:每个模型渠道新增 OpenAI / Gemini 调用格式选择,默认 OpenAI,切换默认格式时同步更新默认 Base URL;渠道 Tab 增加紧凑醒目的提醒,说明模型是否可选需要到“模型”Tab 选择 - Agent 消息区分:用户消息应在右侧以主题浅底气泡显示,AI 回复应在左侧保留 Agent 图标并使用开放式 Markdown 排版;长文本、多张附件、错误消息及浅色/深色主题下均应清晰且不溢出
- Gemini 调用格式:支持拉取 Gemini 模型列表,并用于文本问答、在线 Agent 工具调用、基础生图和图生图请求。
- Gemini 生图:生图工作台和画布生图会把尺寸配置转换为 Gemini `responseFormat.image.aspectRatio` 传入,需验证 1:1、9:16、16:9 及自定义像素尺寸的输出比例。
- 音频和视频生成:选择 Gemini 调用格式时先给出不支持提示,仍需使用 OpenAI 兼容渠道。
- WebDAV 同步:移除 Next.js 转发代理和连接方式选择,改为浏览器前端直接连接 WebDAV 服务。
- 提示词库:移除 Next.js route 抓取和服务端内存缓存,改为浏览器前端直连 GitHub 原始文件并缓存到 IndexedDB。
- 提示词库:移除已 404 的 `EvoLinkAI/awesome-gpt-image-2-API-and-Prompts` 来源。
- 前端构建:移除 Next.js,改为 Vite + React Router,并将前端目录调整为 `pages` / `components` / `stores` / `lib` / `layouts` / `styles`,需验证首页、图片/视频工作台、素材、提示词库和画布路由跳转。
- 前端构建:修复 TypeScript 严格检查下的构建报错。
- Vercel 部署:新增 `web` 和 `docs` 两个子项目的 Vercel 配置,需分别验证静态前端和文档站部署。
- Docker 部署:运行镜像从 Node serve 改为 nginx 静态托管,需验证前端路由刷新回退。
- 环境变量:移除过期的后端 `.env.example` 示例文件。
- 生图工作台:修复失败图片点击重试后只保存在页面状态、刷新或跳转后丢失的问题,重试成功结果会写入本地图片存储和生成记录。
- 视频生成:任务查询接口直接返回 `url` / `result_url` / `video_url` 时会跳过 `/content` 下载,并优先保存到浏览器本地媒体存储。
- 视频生成:任务创建或查询失败时会解析接口返回的 `msg` / `message` / 嵌套 JSON 错误,并在生成结果失败卡片里展示真实原因。
- 画布生成配置:修复切换文本/视频/音频模式时模型选择仍显示为生图模型(gpt-image-2)的问题,切换模式后应自动匹配对应能力的默认模型。
@@ -0,0 +1,66 @@
---
title: 提示词面板引用显示图片缩略图规划
description: 将提示词面板输入框从 textarea 改造为 contentEditable,让 @ 引用直接显示真实图片缩略图
---
# 提示词面板 @ 引用显示真实图片缩略图
## 背景 / 要解决的问题
提示词面板(`CanvasNodePromptPanel`)的输入框里,`@` 引用一张图片后,显示的是蓝色文字 chip「图片1」。期望**直接显示这张图片的真实缩略图**,而不是文字编号。
当前该输入框是原生 `<textarea>`,只能承载纯文本,无法内嵌图片。要显示缩略图,必须改用 `contentEditable`(可编辑 div),把引用做成带 `<img>` 的原子小卡片。项目里「组装提示词」组件(`CanvasConfigComposer`)已经是这套 contentEditable + 图片 chip 的成熟实现,可作蓝本。
## 关键约束(为什么不能直接改现有组件)
`CanvasResourceMentionTextarea` 被两处复用:
1. **提示词面板** `web/src/components/canvas/canvas-node-prompt-panel.tsx` —— 本次要改的对象。`highlightLabels` 默认 truevalue 是提示词字符串,引用以 label 文本「图片1」嵌在其中,**直接发给 AI**(配合 `web/src/lib/image-reference-prompt.ts` 的「参考图片编号:图片1」前缀)。
2. **节点内文本编辑** `web/src/components/canvas/canvas-node.tsx``isEditingContent` 分支)—— `highlightLabels={false}`,通过 `ref` 调用 `textarea.setSelectionRange(...)` 等 **textarea 专有 API**。若把组件改成 contentEditable,这处会直接崩。
> 结论:**新建一个 contentEditable chip 输入组件,只替换提示词面板那一处**`CanvasResourceMentionTextarea`textarea 版)保持不动,继续服务节点内文本编辑。
## 方案:新增 `CanvasPromptChipInput` 组件
新文件 `web/src/components/canvas/canvas-prompt-chip-input.tsx`,参照 `canvas-config-composer.tsx` 的 contentEditable 模式,但有一处**核心差异**:
- 「组装提示词」把引用序列化成 `@[node:UUID]` 标记;
- 本组件的 value 必须保持**引用的 label 文本**(如「图片1」),这样发给生成的提示词语义不变。因此 chip ↔ 文本的序列化用 **label 文本**,不是 node 标记。
### 组件契约(与现用法一致)
Props`value: string`、`references: CanvasResourceReference[]`、`onChange(value)`、`onSubmit?()`、`className`、`style`、`placeholder`。图片缩略图取 `reference.previewUrl`(即 `node.metadata.content`,已存在于 `CanvasResourceReference`)。
### 实现要点(大量复用 composer 的做法)
1. **DOM ← value(未聚焦时重建)**:仿 `CanvasConfigComposer` 的重建 effect。用 active label 列表(`references.filter(active).map(label)`,按长度降序)把 value 字符串切成「文本片段 + 命中 label」,命中处插入 chip。聚焦时不重建(`document.activeElement === editor` 直接 return),避免与输入 / IME 打架。
2. **chip 渲染**:仿 `createReferenceChip`。image 类型 → `size-6` 的 `<img src={reference.previewUrl}>`;其它类型 → 截断文字。chip 为 `contentEditable="false"` 的 `<span>`,用 `dataset.refLabel` 存 label 以便序列化。
3. **value ← DOMsyncFromEditor**:仿 `serializeNodes`,但 chip 序列化为 `dataset.refLabel`label 文本)而非 node 标记;文本节点原样拼接,`<br>` → `\n`。
4. **@ 菜单**:复用 `textBeforeCaret` + `/@([^\s@]*)$/` 检测;菜单定位改用 `getSelection().getRangeAt(0).getBoundingClientRect()`(比 textarea 的镜像 div 更简单、天然贴着光标)。菜单 UI 沿用现有 `MentionMenu` 的视觉(缩略图 + label + 副标题)。
5. **原子删除**:直接复用 composer 的 `deleteAdjacentReference` / `adjacentReferenceNode` / `findReferenceSibling`——contentEditable + `contentEditable="false"` chip 天然整体删除。可把这几个通用 DOM 助手连同 `textBeforeCaret` / `placeCaretAtEnd` / `closestEditor` 复制进新组件(保持 composer 不动,规避回归风险)。
6. **回车发送 + IME**:复用 `isPlainEnterKey` / `isImeComposing``@/lib/keyboard-event`)与 `onCompositionStart/End`,发送时调用 `onSubmit`。
7. **占位符 & 样式**:空值时显示 placeholderabsolute 占位层);容器套用面板传入的 `className/style``min-h-40`、透明底、无边框,与当前一致)。
### 替换点
`canvas-node-prompt-panel.tsx` 里把 `<CanvasResourceMentionTextarea .../>` 换成 `<CanvasPromptChipInput .../>`(同样的 value/references/onChange/onSubmit/className/style/placeholder)。其余(模型选择、设置、生成按钮)不动。
### 不改动
- `CanvasResourceMentionTextarea`textarea 版)保留,节点内文本编辑继续用它。
- 生成链路、`image-reference-prompt.ts` 前缀逻辑不变(value 仍是 label 文本)。
## 影响文件
- 新增:`web/src/components/canvas/canvas-prompt-chip-input.tsx`
- 修改:`web/src/components/canvas/canvas-node-prompt-panel.tsx`(仅替换输入框组件 + 相应 import)
## 验证
1. `npx tsc --noEmit` 通过。
2. dev server 刷新后:
- 连一张图片到图片 / 视频节点 → 面板输入框输入 `@` → 菜单贴着光标弹出 → 选中 → **输入框内显示该图的真实缩略图 chip**(不再是「图片1」文字)。
- 光标在 chip 后按一次 Backspace → 整个 chip 一次删除。
- 普通文字 + chip 混排,回车触发生成;确认发给生成的 prompt 里引用仍是「图片1」文本(生成结果正常引用到图)。
- 中文输入法输入不吞字、不误触发发送。
- 回归:双击文本节点进入内文本编辑仍正常(未受影响)。
-25
View File
@@ -1,25 +0,0 @@
---
title: 打赏支持
description: 支持无限画布项目继续维护
---
# 打赏支持
如果本项目对你有帮助,欢迎通过打赏、Star、提供AI订阅账号等方式支持项目继续维护。
项目开发大量依赖 Codex、Claude 等 AI 编程工具辅助写代码、排查问题和整理文档,这些工具需要持续付费。你的打赏会优先用于购买 AI 开发工具、模型服务和项目维护相关资源,帮助项目继续迭代。
## 支持方式
支持方式包括但不限于:
- 给 GitHub 仓库点 Star,帮助项目被更多人看到。
- 金额打赏,用于支持 AI 工具订阅、模型服务和项目维护成本。
- Codex、Claude 等 AI 编程工具订阅账号支持,帮助维护者持续使用 AI 辅助开发。
<div className="not-prose mt-6 flex flex-wrap items-center gap-3">
<span className="text-lg font-semibold text-fd-foreground">赞助地址:</span>
<a href="https://ifdian.net/a/basketikun" target="_blank" rel="noreferrer" className="inline-flex">
<img src="https://img.shields.io/badge/%E7%88%B1%E5%8F%91%E7%94%B5-%E8%B5%9E%E5%8A%A9%E4%BD%9C%E8%80%85-946ce6?style=for-the-badge&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0id2hpdGUiPjxwYXRoIGQ9Ik0xMiAyMS4zNWwtMS40NS0xLjMyQzUuNCAxNS4zNiAyIDEyLjI4IDIgOC41IDIgNS40MiA0LjQyIDMgNy41IDNjMS43NCAwIDMuNDEuODEgNC41IDIuMDlDMTMuMDkgMy44MSAxNC43NiAzIDE2LjUgMyAxOS41OCAzIDIyIDUuNDIgMjIgOC41YzAgMy43OC0zLjQgNi44Ni04LjU1IDExLjU0TDEyIDIxLjM1eiIvPjwvc3ZnPg==&logoColor=white" alt="爱发电赞助" />
</a>
</div>
-1
View File
@@ -4,7 +4,6 @@
"defaultOpen": true, "defaultOpen": true,
"pages": [ "pages": [
"security", "security",
"donate",
"sponsor" "sponsor"
] ]
} }
+19 -5
View File
@@ -1,19 +1,33 @@
--- ---
title: 广告赞助 title: 赞助支持
description: 广告赞助、项目露出和社区推广说明 description: 赞助支持、广告合作、项目露出和社区推广说明
--- ---
# 广告赞助 # 赞助支持
如果本项目对你有帮助,欢迎通过 Star、广告赞助合作、提供 AI 订阅账号等方式支持项目继续维护。
项目开发大量依赖 Codex、Claude 等 AI 编程工具辅助写代码、排查问题和整理文档,这些工具需要持续付费。相关支持会优先用于购买 AI 开发工具、模型服务和项目维护相关资源,帮助项目继续迭代。
## 支持方式
支持方式包括但不限于:
- 给 GitHub 仓库点 Star,帮助项目被更多人看到。
- 通过广告赞助合作支持项目维护成本。
- Codex、Claude 等 AI 编程工具订阅账号支持,帮助维护者持续使用 AI 辅助开发。
## 广告赞助
如果你希望在项目文档、README 或相关页面获得赞助露出,可以直接通过邮箱联系项目维护者沟通位置、周期和展示形式。 如果你希望在项目文档、README 或相关页面获得赞助露出,可以直接通过邮箱联系项目维护者沟通位置、周期和展示形式。
## 适合的赞助内容 ### 适合的赞助内容
- AI 工具、模型服务、API 服务或开发者工具。 - AI 工具、模型服务、API 服务或开发者工具。
- 与图片、视频、文本生成工作流相关的产品。 - 与图片、视频、文本生成工作流相关的产品。
- 适合开源项目用户和 AI 创作者使用的服务。 - 适合开源项目用户和 AI 创作者使用的服务。
## 建议提供的信息 ### 建议提供的信息
- 品牌或产品名称。 - 品牌或产品名称。
- 希望展示的位置和周期。 - 希望展示的位置和周期。
+4 -5
View File
@@ -15,8 +15,8 @@
## 开发与数据 ## 开发与数据
- [本地开发](/docs/backend/local-development) - [本地开发](/docs/development/local-development)
- [画布数据结构](/docs/backend/canvas-data-structure) - [画布数据结构](/docs/development/canvas-data-structure)
## 商务合作 ## 商务合作
@@ -27,8 +27,7 @@
## 支持与安全 ## 支持与安全
- [漏洞提交](/docs/support/security) - [漏洞提交](/docs/support/security)
- [打赏支持](/docs/support/donate) - [赞助支持](/docs/support/sponsor)
- [广告赞助](/docs/support/sponsor)
## 项目进度 ## 项目进度
@@ -43,4 +42,4 @@
## 原理说明 ## 原理说明
- [本地 Codex 连接画布原理](/docs/overview/local-codex-canvas) - [本地 Codex 连接画布原理](/docs/development/local-codex-canvas)
+2 -2
View File
@@ -7,10 +7,10 @@ import { cn } from '@/lib/cn';
const tabs = [ const tabs = [
{ title: '项目介绍', href: '/docs/overview/quick-start', prefix: '/docs/overview' }, { title: '项目介绍', href: '/docs/overview/quick-start', prefix: '/docs/overview' },
{ title: '操作手册', href: '/docs/canvas/canvas-node-manual', prefix: '/docs/canvas' }, { title: '操作手册', href: '/docs/canvas/canvas-node-manual', prefix: '/docs/canvas' },
{ title: '开发文档', href: '/docs/backend/local-development', prefix: '/docs/backend' }, { title: '开发文档', href: '/docs/development/local-development', prefix: '/docs/development' },
{ title: '项目进度', href: '/docs/progress/changelog', prefix: '/docs/progress' }, { title: '项目进度', href: '/docs/progress/changelog', prefix: '/docs/progress' },
{ title: '商务合作', href: '/docs/business/business', prefix: '/docs/business' }, { title: '商务合作', href: '/docs/business/business', prefix: '/docs/business' },
{ title: '赞助支持', href: '/docs/support/donate', prefix: '/docs/support' }, { title: '赞助支持', href: '/docs/support/sponsor', prefix: '/docs/support' },
]; ];
export function DocsTopTabs() { export function DocsTopTabs() {
+5
View File
@@ -7,4 +7,9 @@ server {
location / { location / {
try_files $uri $uri/ /index.html; try_files $uri $uri/ /index.html;
} }
# 运行期配置由容器启动时生成,禁止缓存,确保改环境变量后即时生效。
location = /config.js {
add_header Cache-Control "no-store";
}
} }
+163
View File
@@ -0,0 +1,163 @@
# Infinite Canvas 画布节点插件
给画布扩展自定义节点。每个插件是一个**独立目录**,用 **TypeScript** 编写,自带 `package.json` / `build.mjs` / `src/index.tsx` / `dist/`,互不耦合,可单独构建、发布、升级。
内置节点只有文本、图片、视频、音频、生成配置、组六种;其余节点(Markdown、SVG、HTML、3D 全景、便利贴……)都是插件。
作者只写节点 UI 与逻辑,**类型、JSX、宿主 React、构建全部由 [`@infinite-canvas/plugin-sdk`](./sdk/README.md) 提供**,写 TSX 全程有代码提示;产物仍是宿主加载器现有契约的 ESM(React external、宿主单例)。
## 目录约定
```
plugins/canvas/
sdk/ # 插件 SDK(类型 + automatic JSX 运行时 + 构建助手)
template/ # 起步模板:复制它开始写新插件
markdown/ # 每个插件一个独立目录
package.json
build.mjs # 一行 buildPlugin,产物名取目录名 → dist/markdown.js
tsconfig.json
src/index.tsx # 插件源码(默认导出 definePlugin(...))
README.md
svg/ html/ panorama/ sticky-note/ ...
```
## 快速开始
```bash
cp -r plugins/canvas/template plugins/canvas/my-plugin
cd plugins/canvas/my-plugin
# 改 package.json 的 name;改 src/index.tsx 里的 id / nodes[].type
npm install
npm run dev # watch 构建,产物同步到 web/public/plugins/my-plugin.js
npm run typecheck # tsc --noEmit,类型自检
```
产物名取**目录名**,复制后记得把目录改成插件名。
## 构建 / 发布 / 升级
```bash
cd plugins/canvas/<name>
npm install
npm run build # → dist/<name>.js,并同步到 web/public/plugins/<name>.js
npm run dev # watch,改动自动构建并同步
```
`dist/<name>.js` 托管到任意静态地址(CDN、GitHub Raw、对象存储),用户在画布「节点插件」管理器填该 URL 安装。升级时重新构建覆盖同一 URL,用户点「更新」即可。
## 官方插件注册表
本项目官方插件由 CI 集中构建后发布到孤儿分支 `plugins-dist`(**构建产物不进 git**),画布「节点插件」面板顶部的**官方插件**区经 jsDelivr 从该分支远程拉取并一键安装;第三方插件仍走下方「第三方插件」的 JS URL 安装。构建脚本与发布说明见 [`registry/`](./registry/README.md);清单地址可用 `VITE_PLUGIN_REGISTRY_URL` 覆盖成自建来源。
## 本地开发
`npm run dev` 起 watch,产物会同步到 `web/public/plugins/<name>.js`。此后有两种方式在画布里用到它:
**方式一(推荐):自动发现。** 画布启动时会扫描 `web/public/plugins/` 下的插件,自动加入「节点插件」管理器列表,**默认关闭**;打开开关即启用。无需手动填 URL,启用时会按文件重新拉取,配合 watch 改完刷新即最新。
**方式二:`VITE_DEV_PLUGINS`。**`web/.env.local` 声明(逗号分隔多个),这些插件每次刷新页面都**重新拉取并直接激活**(不缓存、不落库、无开关):
```env
VITE_DEV_PLUGINS=/plugins/markdown.js,/plugins/svg.js
```
再起画布 `web`(`npm run dev`)。流程即:改 `src/index.tsx` → watch 自动构建 → 刷新画布看到最新效果,无需反复安装。
## 用 SDK 写插件
默认导出 `definePlugin({...})`(对象形式,**无需再 `const { React } = runtime`**):
```tsx
import { definePlugin, useState } from "@infinite-canvas/plugin-sdk";
import type { CanvasNodeContentProps } from "@infinite-canvas/plugin-sdk";
function Content({ ctx }: CanvasNodeContentProps) {
const [n, setN] = useState(0);
return (
<button onMouseDown={(e) => e.stopPropagation()} onClick={() => setN((v) => v + 1)} style={{ color: ctx.theme.node.text }}>
{ctx.node.title}: {n}
</button>
);
}
export default definePlugin({
id: "my-plugin",
name: "我的插件",
version: "1.0.0",
css: "…", // 可选:插件样式,自动注入/清理
nodes: [ /* CanvasNodeDefinition[] */ ],
setup(app) { return () => {}; }, // 可选,返回清理函数;app 含 injectCSS/emit/on
});
```
SDK 导出的 hooks(`useState/useEffect/useMemo/useRef/...`)运行时转发宿主 React,类型来自 `@types/react`。SDK 与依赖接入见 [`sdk/README.md`](./sdk/README.md)。
### CanvasNodeDefinition
```ts
{
type: string; // 建议 "<pluginId>:<name>",全局唯一
title: string; // 创建菜单/默认标题
icon: ReactNode; // emoji 字符串或任意 ReactNode
description?: string;
defaultSize: { width, height };
defaultMetadata?: object; // 新建节点初始 metadata(文本内容放 content)
minimapColor?: string;
showInCreateMenu?: boolean; // 默认 true
hasSourceHandle?: boolean; // 右侧输出连接点,默认 true
keepAspectRatio?: (node) => boolean;
resource?: (node) => { kind: "text"|"image"|"video"|"audio", text?, url? } | null; // 作为上游输入被消费时输出什么
Content: ({ ctx }) => ReactNode; // 节点主体渲染
Panel?: ({ ctx, onClose }) => ReactNode; // 可选:节点下方面板
toolbar?: (ctx) => Array<{ id, title, label, icon, onClick, danger? }>; // 追加到 hover 工具栏
onDoubleClick?: (ctx) => boolean; // 返回 true 表示已处理双击
}
```
### ctx:节点与画布交互接口
`Content` / `Panel` / `toolbar` 都会拿到 `ctx`(类型 `CanvasNodeContext`):
| 能力 | 说明 |
| --- | --- |
| `ctx.node` | 当前节点数据(含 `metadata.content` 等) |
| `ctx.theme` / `ctx.scale` | 当前画布主题 token 与缩放,用来让 UI 跟随主题 |
| `ctx.updateMetadata(patch)` | 更新自身 metadata(如保存内容) |
| `ctx.updateNode(patch)` | 更新自身 title/width/height |
| `ctx.getNode(id)` / `ctx.getNodes()` / `ctx.getConnections()` | 读画布 |
| `ctx.getUpstream()` / `ctx.getDownstream()` | 取上/下游相连节点 |
| `ctx.applyOps(ops)` | 用画布指令集增删节点/连线、选择、触发生成(见下) |
| `ctx.emit(event, payload)` / `ctx.on(event, handler)` | 节点/插件间事件通信 |
| `ctx.storage` | 插件私有持久化(按插件 id 命名空间) |
> `metadata` 的**内置字段**(content、status、model…)是强类型;插件写入的**自定义字段**读出为 `unknown`,按需 `as` 断言(参考 `sticky-note` 的 `pluginColor`)。
### 画布指令集(ctx.applyOps)
```ts
ctx.applyOps([
{ type: "add_node", id?, nodeType, title?, x?, y?, width?, height?, metadata? },
{ type: "update_node", id, patch?, metadata? },
{ type: "delete_node", id? | ids? },
{ type: "connect_nodes", fromNodeId, toNodeId },
{ type: "delete_connections", id? | ids? | all? },
{ type: "select_nodes", ids },
{ type: "set_viewport", viewport },
{ type: "run_generation", nodeId, mode?, prompt? },
]);
```
## 重依赖 / 资源
- **重依赖**(three.js、marked 等):不要打进 bundle,运行时 `await import("https://esm.sh/...")` 动态加载(esbuild 自动 external);在 `src/env.d.ts` 声明该模块以通过 tsc。参考 `panorama/``markdown/`
- **CSS**:写独立 `.css`,`import css from "./styles.css"` 拿到字符串(esbuild `text` loader),放到 `css` 字段自动注入/清理;`src/env.d.ts` 声明 `*.css`。参考 `markdown/`
- **HTML**:HTML 节点把 HTML 字符串塞进 sandbox iframe 的 `srcDoc`,自带 `<style>`,不需要插件级 CSS。参考 `html/`
## 兼容说明
加载器仍接受**默认导出为工厂函数** `(runtime) => CanvasPlugin`(用 `runtime.React`)或**普通对象**;老的 JS 插件无需改动即可运行。SDK 的 automatic JSX 让新插件走对象形式,更简洁。
## 注意
- 插件代码会在画布页面内**直接执行**,可访问浏览器本地数据(含 AI API Key)。发布前请自审,用户也只应安装可信来源。
- 交互控件记得 `onMouseDown={(e) => e.stopPropagation()}`(避免触发节点拖拽),滚动区域加 `onWheel={(e) => e.stopPropagation()}` 与容器 `data-canvas-no-zoom`(避免被画布缩放拦截)。
+17
View File
@@ -0,0 +1,17 @@
# HTML 节点插件
Infinite Canvas 画布节点插件:用沙箱 iframe 渲染 HTML。源码里的 `{{input}}` 会被替换为上游文本节点内容。
## 构建
```bash
npm install
npm run build # 产物 dist/html.js,并同步到 web/public/plugins/html.js
npm run dev # watch
```
## 安装
画布 → 左上菜单「节点插件」→ 安装 URL 填 `/plugins/html.js`(或托管后的公网 URL)。
插件契约见 `plugins/canvas/README.md`
+3
View File
@@ -0,0 +1,3 @@
import { buildPlugin } from "@infinite-canvas/plugin-sdk/build";
await buildPlugin(import.meta.url);
+65
View File
@@ -0,0 +1,65 @@
{
"name": "canvas-plugin-html",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "canvas-plugin-html",
"version": "1.0.0",
"devDependencies": {
"@infinite-canvas/plugin-sdk": "file:../sdk",
"@types/react": "19.1.12",
"typescript": "^5"
}
},
"../sdk": {
"name": "@infinite-canvas/plugin-sdk",
"version": "0.1.0",
"dev": true,
"devDependencies": {
"@types/react": "19.1.12",
"esbuild": "^0.25.0",
"typescript": "^5"
},
"peerDependencies": {
"@types/react": ">=18"
}
},
"node_modules/@infinite-canvas/plugin-sdk": {
"resolved": "../sdk",
"link": true
},
"node_modules/@types/react": {
"version": "19.1.12",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.12.tgz",
"integrity": "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==",
"dev": true,
"license": "MIT",
"dependencies": {
"csstype": "^3.0.2"
}
},
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"dev": true,
"license": "MIT"
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
}
}
}
+17
View File
@@ -0,0 +1,17 @@
{
"name": "canvas-plugin-html",
"version": "1.2.0",
"private": true,
"type": "module",
"description": "Infinite Canvas HTML 节点插件",
"scripts": {
"build": "node build.mjs",
"dev": "node build.mjs --watch",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"@infinite-canvas/plugin-sdk": "file:../sdk",
"@types/react": "19.1.12",
"typescript": "^5"
}
}
+138
View File
@@ -0,0 +1,138 @@
// HTML 节点:沙箱 iframe 渲染 HTML,{{input}} 会替换为上游文本节点内容。
// 交互:预览态 iframe 默认 pointer-events:none —— 鼠标事件穿透到宿主节点体,
// 因此点节点任意位置都能拖动,无需在节点上加任何标题栏/按钮。
// 「编辑/预览」与「交互」开关都放在节点外的悬浮工具条(toolbar 扩展点),状态存 metadata。
import { definePlugin, useMemo, useRef, useState } from "@infinite-canvas/plugin-sdk";
import type { CanvasNodeContentProps } from "@infinite-canvas/plugin-sdk";
// 源码编辑器行高/字号,行号槽与文本域必须完全一致才能对齐
const EDITOR_FONT = 12;
const EDITOR_LINE = 20;
function HtmlEditor({ ctx, value }: { ctx: CanvasNodeContentProps["ctx"]; value: string }) {
const gutterRef = useRef<HTMLDivElement>(null);
// 行数:按换行统计,至少 1 行;value 变化时重算
const lineCount = useMemo(() => Math.max(1, value.split("\n").length), [value]);
const [scrollTop, setScrollTop] = useState(0);
const codeStyle = { fontFamily: "monospace", fontSize: EDITOR_FONT, lineHeight: `${EDITOR_LINE}px`, boxSizing: "border-box" } as const;
return (
<div data-canvas-no-zoom style={{ height: "100%", width: "100%", display: "flex", overflow: "hidden", borderRadius: 16, background: ctx.theme.node.fill }} onMouseDown={(e) => e.stopPropagation()}>
{/* 行号槽:跟随文本域滚动,不可单独滚动 */}
<div
ref={gutterRef}
aria-hidden
style={{
...codeStyle,
flex: "0 0 auto",
padding: "16px 8px 16px 12px",
textAlign: "right",
color: ctx.theme.node.placeholder,
background: `${ctx.theme.toolbar.panel}66`,
borderRight: `1px solid ${ctx.theme.node.stroke}`,
overflow: "hidden",
userSelect: "none",
whiteSpace: "pre",
}}
>
{/* 用负 margin 让整列跟随 scrollTop 平移,和 textarea 同步 */}
<div style={{ transform: `translateY(${-scrollTop}px)` }}>
{Array.from({ length: lineCount }, (_, i) => (
<div key={i}>{i + 1}</div>
))}
</div>
</div>
<textarea
autoFocus
value={value}
placeholder="<div>Hello, {{input}}</div>"
spellCheck={false}
wrap="off"
onChange={(e) => ctx.updateMetadata({ content: e.target.value })}
onScroll={(e) => setScrollTop(e.currentTarget.scrollTop)}
onMouseDown={(e) => e.stopPropagation()}
onWheel={(e) => e.stopPropagation()}
style={{ ...codeStyle, flex: "1 1 auto", minWidth: 0, height: "100%", resize: "none", background: "transparent", padding: "16px 16px 16px 12px", outline: "none", border: "none", color: ctx.theme.node.text, whiteSpace: "pre", overflow: "auto" }}
/>
</div>
);
}
function HtmlContent({ ctx }: CanvasNodeContentProps) {
const value = ctx.node.metadata?.content || "";
const editing = Boolean(ctx.node.metadata?.editing);
const upstreamText = useMemo(
() =>
ctx
.getUpstream()
.map((node) => node.metadata?.content)
.filter(Boolean)
.join("\n"),
[ctx],
);
const html = value.replace(/\{\{\s*input\s*\}\}/g, upstreamText);
if (editing) {
return <HtmlEditor ctx={ctx} value={value} />;
}
if (!value) {
return (
<div style={{ height: "100%", width: "100%", display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: 8, color: ctx.theme.node.placeholder }}>
<span style={{ fontSize: 26 }}>{"</>"}</span>
<span style={{ fontSize: 13 }}>, HTML</span>
</div>
);
}
// 预览态:iframe 的鼠标交互由宿主「交互 ⇄ 移动」开关统一控制(见 interactionToggle),
// 这里无需再手动做 pointer-events 穿透。data-canvas-no-zoom 保证交互时滚动作用于页面而非缩放画布。
return (
<div data-canvas-no-zoom style={{ position: "relative", height: "100%", width: "100%" }}>
<iframe
title="html-preview"
sandbox="allow-scripts allow-forms"
srcDoc={html}
style={{ height: "100%", width: "100%", border: 0, borderRadius: 16, background: "#fff", display: "block" }}
/>
</div>
);
}
export default definePlugin({
id: "html",
name: "HTML 节点",
version: "1.2.0",
description: "沙箱 iframe 渲染 HTML,支持 {{input}} 注入上游文本",
nodes: [
{
type: "html:render",
title: "HTML",
icon: "🌐",
description: "沙箱渲染 HTML",
defaultSize: { width: 420, height: 320 },
defaultMetadata: { content: "" },
minimapColor: "#ec4899",
hidePanel: true, // 纯展示型节点:点击/新建不弹出下方生图面板
// 宿主统一提供「交互 ⇄ 移动」开关;编辑态强制可交互(编辑器始终可操作)并隐藏该开关
interactionToggle: true,
forceInteractive: (node) => Boolean(node.metadata?.editing),
Content: HtmlContent,
// 仅保留「编辑/预览」开关;交互/移动 由宿主自动注入
toolbar: (ctx) => {
const editing = Boolean(ctx.node.metadata?.editing);
return [
{
id: "html-toggle-edit",
title: editing ? "预览渲染结果" : "编辑 HTML 源码",
label: editing ? "预览" : "编辑",
icon: editing ? "👁" : "✎",
active: editing,
onClick: () => ctx.updateMetadata({ editing: !editing }),
},
];
},
},
],
});
+17
View File
@@ -0,0 +1,17 @@
{
"compilerOptions": {
"target": "es2020",
"module": "esnext",
"moduleResolution": "bundler",
"lib": ["dom", "dom.iterable", "esnext"],
"jsx": "react-jsx",
"jsxImportSource": "@infinite-canvas/plugin-sdk",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"esModuleInterop": true,
"isolatedModules": true,
"types": []
},
"include": ["src"]
}
+23
View File
@@ -0,0 +1,23 @@
# Markdown 节点插件
Infinite Canvas 画布节点插件:在画布里编辑与渲染 Markdown。
## 构建
```bash
npm install
npm run build # 产物 dist/markdown.js,并同步到 web/public/plugins/markdown.js
npm run dev # watch,改动自动构建
```
## 安装
画布 → 左上菜单「节点插件」→ 安装 URL 填 `/plugins/markdown.js`(或托管后的公网 URL)。
## 本地开发
`npm run dev` 起 watch,在 `web/.env.local``VITE_DEV_PLUGINS=/plugins/markdown.js`,起画布后改 `src/index.jsx` 刷新页面即生效,无需反复安装。
插件契约见 `plugins/canvas/README.md`
> 本插件演示了独立 CSS 文件用法:`src/styles.css` 经 esbuild `text` loader 打进 bundle,通过插件 `css` 字段自动注入/清理。
+3
View File
@@ -0,0 +1,3 @@
import { buildPlugin } from "@infinite-canvas/plugin-sdk/build";
await buildPlugin(import.meta.url);
+65
View File
@@ -0,0 +1,65 @@
{
"name": "canvas-plugin-markdown",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "canvas-plugin-markdown",
"version": "1.0.0",
"devDependencies": {
"@infinite-canvas/plugin-sdk": "file:../sdk",
"@types/react": "19.1.12",
"typescript": "^5"
}
},
"../sdk": {
"name": "@infinite-canvas/plugin-sdk",
"version": "0.1.0",
"dev": true,
"devDependencies": {
"@types/react": "19.1.12",
"esbuild": "^0.25.0",
"typescript": "^5"
},
"peerDependencies": {
"@types/react": ">=18"
}
},
"node_modules/@infinite-canvas/plugin-sdk": {
"resolved": "../sdk",
"link": true
},
"node_modules/@types/react": {
"version": "19.1.12",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.12.tgz",
"integrity": "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==",
"dev": true,
"license": "MIT",
"dependencies": {
"csstype": "^3.0.2"
}
},
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"dev": true,
"license": "MIT"
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
}
}
}
+17
View File
@@ -0,0 +1,17 @@
{
"name": "canvas-plugin-markdown",
"version": "1.1.0",
"private": true,
"type": "module",
"description": "Infinite Canvas Markdown 节点插件",
"scripts": {
"build": "node build.mjs",
"dev": "node build.mjs --watch",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"@infinite-canvas/plugin-sdk": "file:../sdk",
"@types/react": "19.1.12",
"typescript": "^5"
}
}
+10
View File
@@ -0,0 +1,10 @@
// esbuild 以 text loader 把 .css 打成字符串
declare module "*.css" {
const css: string;
export default css;
}
// 重依赖从 CDN 动态加载,不打进 bundle;此处声明以便 TS 提示
declare module "https://esm.sh/marked@14" {
export const marked: { parse: (src: string) => string };
}
+125
View File
@@ -0,0 +1,125 @@
// Markdown 节点:编辑与渲染 Markdown。marked 从 CDN 按需加载,不打进插件体积。
// 移动/交互:走宿主统一的「交互 ⇄ 移动」开关(interactionToggle)——默认「移动」态整块可拖,
// 切「交互」态可滚动/选择;编辑态强制可交互。
// 防闪烁:解析结果按源码模块级缓存,且只在 HTML 真正变化时写入 DOM——
// 画布任何重渲染都不会重新解析或重载 Markdown 里的图片。
// styles.css 由 esbuild 以 text 方式打进 bundle,通过 plugin.css 自动注入。
import { definePlugin, useEffect, useRef, useState } from "@infinite-canvas/plugin-sdk";
import type { CanvasNodeContentProps } from "@infinite-canvas/plugin-sdk";
import css from "./styles.css";
type Marked = { parse: (src: string) => string };
let marked: Marked | undefined; // 加载完成后的 marked 实例,模块级缓存,后续同步渲染
let markedPromise: Promise<Marked> | undefined;
function loadMarked(): Promise<Marked> {
if (marked) return Promise.resolve(marked);
if (!markedPromise) markedPromise = import("https://esm.sh/marked@14").then((mod: { marked: Marked }) => (marked = mod.marked));
return markedPromise;
}
const PLACEHOLDER = "*选中节点,点上方工具条的 ✎ 编辑 Markdown*";
// 解析结果按源码缓存(模块级):同一段 Markdown 只解析一次,重复渲染/重挂载都命中缓存,
// 返回的是同一个字符串引用,配合下方「值不变不写 DOM」彻底避免图片被重新请求。
const htmlCache = new Map<string, string>();
function renderMarkdown(source: string): string {
if (!marked) return "";
const key = source || PLACEHOLDER;
let out = htmlCache.get(key);
if (out === undefined) {
out = marked.parse(key);
htmlCache.set(key, out);
}
return out;
}
// 预览态:用 ref 手动写 innerHTML,且仅在 html 真正变化时写入。
// 这样宿主的任何重渲染(点击、移动视角、选中态变化等)都不会触碰已渲染的 DOM,图片不会重新加载。
function MarkdownPreview({ ctx }: CanvasNodeContentProps) {
const [, force] = useState(0);
const ref = useRef<HTMLDivElement>(null);
const lastHtml = useRef<string | null>(null);
// marked 未就绪时按需加载,加载完触发一次重渲染填充内容
useEffect(() => {
if (marked) return;
let alive = true;
loadMarked().then(() => alive && force((n) => n + 1));
return () => {
alive = false;
};
}, []);
const source = (ctx.node.metadata?.content as string | undefined) || "";
const html = renderMarkdown(source);
useEffect(() => {
const el = ref.current;
if (!el || lastHtml.current === html) return; // 内容没变:不动 DOM,已加载的图片保持原样
el.innerHTML = html;
lastHtml.current = html;
}, [html]);
return <div ref={ref} className="cnv-md" data-canvas-no-zoom onWheel={(e) => e.stopPropagation()} style={{ height: "100%", width: "100%", color: ctx.theme.node.text }} />;
}
function MarkdownEditor({ ctx }: CanvasNodeContentProps) {
const value = (ctx.node.metadata?.content as string | undefined) || "";
return (
<textarea
autoFocus
value={value}
placeholder="# 输入 Markdown"
onChange={(e) => ctx.updateMetadata({ content: e.target.value })}
onMouseDown={(e) => e.stopPropagation()}
onPointerDown={(e) => e.stopPropagation()}
onWheel={(e) => e.stopPropagation()}
style={{ height: "100%", width: "100%", resize: "none", background: ctx.theme.node.fill, borderRadius: 16, boxSizing: "border-box", padding: 16, fontFamily: "monospace", fontSize: 14, outline: "none", border: "none", color: ctx.theme.node.text }}
/>
);
}
function MarkdownContent({ ctx }: CanvasNodeContentProps) {
return ctx.node.metadata?.editing ? <MarkdownEditor ctx={ctx} /> : <MarkdownPreview ctx={ctx} />;
}
export default definePlugin({
id: "markdown",
name: "Markdown 节点",
version: "1.1.0",
description: "在画布中编辑与渲染 Markdown",
css,
nodes: [
{
type: "markdown:doc",
title: "Markdown",
icon: "📝",
description: "编辑与渲染 Markdown",
defaultSize: { width: 360, height: 300 },
defaultMetadata: { content: "" },
minimapColor: "#6366f1",
hidePanel: true, // 纯展示/编辑节点:不弹出下方生图面板
// 宿主统一提供「交互 ⇄ 移动」开关;编辑态强制可交互并隐藏该开关
interactionToggle: true,
forceInteractive: (node) => Boolean(node.metadata?.editing),
resource: (node) => ({ kind: "text", text: node.metadata?.content }),
Content: MarkdownContent,
// 仅保留「编辑/预览」开关(状态存 metadata.editing);交互/移动 由宿主自动注入
toolbar: (ctx) => {
const editing = Boolean(ctx.node.metadata?.editing);
return [
{
id: "md-toggle-edit",
title: editing ? "预览渲染结果" : "编辑 Markdown 源码",
label: editing ? "预览" : "编辑",
icon: editing ? "👁" : "✎",
active: editing,
onClick: () => ctx.updateMetadata({ editing: !editing }),
},
];
},
},
],
});
+59
View File
@@ -0,0 +1,59 @@
.cnv-md {
height: 100%;
width: 100%;
overflow: auto;
padding: 16px;
font-size: 14px;
line-height: 1.6;
}
.cnv-md h1,
.cnv-md h2,
.cnv-md h3 {
margin: 0.6em 0 0.3em;
font-weight: 600;
line-height: 1.3;
}
.cnv-md h1 {
font-size: 1.5em;
}
.cnv-md h2 {
font-size: 1.3em;
}
.cnv-md p {
margin: 0.5em 0;
}
.cnv-md a {
color: #6366f1;
text-decoration: underline;
}
.cnv-md code {
padding: 0.1em 0.35em;
border-radius: 4px;
background: rgba(120, 120, 120, 0.16);
font-family: monospace;
font-size: 0.9em;
}
.cnv-md pre {
padding: 12px;
border-radius: 8px;
background: rgba(120, 120, 120, 0.14);
overflow: auto;
}
.cnv-md pre code {
padding: 0;
background: transparent;
}
.cnv-md ul,
.cnv-md ol {
padding-left: 1.4em;
margin: 0.5em 0;
}
.cnv-md blockquote {
margin: 0.5em 0;
padding-left: 0.8em;
border-left: 3px solid rgba(120, 120, 120, 0.4);
opacity: 0.85;
}
.cnv-md img {
max-width: 100%;
}
+17
View File
@@ -0,0 +1,17 @@
{
"compilerOptions": {
"target": "es2020",
"module": "esnext",
"moduleResolution": "bundler",
"lib": ["dom", "dom.iterable", "esnext"],
"jsx": "react-jsx",
"jsxImportSource": "@infinite-canvas/plugin-sdk",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"esModuleInterop": true,
"isolatedModules": true,
"types": []
},
"include": ["src"]
}
+17
View File
@@ -0,0 +1,17 @@
# 3D 全景节点插件
Infinite Canvas 画布节点插件:查看 360° 等距柱状(equirectangular)全景图,可拖拽旋转。可从上游图片节点自动取图。three.js 运行时从 CDN 按需加载,不打进插件体积。
## 构建
```bash
npm install
npm run build # 产物 dist/panorama.js,并同步到 web/public/plugins/panorama.js
npm run dev # watch
```
## 安装
画布 → 左上菜单「节点插件」→ 安装 URL 填 `/plugins/panorama.js`(或托管后的公网 URL)。连接一个全景图片节点到本节点即可查看。
插件契约见 `plugins/canvas/README.md`
+3
View File
@@ -0,0 +1,3 @@
import { buildPlugin } from "@infinite-canvas/plugin-sdk/build";
await buildPlugin(import.meta.url);
+65
View File
@@ -0,0 +1,65 @@
{
"name": "canvas-plugin-panorama",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "canvas-plugin-panorama",
"version": "1.0.0",
"devDependencies": {
"@infinite-canvas/plugin-sdk": "file:../sdk",
"@types/react": "19.1.12",
"typescript": "^5"
}
},
"../sdk": {
"name": "@infinite-canvas/plugin-sdk",
"version": "0.1.0",
"dev": true,
"devDependencies": {
"@types/react": "19.1.12",
"esbuild": "^0.25.0",
"typescript": "^5"
},
"peerDependencies": {
"@types/react": ">=18"
}
},
"node_modules/@infinite-canvas/plugin-sdk": {
"resolved": "../sdk",
"link": true
},
"node_modules/@types/react": {
"version": "19.1.12",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.12.tgz",
"integrity": "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==",
"dev": true,
"license": "MIT",
"dependencies": {
"csstype": "^3.0.2"
}
},
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"dev": true,
"license": "MIT"
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
}
}
}
+17
View File
@@ -0,0 +1,17 @@
{
"name": "canvas-plugin-panorama",
"version": "1.1.0",
"private": true,
"type": "module",
"description": "Infinite Canvas 3D 全景节点插件",
"scripts": {
"build": "node build.mjs",
"dev": "node build.mjs --watch",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"@infinite-canvas/plugin-sdk": "file:../sdk",
"@types/react": "19.1.12",
"typescript": "^5"
}
}
+2
View File
@@ -0,0 +1,2 @@
// three 从 CDN 动态加载,不打进 bundle;声明为宽松类型以便通过 tsc
declare module "https://esm.sh/three@0.180.0";
+311
View File
@@ -0,0 +1,311 @@
// 3D 全景节点:等距柱状(equirectangular)360° 全景查看器。
// 三种取图方式:① 从上游图片节点自动取图 ② 本地上传 ③ AI 生成(复用宿主内置生成面板)。
// three.js 从 CDN 按需加载,不打进 bundle。AI 生成通过 useBuiltinPanel 声明,结果写回本节点。
import { definePlugin, useCallback, useEffect, useRef, useState } from "@infinite-canvas/plugin-sdk";
import type { CanvasNodeContentProps, CanvasNodeContext } from "@infinite-canvas/plugin-sdk";
// three 从 CDN 动态加载(env.d.ts 里声明为 any),不打进 bundle
let threePromise: Promise<any> | undefined;
function loadThree(): Promise<any> {
if (!threePromise) threePromise = import("https://esm.sh/three@0.180.0");
return threePromise;
}
// AI 生成的固定前置提示词:约束模型产出可用于球面贴图的 equirectangular 全景图。
// 用户只需描述场景内容,这段会自动拼在前面,保证 2:1、无缝、无畸变文字等。
const PANORAMA_SYSTEM_PROMPT =
"A seamless 360-degree equirectangular panorama, 2:1 aspect ratio, full spherical VR photo, " +
"horizontally wrapping seamlessly at the left and right edges, no visible seam, no distortion artifacts, " +
"even horizon, no text, no watermark. Scene: ";
// ---------------------------------------------------------------------------
// 全景查看器:球体内壁贴等距柱状纹理,指针拖动转视角、滚轮缩放 fov。
// 自适应容器尺寸(ResizeObserver);超过 GPU 上限的大图先降采样,避免加载失败。
// ---------------------------------------------------------------------------
function PanoramaViewer({ src, ctx }: { src: string; ctx: CanvasNodeContext }) {
const mountRef = useRef<HTMLDivElement>(null);
const [status, setStatus] = useState<"loading" | "ready" | "error">("loading");
useEffect(() => {
const mount = mountRef.current;
if (!mount || !src) return;
let disposed = false;
let frame = 0;
let renderer: any = null;
let geometry: any = null;
let material: any = null;
let texture: any = null;
let resizeObserver: ResizeObserver | null = null;
let cleanupEvents = () => {};
setStatus("loading");
loadThree()
.then((THREE: any) => {
if (disposed || !mountRef.current) return;
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(70, 1, 1, 1100);
const target = new THREE.Vector3();
renderer = new THREE.WebGLRenderer({ antialias: true, powerPreference: "high-performance" });
renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
renderer.setClearColor(0x000000, 1);
const canvas: HTMLCanvasElement = renderer.domElement;
canvas.style.width = "100%";
canvas.style.height = "100%";
canvas.style.display = "block";
canvas.style.cursor = "grab";
mount.appendChild(canvas);
const maxTextureSide = Math.min(renderer.capabilities.maxTextureSize || 4096, 4096);
geometry = new THREE.SphereGeometry(500, 96, 64);
geometry.scale(-1, 1, 1); // 翻转法线,从球心向内看
material = new THREE.MeshBasicMaterial({ color: 0xffffff });
scene.add(new THREE.Mesh(geometry, material));
// 视角状态
let lon = 0;
let lat = 0;
let fov = 70;
let dragging = false;
let startX = 0;
let startY = 0;
let startLon = 0;
let startLat = 0;
const onDown = (event: PointerEvent) => {
dragging = true;
startX = event.clientX;
startY = event.clientY;
startLon = lon;
startLat = lat;
canvas.style.cursor = "grabbing";
canvas.setPointerCapture?.(event.pointerId);
};
const onMove = (event: PointerEvent) => {
if (!dragging) return;
lon = startLon - (event.clientX - startX) * 0.12;
lat = Math.max(-84, Math.min(84, startLat + (event.clientY - startY) * 0.12));
};
const onUp = () => {
dragging = false;
canvas.style.cursor = "grab";
};
const onWheel = (event: WheelEvent) => {
event.preventDefault();
fov = Math.max(38, Math.min(86, fov + event.deltaY * 0.035));
};
canvas.addEventListener("pointerdown", onDown);
canvas.addEventListener("pointermove", onMove);
canvas.addEventListener("pointerup", onUp);
canvas.addEventListener("pointercancel", onUp);
canvas.addEventListener("wheel", onWheel, { passive: false });
cleanupEvents = () => {
canvas.removeEventListener("pointerdown", onDown);
canvas.removeEventListener("pointermove", onMove);
canvas.removeEventListener("pointerup", onUp);
canvas.removeEventListener("pointercancel", onUp);
canvas.removeEventListener("wheel", onWheel);
};
const resize = () => {
if (!renderer || !mountRef.current) return;
const width = Math.max(1, mountRef.current.clientWidth);
const height = Math.max(1, mountRef.current.clientHeight);
renderer.setSize(width, height, false);
camera.aspect = width / height;
camera.updateProjectionMatrix();
};
resizeObserver = new ResizeObserver(resize);
resizeObserver.observe(mount);
resize();
const render = () => {
if (disposed || !renderer) return;
if (!dragging) lon += 0.02; // 缓慢自转
camera.fov = fov;
camera.updateProjectionMatrix();
const phi = THREE.MathUtils.degToRad(90 - lat);
const theta = THREE.MathUtils.degToRad(lon);
target.set(500 * Math.sin(phi) * Math.cos(theta), 500 * Math.cos(phi), 500 * Math.sin(phi) * Math.sin(theta));
camera.lookAt(target);
renderer.render(scene, camera);
frame = requestAnimationFrame(render);
};
render();
// 载入纹理:大图降采样到 GPU 上限,避免超限导致黑屏/失败
const image = new Image();
image.crossOrigin = "anonymous";
image.onload = () => {
if (disposed || !material) return;
try {
const w = image.naturalWidth || image.width;
const h = image.naturalHeight || image.height;
const scale = Math.min(1, maxTextureSide / w, maxTextureSide / h);
let source: HTMLImageElement | HTMLCanvasElement = image;
if (scale < 1) {
const c = document.createElement("canvas");
c.width = Math.max(1, Math.round(w * scale));
c.height = Math.max(1, Math.round(h * scale));
c.getContext("2d")?.drawImage(image, 0, 0, c.width, c.height);
source = c;
}
texture = source instanceof HTMLCanvasElement ? new THREE.CanvasTexture(source) : new THREE.Texture(source);
texture.colorSpace = THREE.SRGBColorSpace;
texture.minFilter = THREE.LinearFilter;
texture.needsUpdate = true;
material.map = texture;
material.needsUpdate = true;
setStatus("ready");
} catch {
setStatus("error");
}
};
image.onerror = () => !disposed && setStatus("error");
image.src = src;
})
.catch(() => !disposed && setStatus("error"));
return () => {
disposed = true;
cleanupEvents();
resizeObserver?.disconnect();
if (frame) cancelAnimationFrame(frame);
texture?.dispose?.();
material?.dispose?.();
geometry?.dispose?.();
if (renderer) {
renderer.domElement?.remove?.();
renderer.dispose?.();
renderer.forceContextLoss?.();
}
};
}, [src]);
return (
<div style={{ position: "relative", height: "100%", width: "100%", overflow: "hidden", borderRadius: 16, background: "#000" }}>
<div ref={mountRef} data-canvas-no-zoom onMouseDown={(e) => e.stopPropagation()} onWheel={(e) => e.stopPropagation()} style={{ position: "absolute", inset: 0 }} />
{status !== "ready" ? (
<div style={{ position: "absolute", inset: 0, display: "grid", placeItems: "center", pointerEvents: "none", color: ctx.theme.node.placeholder, fontSize: 13, background: status === "error" ? "rgba(0,0,0,0.6)" : `linear-gradient(135deg, ${ctx.theme.node.fill}, #000)` }}>
{status === "error" ? "全景图读取失败,请换 2:1 的 JPG/PNG 全景图" : "正在加载全景…"}
</div>
) : null}
</div>
);
}
// ---------------------------------------------------------------------------
// 空态:两个明确按钮——「上传全景图」和「AI 生成」(打开下方内置生成面板)。
// 整块不可点,避免误触;拖动节点照常。
// ---------------------------------------------------------------------------
function PanoramaEmpty({ ctx }: { ctx: CanvasNodeContext }) {
const fileRef = useRef<HTMLInputElement>(null);
// 本地上传:读为 dataURL 写入自身 metadata.content
const onPick = useCallback(
(file: File | undefined) => {
if (!file) return;
const reader = new FileReader();
reader.onload = () => ctx.updateMetadata({ content: String(reader.result || "") });
reader.readAsDataURL(file);
},
[ctx],
);
const t = ctx.theme;
const baseBtn = {
display: "inline-flex",
alignItems: "center",
gap: 6,
cursor: "pointer",
borderRadius: 999,
padding: "9px 18px",
fontSize: 13,
fontWeight: 600,
border: `1px solid ${t.node.stroke}`,
background: t.node.fill,
color: t.node.text,
} as const;
return (
<div style={{ height: "100%", width: "100%", display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: 14, padding: 20, borderRadius: 16, boxSizing: "border-box", background: t.node.fill }}>
<span style={{ fontSize: 34 }}>🌐</span>
<div data-canvas-no-zoom onMouseDown={(e) => e.stopPropagation()} style={{ display: "flex", gap: 10 }}>
<input ref={fileRef} type="file" accept="image/*" style={{ display: "none" }} onChange={(e) => onPick(e.target.files?.[0])} />
<button type="button" style={baseBtn} onClick={() => fileRef.current?.click()}>
🖼
</button>
<button type="button" style={{ ...baseBtn, border: "none", background: t.toolbar.activeBg, color: t.toolbar.activeText }} onClick={() => ctx.openPanel()}>
AI
</button>
</div>
<span style={{ fontSize: 12, color: t.node.placeholder, textAlign: "center" }}> 2:1 </span>
</div>
);
}
function PanoramaContent({ ctx }: CanvasNodeContentProps) {
const upstreamImage = ctx
.getUpstream()
.map((node) => node.metadata?.content)
.find((content): content is string => typeof content === "string" && Boolean(content));
const source = (typeof ctx.node.metadata?.content === "string" ? ctx.node.metadata.content : "") || upstreamImage || "";
if (!source) return <PanoramaEmpty ctx={ctx} />;
return <PanoramaViewer src={source} ctx={ctx} />;
}
export default definePlugin({
id: "panorama",
name: "3D 全景节点",
version: "1.1.0",
description: "查看 360° 等距柱状全景图,支持上传与 AI 生成,可从上游图片节点取图",
nodes: [
{
type: "panorama:viewer",
title: "3D 全景",
icon: "🌐",
description: "360° 全景查看器(上传 / AI 生成)",
defaultSize: { width: 480, height: 300 },
defaultMetadata: {},
minimapColor: "#0ea5e9",
// 宿主自动提供「交互 ⇄ 移动」开关:默认移动(拖动节点),切到交互后可转全景视角
interactionToggle: true,
// 复用宿主内置生成面板(模型选择/设置/提示词库完全一致),生成结果写回本节点。
// 前缀约束模型产出可用于球面贴图的 equirectangular 全景图。
useBuiltinPanel: { mode: "image", promptPrefix: PANORAMA_SYSTEM_PROMPT, writeBackToSelf: true },
// 作为上游被消费时,输出自身全景图,供下游节点引用
resource: (node) => {
const content = node.metadata?.content;
return typeof content === "string" && content ? { kind: "image", url: content } : null;
},
Content: PanoramaContent,
// 工具条始终提供「AI 生成」(打开下方内置面板);有图时额外提供「换图」
toolbar: (ctx) => {
const hasOwnContent = typeof ctx.node.metadata?.content === "string" && Boolean(ctx.node.metadata?.content);
return [
{
id: "panorama-generate",
title: "用 AI 生成全景图(打开下方面板)",
label: "AI 生成",
icon: "✨",
onClick: () => ctx.openPanel(),
},
...(hasOwnContent
? [
{
id: "panorama-reset",
title: "清空当前全景图,重新上传",
label: "换图",
icon: "🔄",
onClick: () => ctx.updateMetadata({ content: "" }),
},
]
: []),
];
},
},
],
});
+17
View File
@@ -0,0 +1,17 @@
{
"compilerOptions": {
"target": "es2020",
"module": "esnext",
"moduleResolution": "bundler",
"lib": ["dom", "dom.iterable", "esnext"],
"jsx": "react-jsx",
"jsxImportSource": "@infinite-canvas/plugin-sdk",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"esModuleInterop": true,
"isolatedModules": true,
"types": []
},
"include": ["src"]
}
+39
View File
@@ -0,0 +1,39 @@
# 官方插件注册表(集中构建 + 远程发布)
本目录**只放构建脚本**,不放构建产物。官方插件由 CI 构建后发布到孤儿分支 `plugins-dist`,画布经 jsDelivr 从该分支远程拉取并一键安装。第三方插件不进本流程,由用户自行填 JS URL 安装。
```
registry/
package.json # 构建依赖(esbuild + SDK)
build.mjs # 一次进程构建所有官方插件 → dist/(gitignore)+ 生成清单
dist/ # 构建产物(gitignore,不提交;CI 发布到 plugins-dist 分支)
```
**产物不进 git**:`dist/``node_modules/` 均被 `.gitignore` 覆盖。`main` 分支只有源码与脚本。
## 发布流程(CI 自动)
`.github/workflows/publish-plugins.yml` 在**打版本 tag(`v*`)**或手动触发(`workflow_dispatch`)时,与 GitHub Pages 发布一起跑:
1. `npm install && npm run build` → 在 `dist/` 产出各 `<id>.js``official-plugins.json`;
2.`dist/` 强推到孤儿分支 **`plugins-dist`**(仅含产物,force-push 覆盖)。
前端默认从下面地址读取(可用 `VITE_PLUGIN_REGISTRY_URL` 覆盖):
```
https://cdn.jsdelivr.net/gh/basketikun/infinite-canvas@plugins-dist/official-plugins.json
```
清单里每条的 `entry`(相对文件名)由前端解析成与清单同目录的绝对 URL,再走既有 URL 安装流程。jsDelivr 对分支有缓存(约数小时),需要立即生效可对该分支目录做 purge。
## 新增 / 更新官方插件
- 改完某官方插件源码后,在 `build.mjs``OFFICIAL` 里保持登记(新增插件在此加一条),提交到 `main`;
- 下次打版本 tag(或手动 `workflow_dispatch`)时,CI 自动重新构建并发布到 `plugins-dist`
## 本地自测官方面板
```bash
cd plugins/canvas/registry && npm install && npm run build # 产出 dist/
# 用任意静态服务器伺服 dist/,把 VITE_PLUGIN_REGISTRY_URL 指向其 official-plugins.json
```
+69
View File
@@ -0,0 +1,69 @@
// 官方插件集中构建:一次进程构建所有官方插件 + 生成清单,产物进 dist/(已 gitignore)。
// 官方插件目录本身不各自安装依赖:统一从本目录 node_modules 解析 SDK(nodePaths)。
// CI(publish-plugins.yml)把 dist/ 强推到 plugins-dist 分支,前端经 jsDelivr 远程拉取。
// 本地自测:`npm install && npm run build`,再把 VITE_PLUGIN_REGISTRY_URL 指向本地 dist。
import { build } from "esbuild";
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
const root = dirname(fileURLToPath(import.meta.url));
const outDir = join(root, "dist");
const nodeModules = join(root, "node_modules");
// 官方插件登记表:新增官方插件在此登记即可。
// 版本不在这里写死 —— 从各插件的 package.json 读取(单一真源),
// 与插件 src 里 definePlugin({version}) 保持一致,避免清单与产物版本脱节。
const OFFICIAL = [
{ id: "markdown", dir: "markdown", name: "Markdown 节点", description: "在画布中编辑与渲染 Markdown", icon: "📝" },
{ id: "svg", dir: "svg", name: "SVG 节点", description: "透明背景渲染 SVG,可接收上游文本节点的 SVG 源码", icon: "🔷" },
{ id: "html", dir: "html", name: "HTML 节点", description: "沙箱 iframe 渲染 HTML,支持 {{input}} 注入上游文本", icon: "🌐" },
{ id: "panorama", dir: "panorama", name: "3D 全景节点", description: "查看 360° 等距柱状全景图,可从上游图片节点取图", icon: "🧭" },
{ id: "sticky-note", dir: "sticky-note", name: "便利贴节点", description: "可自选颜色、双击编辑、拖动即可移动的便利贴", icon: "📌" },
];
// 读取插件 package.json 的 version 作为清单版本的唯一来源
async function readPluginVersion(dir) {
const pkg = JSON.parse(await readFile(join(root, "..", dir, "package.json"), "utf8"));
if (!pkg.version) throw new Error(`插件 ${dir} 的 package.json 缺少 version`);
return pkg.version;
}
await rm(outDir, { recursive: true, force: true });
await mkdir(outDir, { recursive: true });
for (const plugin of OFFICIAL) {
await build({
entryPoints: [join(root, "..", plugin.dir, "src", "index.tsx")],
outfile: join(outDir, `${plugin.id}.js`),
bundle: true,
format: "esm",
platform: "browser",
target: "es2020",
// automatic JSX → SDK 的 jsx-runtime(内部用宿主 React),react external
jsx: "automatic",
jsxImportSource: "@infinite-canvas/plugin-sdk",
loader: { ".ts": "ts", ".tsx": "tsx", ".css": "text" },
external: ["react", "react-dom"],
minify: true,
nodePaths: [nodeModules],
});
console.log(`built ${plugin.id}.js`);
}
const manifest = {
// 清单结构版本;entry 为相对本清单的 bundle 文件名,由前端解析成绝对 URL
version: 1,
plugins: await Promise.all(
OFFICIAL.map(async (plugin) => ({
id: plugin.id,
name: plugin.name,
version: await readPluginVersion(plugin.dir),
description: plugin.description,
icon: plugin.icon,
entry: `${plugin.id}.js`,
})),
),
};
await writeFile(join(outDir, "official-plugins.json"), JSON.stringify(manifest, null, 4) + "\n");
console.log(`wrote official-plugins.json (${OFFICIAL.length} plugins) → dist/`);
+517
View File
@@ -0,0 +1,517 @@
{
"name": "canvas-official-plugins",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "canvas-official-plugins",
"version": "1.0.0",
"devDependencies": {
"@infinite-canvas/plugin-sdk": "file:../sdk",
"esbuild": "^0.25.0"
}
},
"../sdk": {
"name": "@infinite-canvas/plugin-sdk",
"version": "0.1.0",
"dev": true,
"devDependencies": {
"@types/react": "19.1.12",
"esbuild": "^0.25.0",
"typescript": "^5"
},
"peerDependencies": {
"@types/react": ">=18"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
"integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
"integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
"integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
"integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
"integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
"integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
"integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
"integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
"integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
"integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
"integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
"integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
"integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
"cpu": [
"mips64el"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
"integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
"integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
"integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
"integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
"integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
"integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
"integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
"integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openharmony-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
"integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openharmony"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
"integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
"integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
"integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
"integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@infinite-canvas/plugin-sdk": {
"resolved": "../sdk",
"link": true
},
"node_modules/esbuild": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
"integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.25.12",
"@esbuild/android-arm": "0.25.12",
"@esbuild/android-arm64": "0.25.12",
"@esbuild/android-x64": "0.25.12",
"@esbuild/darwin-arm64": "0.25.12",
"@esbuild/darwin-x64": "0.25.12",
"@esbuild/freebsd-arm64": "0.25.12",
"@esbuild/freebsd-x64": "0.25.12",
"@esbuild/linux-arm": "0.25.12",
"@esbuild/linux-arm64": "0.25.12",
"@esbuild/linux-ia32": "0.25.12",
"@esbuild/linux-loong64": "0.25.12",
"@esbuild/linux-mips64el": "0.25.12",
"@esbuild/linux-ppc64": "0.25.12",
"@esbuild/linux-riscv64": "0.25.12",
"@esbuild/linux-s390x": "0.25.12",
"@esbuild/linux-x64": "0.25.12",
"@esbuild/netbsd-arm64": "0.25.12",
"@esbuild/netbsd-x64": "0.25.12",
"@esbuild/openbsd-arm64": "0.25.12",
"@esbuild/openbsd-x64": "0.25.12",
"@esbuild/openharmony-arm64": "0.25.12",
"@esbuild/sunos-x64": "0.25.12",
"@esbuild/win32-arm64": "0.25.12",
"@esbuild/win32-ia32": "0.25.12",
"@esbuild/win32-x64": "0.25.12"
}
}
}
}
+14
View File
@@ -0,0 +1,14 @@
{
"name": "canvas-official-plugins",
"version": "1.0.0",
"private": true,
"type": "module",
"description": "Infinite Canvas 官方插件的集中构建(CI 发布到 plugins-dist 分支)",
"scripts": {
"build": "node build.mjs"
},
"devDependencies": {
"@infinite-canvas/plugin-sdk": "file:../sdk",
"esbuild": "^0.25.0"
}
}
+77
View File
@@ -0,0 +1,77 @@
# @infinite-canvas/plugin-sdk
Infinite Canvas 画布节点插件的 **TypeScript SDK**。插件作者只写节点 UI 与逻辑,类型、JSX、运行时桥接、构建全部由 SDK 提供;产物仍是宿主加载器现有契约的 ESM(React external,宿主单例)。
## 提供什么
| 能力 | 说明 |
| --- | --- |
| **完整类型** | `CanvasPlugin` / `CanvasNodeDefinition` / `CanvasNodeContext` / `CanvasAgentOp` / `CanvasTheme` / `CanvasNodeData` … 全部有提示 |
| `definePlugin(...)` | 给插件对象(或工厂)补全类型;对象形式无需再 `const { React } = runtime` |
| automatic JSX | `jsxImportSource` 指向本包,TSX 自动转发到宿主 React,**不打包第二份 React** |
| 类型化 hooks | `import { useState, useEffect, useMemo, useRef, ... }`,运行时转发宿主 React |
| `buildPlugin(...)` | 统一 esbuild 构建,插件 `build.mjs` 只需一行 |
## 最小插件
```tsx
// src/index.tsx
import { definePlugin, useState } from "@infinite-canvas/plugin-sdk";
import type { CanvasNodeContentProps } from "@infinite-canvas/plugin-sdk";
function Content({ ctx }: CanvasNodeContentProps) {
const [n, setN] = useState(0);
return (
<button onMouseDown={(e) => e.stopPropagation()} onClick={() => setN((v) => v + 1)} style={{ color: ctx.theme.node.text }}>
{ctx.node.title}: {n}
</button>
);
}
export default definePlugin({
id: "my-plugin",
name: "我的插件",
version: "1.0.0",
nodes: [
{
type: "my-plugin:node",
title: "示例",
icon: "✨",
defaultSize: { width: 240, height: 160 },
Content,
},
],
});
```
```js
// build.mjs
import { buildPlugin } from "@infinite-canvas/plugin-sdk/build";
await buildPlugin(import.meta.url);
```
`npm run build` 产出 `dist/<目录名>.js` 并同步到 `web/public/plugins/`
## 依赖接入
插件 `package.json`:
```json
{
"type": "module",
"scripts": { "build": "node build.mjs", "dev": "node build.mjs --watch", "typecheck": "tsc --noEmit" },
"devDependencies": {
"@infinite-canvas/plugin-sdk": "file:../sdk",
"@types/react": "19.1.12",
"typescript": "^5"
}
}
```
插件 `tsconfig.json` 关键项:`"jsx": "react-jsx"``"jsxImportSource": "@infinite-canvas/plugin-sdk"``"moduleResolution": "bundler"`
## 设计约束
- **React 单例**:JSX 与 hooks 惰性读取 `globalThis.InfiniteCanvasRuntime.React`(宿主在加载插件前注入),react 全程 external,绝不打包第二份。
- **重依赖**:three、marked 等在源码里 `await import("https://esm.sh/...")` 动态加载,esbuild 自动 external,不进 bundle。
- **类型真源**:`src/types.ts` 是宿主 `web/src/types/canvas-plugin.ts` 公开契约的镜像;宿主契约变更时同步此处。
+75
View File
@@ -0,0 +1,75 @@
// 共享构建助手:插件的 build.mjs 只需一行 `buildPlugin(import.meta.url)`。
// 统一 esbuild 配置(automatic JSX 指向本 SDK、react external、TS/TSX/CSS loader、
// 产物同步到 web/public/plugins),消除各插件重复的构建脚本。
import { build, context } from "esbuild";
import { cp, mkdir, readFile, writeFile } from "node:fs/promises";
import { basename, dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
/**
* @param {string} metaUrl 插件 build.mjs 的 import.meta.url
* @param {{ name?: string, entry?: string, publicDir?: string, esbuild?: object, plugins?: import("esbuild").Plugin[] }} [overrides]
*/
export async function buildPlugin(metaUrl, overrides = {}) {
const root = dirname(fileURLToPath(metaUrl));
const name = overrides.name ?? basename(root); // 目录名即产物名,如 markdown → markdown.js
const distDir = join(root, "dist");
// 默认同步到仓库内 web/public/plugins(plugins/canvas/<name> → 上溯三层到仓库根)
const publicDir = overrides.publicDir ?? join(root, "..", "..", "..", "web", "public", "plugins");
const watch = process.argv.includes("--watch");
const entry = overrides.entry ?? join(root, "src", "index.tsx");
const syncToPublic = {
name: "sync-to-public",
setup(builder) {
builder.onEnd(async (result) => {
if (result.errors.length) return;
await mkdir(publicDir, { recursive: true });
await cp(join(distDir, `${name}.js`), join(publicDir, `${name}.js`));
// 维护本地插件清单 index.json,供画布启动时自动发现(该目录已 gitignore,仅本地开发用)
const indexPath = join(publicDir, "index.json");
let list = [];
try {
const parsed = JSON.parse(await readFile(indexPath, "utf8"));
if (Array.isArray(parsed)) list = parsed;
} catch {
// 无清单则新建
}
const entry = `/plugins/${name}.js`;
if (!list.includes(entry)) {
list.push(entry);
await writeFile(indexPath, JSON.stringify(list, null, 2) + "\n");
}
console.log(`[${name}] synced → web/public/plugins/${name}.js`);
});
},
};
const options = {
entryPoints: [entry],
outfile: join(distDir, `${name}.js`),
bundle: true,
format: "esm",
platform: "browser",
target: "es2020",
// automatic JSX → 转发到本 SDK 的 jsx-runtime(内部用宿主 React),插件无需自带 React
jsx: "automatic",
jsxImportSource: "@infinite-canvas/plugin-sdk",
loader: { ".js": "jsx", ".jsx": "jsx", ".ts": "ts", ".tsx": "tsx", ".css": "text" },
// 宿主提供单例 React,插件不打包 react;https:// 依赖 esbuild 自动 external
external: ["react", "react-dom"],
minify: !watch,
plugins: [syncToPublic, ...(overrides.plugins ?? [])],
...overrides.esbuild,
};
if (watch) {
const ctx = await context(options);
await ctx.watch();
console.log(`[${name}] watching src/ ...`);
} else {
await build(options);
console.log(`[${name}] built → dist/${name}.js`);
}
}
+535
View File
@@ -0,0 +1,535 @@
{
"name": "@infinite-canvas/plugin-sdk",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@infinite-canvas/plugin-sdk",
"version": "0.1.0",
"devDependencies": {
"@types/react": "19.1.12",
"esbuild": "^0.25.0",
"typescript": "^5"
},
"peerDependencies": {
"@types/react": ">=18"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
"integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
"integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
"integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
"integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
"integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
"integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
"integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
"integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
"integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
"integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
"integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
"integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
"integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
"cpu": [
"mips64el"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
"integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
"integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
"integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
"integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
"integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
"integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
"integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
"integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openharmony-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
"integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openharmony"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
"integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
"integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
"integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
"integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@types/react": {
"version": "19.1.12",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.12.tgz",
"integrity": "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==",
"dev": true,
"license": "MIT",
"dependencies": {
"csstype": "^3.0.2"
}
},
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"dev": true,
"license": "MIT"
},
"node_modules/esbuild": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
"integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.25.12",
"@esbuild/android-arm": "0.25.12",
"@esbuild/android-arm64": "0.25.12",
"@esbuild/android-x64": "0.25.12",
"@esbuild/darwin-arm64": "0.25.12",
"@esbuild/darwin-x64": "0.25.12",
"@esbuild/freebsd-arm64": "0.25.12",
"@esbuild/freebsd-x64": "0.25.12",
"@esbuild/linux-arm": "0.25.12",
"@esbuild/linux-arm64": "0.25.12",
"@esbuild/linux-ia32": "0.25.12",
"@esbuild/linux-loong64": "0.25.12",
"@esbuild/linux-mips64el": "0.25.12",
"@esbuild/linux-ppc64": "0.25.12",
"@esbuild/linux-riscv64": "0.25.12",
"@esbuild/linux-s390x": "0.25.12",
"@esbuild/linux-x64": "0.25.12",
"@esbuild/netbsd-arm64": "0.25.12",
"@esbuild/netbsd-x64": "0.25.12",
"@esbuild/openbsd-arm64": "0.25.12",
"@esbuild/openbsd-x64": "0.25.12",
"@esbuild/openharmony-arm64": "0.25.12",
"@esbuild/sunos-x64": "0.25.12",
"@esbuild/win32-arm64": "0.25.12",
"@esbuild/win32-ia32": "0.25.12",
"@esbuild/win32-x64": "0.25.12"
}
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
}
}
}
+24
View File
@@ -0,0 +1,24 @@
{
"name": "@infinite-canvas/plugin-sdk",
"version": "0.1.0",
"private": true,
"type": "module",
"description": "Infinite Canvas 画布节点插件 SDK:类型、automatic JSX 运行时、构建助手",
"exports": {
".": "./src/index.ts",
"./jsx-runtime": "./src/jsx-runtime.ts",
"./jsx-dev-runtime": "./src/jsx-dev-runtime.ts",
"./build": "./build.mjs"
},
"scripts": {
"typecheck": "tsc --noEmit"
},
"peerDependencies": {
"@types/react": ">=18"
},
"devDependencies": {
"@types/react": "19.1.12",
"esbuild": "^0.25.0",
"typescript": "^5"
}
}
+18
View File
@@ -0,0 +1,18 @@
import type { CanvasPlugin, CanvasPluginFactory, PluginRuntime } from "./types";
// 身份帮助函数:为插件对象(或工厂)补全类型,给作者完整补全与校验。
// 采用 automatic JSX 后,插件通常不再需要 runtime,直接写对象形式即可:
//
// export default definePlugin({ id, name, version, nodes: [...] })
//
// 仍支持工厂形式(需要 runtime.version/emit/on 时):
//
// export default definePlugin((runtime) => ({ ... }))
export function definePlugin(plugin: CanvasPlugin): CanvasPlugin;
export function definePlugin(factory: CanvasPluginFactory): CanvasPluginFactory;
export function definePlugin(input: CanvasPlugin | CanvasPluginFactory): CanvasPlugin | CanvasPluginFactory {
return input;
}
export type { PluginRuntime };
+8
View File
@@ -0,0 +1,8 @@
// @infinite-canvas/plugin-sdk 公开入口。
//
// 插件作者用 TS/TSX 编写,只关注节点 UI 与逻辑;类型、JSX、运行时桥接、构建
// 全部由本 SDK 提供,产物仍是宿主 loader 现有契约的 ESM(React external,宿主单例)。
export * from "./types";
export { definePlugin } from "./define-plugin";
export { getReact, getRuntime, useState, useEffect, useLayoutEffect, useMemo, useCallback, useRef, useReducer, useContext, useId } from "./runtime";
+16
View File
@@ -0,0 +1,16 @@
// automatic JSX 的 dev 变体(编译器在 dev 模式会引用 jsxDEV)。转发到同一套 createElement。
import type * as React from "react";
import { getReact } from "./runtime";
import { Fragment } from "./jsx-runtime";
export { Fragment };
export type { JSX } from "./jsx-runtime";
export function jsxDEV(type: unknown, props: Record<string, unknown> | null, key?: unknown): React.ReactElement {
const react = getReact();
const resolvedType = type === Fragment ? react.Fragment : type;
const config = key === undefined ? props : { ...(props ?? {}), key };
return react.createElement(resolvedType as never, config as never);
}
+38
View File
@@ -0,0 +1,38 @@
// automatic JSX 运行时:esbuild/tsc 的 `jsxImportSource` 指向本包时,
// TSX 会被编译成对本模块 jsx()/jsxs() 的调用。这里统一转发到宿主 React.createElement,
// 让插件写纯 TSX、无需手动 `const { React } = runtime`,同时 react 全程 external。
import type * as React from "react";
import { getReact } from "./runtime";
// Fragment 哨兵:渲染时才解析为宿主 React.Fragment,避免模块顶层触碰运行时。
export const Fragment = Symbol.for("infinite-canvas.jsx.fragment") as unknown as React.ExoticComponent<{ children?: React.ReactNode }>;
function createElement(type: unknown, props: Record<string, unknown> | null, key?: unknown): React.ReactElement {
const react = getReact();
const resolvedType = type === Fragment ? react.Fragment : type;
// automatic 运行时已把 children 放进 props;key 单独传入以避免展开 key 警告。
const config = key === undefined ? props : { ...(props ?? {}), key };
return react.createElement(resolvedType as never, config as never);
}
export function jsx(type: unknown, props: Record<string, unknown> | null, key?: unknown): React.ReactElement {
return createElement(type, props, key);
}
// jsxs 用于静态多子节点;转发逻辑与 jsx 一致(children 已在 props 内)。
export const jsxs = jsx;
// 让 `jsxImportSource` 指向本包的编译器能从这里取到 JSX 内建标签类型(复用 @types/react)。
export namespace JSX {
export type Element = React.JSX.Element;
export type ElementType = React.JSX.ElementType;
export type ElementClass = React.JSX.ElementClass;
export type ElementAttributesProperty = React.JSX.ElementAttributesProperty;
export type ElementChildrenAttribute = React.JSX.ElementChildrenAttribute;
export type LibraryManagedAttributes<C, P> = React.JSX.LibraryManagedAttributes<C, P>;
export type IntrinsicAttributes = React.JSX.IntrinsicAttributes;
export type IntrinsicClassAttributes<T> = React.JSX.IntrinsicClassAttributes<T>;
export type IntrinsicElements = React.JSX.IntrinsicElements;
}
+37
View File
@@ -0,0 +1,37 @@
// 运行时桥接:所有对宿主 React 的访问都惰性读取全局运行时,
// 保证插件与画布共用同一份 React(不打包第二份),且不在模块顶层触碰运行时
// (宿主 loader 会先 import 插件模块、再设置运行时)。
import type * as React from "react";
import type { PluginRuntime } from "./types";
type RuntimeGlobal = { InfiniteCanvasRuntime?: PluginRuntime };
/** 取宿主注入的插件运行时(含 React、事件总线、injectCSS 等)。 */
export function getRuntime(): PluginRuntime {
const runtime = (globalThis as unknown as RuntimeGlobal).InfiniteCanvasRuntime;
if (!runtime) {
throw new Error("[plugin-sdk] Infinite Canvas 运行时未就绪:请在画布宿主中加载本插件");
}
return runtime;
}
/** 取宿主的 React 实例。仅在渲染/hook 调用时(运行时已就绪)使用。 */
export function getReact(): typeof React {
return getRuntime().React as unknown as typeof React;
}
// --- 类型完整的 hooks 转发:签名取自 @types/react,运行时转发到宿主 React ---
// 这样插件作者可以直接 `import { useState } from "@infinite-canvas/plugin-sdk"`,
// 无需再从 runtime 里解构 React。
export const useState: typeof React.useState = ((...args: unknown[]) => (getReact().useState as (...a: unknown[]) => unknown)(...args)) as typeof React.useState;
export const useEffect: typeof React.useEffect = ((...args: unknown[]) => (getReact().useEffect as (...a: unknown[]) => unknown)(...args)) as typeof React.useEffect;
export const useLayoutEffect: typeof React.useLayoutEffect = ((...args: unknown[]) => (getReact().useLayoutEffect as (...a: unknown[]) => unknown)(...args)) as typeof React.useLayoutEffect;
export const useMemo: typeof React.useMemo = ((...args: unknown[]) => (getReact().useMemo as (...a: unknown[]) => unknown)(...args)) as typeof React.useMemo;
export const useCallback: typeof React.useCallback = ((...args: unknown[]) => (getReact().useCallback as (...a: unknown[]) => unknown)(...args)) as typeof React.useCallback;
export const useRef: typeof React.useRef = ((...args: unknown[]) => (getReact().useRef as (...a: unknown[]) => unknown)(...args)) as typeof React.useRef;
export const useReducer: typeof React.useReducer = ((...args: unknown[]) => (getReact().useReducer as (...a: unknown[]) => unknown)(...args)) as typeof React.useReducer;
export const useContext: typeof React.useContext = ((...args: unknown[]) => (getReact().useContext as (...a: unknown[]) => unknown)(...args)) as typeof React.useContext;
export const useId: typeof React.useId = ((...args: unknown[]) => (getReact().useId as (...a: unknown[]) => unknown)(...args)) as typeof React.useId;
+331
View File
@@ -0,0 +1,331 @@
// Infinite Canvas 插件公共契约类型。
//
// 这是插件作者面向的「公开接口」子集,自包含、不依赖宿主 `@/` 内部模块,
// 因此可以被独立构建的插件包直接 import,获得完整的 TS 提示。
//
// 真源:宿主 `web/src/types/canvas-plugin.ts` 及其引用的类型。本文件是它的公开镜像,
// 若宿主契约变更,请同步更新此处(两者结构保持一致即可,无需逐字节相同)。
import type { ComponentType, ReactNode } from "react";
// ---------------------------------------------------------------------------
// 画布基础几何与节点数据
// ---------------------------------------------------------------------------
export type Position = { x: number; y: number };
export type ViewportTransform = { x: number; y: number; k: number };
// 内置节点类型;插件节点建议用 "<pluginId>:<name>"。放开为字符串以便扩展。
export type CanvasBuiltinNodeType = "image" | "text" | "config" | "video" | "audio" | "group";
export type CanvasNodeTypeId = CanvasBuiltinNodeType | (string & {});
export type CanvasNodeStatus = "idle" | "success" | "loading" | "error";
export type CanvasGenerationMode = "text" | "image" | "video" | "audio";
export type CanvasImageGenerationType = "generation" | "edit";
// 节点 metadata 是扁平可选字段袋;插件自定义字段可直接写入(内容惯例放 content)。
export type CanvasNodeMetadata = {
content?: string;
composerContent?: string;
prompt?: string;
status?: CanvasNodeStatus;
errorDetails?: string;
fontSize?: number;
generationMode?: CanvasGenerationMode;
generationType?: CanvasImageGenerationType;
model?: string;
size?: string;
quality?: string;
count?: number;
seconds?: string;
vquality?: string;
generateAudio?: string;
watermark?: string;
audioVoice?: string;
audioFormat?: string;
audioSpeed?: string;
audioInstructions?: string;
references?: string[];
naturalWidth?: number;
naturalHeight?: number;
freeResize?: boolean;
isBatchRoot?: boolean;
batchRootId?: string;
batchChildIds?: string[];
batchUsesReferenceImages?: boolean;
primaryImageId?: string;
imageBatchExpanded?: boolean;
storageKey?: string;
mimeType?: string;
bytes?: number;
durationMs?: number;
groupId?: string;
// 插件可写入任意自定义字段
[key: string]: unknown;
};
export type CanvasNodeData = {
id: string;
type: CanvasNodeTypeId;
title: string;
position: Position;
width: number;
height: number;
metadata?: CanvasNodeMetadata;
};
export type CanvasConnection = {
id: string;
fromNodeId: string;
toNodeId: string;
};
// ---------------------------------------------------------------------------
// 主题 token(用来让插件 UI 跟随画布明暗主题)
// ---------------------------------------------------------------------------
export type CanvasTheme = {
canvas: {
background: string;
dot: string;
line: string;
selectionStroke: string;
selectionFill: string;
};
node: {
label: string;
fill: string;
panel: string;
stroke: string;
activeStroke: string;
placeholder: string;
text: string;
muted: string;
faint: string;
};
toolbar: {
panel: string;
border: string;
item: string;
itemHover: string;
activeBg: string;
activeText: string;
};
};
// ---------------------------------------------------------------------------
// 画布指令集(ctx.applyOps):与 AI Agent 同级的画布操作能力
// ---------------------------------------------------------------------------
export type CanvasAgentOp =
| { type: "add_node"; id?: string; nodeType?: CanvasNodeTypeId; title?: string; position?: { x: number; y: number }; x?: number; y?: number; width?: number; height?: number; metadata?: CanvasNodeMetadata }
| { type: "update_node"; id: string; patch?: Partial<CanvasNodeData>; metadata?: CanvasNodeMetadata }
| { type: "delete_node"; id?: string; ids?: string[]; nodeType?: CanvasNodeTypeId }
| { type: "delete_connections"; id?: string; ids?: string[]; all?: boolean }
| { type: "connect_nodes"; id?: string; fromNodeId: string; toNodeId: string }
| { type: "set_viewport"; viewport: ViewportTransform }
| { type: "select_nodes"; ids: string[] }
| { type: "run_generation"; nodeId: string; mode?: CanvasGenerationMode; prompt?: string };
// ---------------------------------------------------------------------------
// 资源:插件节点作为上游输入被消费时输出什么(接入生成/引用体系)
// ---------------------------------------------------------------------------
export type CanvasResourceKind = "image" | "video" | "audio" | "text";
export type CanvasNodeResource = { kind: CanvasResourceKind; text?: string; url?: string };
// ---------------------------------------------------------------------------
// AI 生成:插件直接复用宿主的模型/密钥配置发起生成(生图/生视频/生文本/生音频)
//
// 插件本身拿不到 API Key 与模型配置,这些能力由宿主注入。前置/系统提示词由
// 插件自行拼进 prompt(宿主不感知),因此不同插件可各自定制自己的提示词策略。
// 若宿主 AI 配置未就绪,会抛错(并由宿主提示用户去配置),插件用 try/catch 处理即可。
// ---------------------------------------------------------------------------
// 生成公共可选项;references 为图生图/图生视频的参考图(dataURL 或可访问 URL)
export type GenerateOptions = {
signal?: AbortSignal;
references?: string[];
model?: string; // 指定模型(取自 ai.listModels 的 value);缺省用宿主当前配置
};
export type GenerateImageOptions = GenerateOptions & {
count?: number; // 期望生成张数(宿主会按模型上限裁剪)
size?: string; // 形如 "1024x1024" / "auto";缺省用宿主当前配置
};
export type GenerateImageResult = {
// 生成图的 dataURL(已可直接作为 <img src> 或 three 纹理使用)
images: string[];
};
export type GenerateVideoOptions = GenerateOptions & {
size?: string;
seconds?: string;
};
export type GenerateVideoResult = {
url: string; // 视频可访问 URL
mimeType: string;
width?: number;
height?: number;
durationMs?: number;
};
export type GenerateTextOptions = {
signal?: AbortSignal;
model?: string;
system?: string; // 附加系统提示词(拼在宿主系统提示之后)
onDelta?: (text: string) => void; // 流式增量回调
};
export type GenerateTextResult = {
text: string;
};
// 一个可选模型:value 传回给 generateXxx({ model }),label 用于展示
export type ModelCapability = "image" | "video" | "text" | "audio";
export type ModelOption = { value: string; label: string };
// 宿主注入的 AI 生成能力,挂在 ctx.ai 下。任何插件均可调用。
export type CanvasPluginAi = {
generateImage: (prompt: string, options?: GenerateImageOptions) => Promise<GenerateImageResult>;
generateVideo: (prompt: string, options?: GenerateVideoOptions) => Promise<GenerateVideoResult>;
generateText: (prompt: string, options?: GenerateTextOptions) => Promise<GenerateTextResult>;
// 列出某能力下用户已配置的可选模型;不传能力则返回全部
listModels: (capability?: ModelCapability) => ModelOption[];
// 该能力当前默认选中的模型 value(可作为下拉框初始值)
defaultModel: (capability: ModelCapability) => string;
};
// ---------------------------------------------------------------------------
// 节点上下文:每个节点渲染时注入,是插件与画布交互的核心接口
// ---------------------------------------------------------------------------
export type PluginStorage = {
get: <T = unknown>(key: string) => Promise<T | null>;
set: (key: string, value: unknown) => Promise<void>;
remove: (key: string) => Promise<void>;
};
export type CanvasNodeContext = {
// 自身数据
node: CanvasNodeData;
theme: CanvasTheme;
scale: number;
isSelected: boolean; // 该节点当前是否被选中(用于按需启用 iframe 交互等)
updateMetadata: (patch: CanvasNodeMetadata) => void;
updateNode: (patch: Partial<Pick<CanvasNodeData, "title" | "width" | "height">>) => void;
// 图访问
getNode: (id: string) => CanvasNodeData | null;
getNodes: () => CanvasNodeData[];
getConnections: () => CanvasConnection[];
getUpstream: () => CanvasNodeData[];
getDownstream: () => CanvasNodeData[];
// 画布操作(复用 Agent 指令集)
applyOps: (ops: CanvasAgentOp[]) => void;
// 节点间/插件间通信
emit: (event: string, payload?: unknown) => void;
on: (event: string, handler: (payload: unknown) => void) => () => void;
// AI 生成能力(生图/生视频/生文本),复用宿主模型配置
ai: CanvasPluginAi;
// 打开/关闭本节点下方的自定义 Panel(需在节点定义里提供 Panel)
openPanel: () => void;
closePanel: () => void;
// 插件私有持久化,按插件 id 命名空间隔离
storage: PluginStorage;
};
// ---------------------------------------------------------------------------
// 节点定义:内置节点与插件节点统一走这套结构
// ---------------------------------------------------------------------------
export type CanvasNodeToolbarItem = {
id: string;
title: string;
label: string;
icon: ReactNode;
onClick: () => void;
active?: boolean;
danger?: boolean;
};
export type CanvasNodeContentProps = { ctx: CanvasNodeContext };
export type CanvasNodePanelProps = { ctx: CanvasNodeContext; onClose: () => void };
// 复用宿主内置生成面板(与图片/视频/文本节点同一个组件:模型选择、参数设置、
// 提示词库、运行/停止状态全部一致)。声明它即可获得完整生成体验,无需自写面板。
export type CanvasBuiltinPanelConfig = {
mode: "image" | "video" | "text" | "audio"; // 生成类型,决定面板里的模型/设置项
// 提交给模型前自动拼在用户提示词前面的固定前缀(如全景图的 equirectangular 约束)
promptPrefix?: string;
// true(默认)时生成结果写回本节点自身 metadata.content;false 则按内置逻辑生成到下游新节点
writeBackToSelf?: boolean;
};
export type CanvasNodeDefinition = {
type: string; // 建议 "<pluginId>:<name>",全局唯一
title: string;
icon: ReactNode; // emoji 字符串或任意 ReactNode
description?: string;
defaultSize: { width: number; height: number };
defaultMetadata?: CanvasNodeMetadata;
minimapColor?: string;
showInCreateMenu?: boolean; // 默认 true
hasSourceHandle?: boolean; // 右侧输出连接点,默认 true
hidePanel?: boolean; // 为 true 时:点击/新建不弹出下方面板(含内置生图面板),纯展示型节点用
// 为 true 时:节点卡片背景与边框透明,内容直接融入画布(如 SVG/矢量图);选中时仍显示选中描边
transparentBackground?: boolean;
autoOpenPanel?: boolean; // 为 true 时:单击节点自动打开自定义 Panel(默认仅内置节点单击自动打开)
// 复用宿主内置生成面板;与自定义 Panel 二选一(同时提供时优先 Panel)
useBuiltinPanel?: CanvasBuiltinPanelConfig;
// 为 true 时:宿主自动在工具条加「交互 ⇄ 移动」开关,并按 metadata.interactive 控制内容层指针事件。
// 默认(interactive 未设/为 false)为「移动」态:内容不吃指针,拖动整块移动节点;
// 切到「交互」态后内容可点击/拖拽(如全景转视角、iframe 操作)。适合内部有交互的展示型节点。
interactionToggle?: boolean;
// 配合 interactionToggle:返回 true 表示当前节点内容「强制可交互」(如编辑态),
// 此时忽略 interactive 标志、始终允许操作,并隐藏移动/交互开关。缺省视为 false。
forceInteractive?: (node: CanvasNodeData) => boolean;
keepAspectRatio?: (node: CanvasNodeData) => boolean;
resource?: (node: CanvasNodeData) => CanvasNodeResource | null;
// 渲染
Content?: ComponentType<CanvasNodeContentProps>;
Panel?: ComponentType<CanvasNodePanelProps>; // 节点下方面板(自定义)
toolbar?: (ctx: CanvasNodeContext) => CanvasNodeToolbarItem[];
onDoubleClick?: (ctx: CanvasNodeContext) => boolean; // 返回 true 表示已处理
};
// ---------------------------------------------------------------------------
// 插件运行时与插件包
// ---------------------------------------------------------------------------
// 插件启动时(setup)可访问的应用能力
export type CanvasPluginApp = {
version: string;
emit: (event: string, payload?: unknown) => void;
on: (event: string, handler: (payload: unknown) => void) => () => void;
// 注入插件样式,返回移除函数;传 key 时同 key 覆盖旧样式
injectCSS: (css: string, key?: string) => () => void;
};
// 宿主注入的运行时(工厂形式插件的入参),内含宿主 React 实例避免双 React
export type PluginRuntime = CanvasPluginApp & {
React: typeof import("react");
jsx: typeof import("react").createElement;
Fragment: typeof import("react").Fragment;
};
// 插件包(默认导出对象,或返回它的工厂函数)
export type CanvasPlugin = {
id: string; // 唯一,kebab-case
name: string;
version: string;
description?: string;
minAppVersion?: string;
css?: string; // 插件样式,启用时自动注入、卸载/禁用时自动清理
nodes: CanvasNodeDefinition[];
setup?: (app: CanvasPluginApp) => void | (() => void);
};
export type CanvasPluginFactory = (runtime: PluginRuntime) => CanvasPlugin;
+18
View File
@@ -0,0 +1,18 @@
{
"compilerOptions": {
"target": "es2020",
"module": "esnext",
"moduleResolution": "bundler",
"lib": ["dom", "dom.iterable", "esnext"],
"jsx": "react-jsx",
"jsxImportSource": ".",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"esModuleInterop": true,
"isolatedModules": true,
"allowJs": true,
"checkJs": false
},
"include": ["src", "build.mjs"]
}
+17
View File
@@ -0,0 +1,17 @@
# 便利贴节点插件
Infinite Canvas 画布节点插件:彩色便利贴,可换色、编辑,并演示用 `ctx.applyOps` 衍生一个连到自己的文本节点(节点间交互示例)。
## 构建
```bash
npm install
npm run build # 产物 dist/sticky-note.js,并同步到 web/public/plugins/sticky-note.js
npm run dev # watch
```
## 安装
画布 → 左上菜单「节点插件」→ 安装 URL 填 `/plugins/sticky-note.js`(或托管后的公网 URL)。
插件契约见 `plugins/canvas/README.md`
+3
View File
@@ -0,0 +1,3 @@
import { buildPlugin } from "@infinite-canvas/plugin-sdk/build";
await buildPlugin(import.meta.url);
+65
View File
@@ -0,0 +1,65 @@
{
"name": "canvas-plugin-sticky-note",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "canvas-plugin-sticky-note",
"version": "1.0.0",
"devDependencies": {
"@infinite-canvas/plugin-sdk": "file:../sdk",
"@types/react": "19.1.12",
"typescript": "^5"
}
},
"../sdk": {
"name": "@infinite-canvas/plugin-sdk",
"version": "0.1.0",
"dev": true,
"devDependencies": {
"@types/react": "19.1.12",
"esbuild": "^0.25.0",
"typescript": "^5"
},
"peerDependencies": {
"@types/react": ">=18"
}
},
"node_modules/@infinite-canvas/plugin-sdk": {
"resolved": "../sdk",
"link": true
},
"node_modules/@types/react": {
"version": "19.1.12",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.12.tgz",
"integrity": "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==",
"dev": true,
"license": "MIT",
"dependencies": {
"csstype": "^3.0.2"
}
},
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"dev": true,
"license": "MIT"
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
}
}
}
+17
View File
@@ -0,0 +1,17 @@
{
"name": "canvas-plugin-sticky-note",
"version": "1.1.0",
"private": true,
"type": "module",
"description": "Infinite Canvas 便利贴节点插件",
"scripts": {
"build": "node build.mjs",
"dev": "node build.mjs --watch",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"@infinite-canvas/plugin-sdk": "file:../sdk",
"@types/react": "19.1.12",
"typescript": "^5"
}
}
+161
View File
@@ -0,0 +1,161 @@
// 便利贴节点:纯展示便利贴——整块可拖动、双击编辑、右上角自选颜色。
// 不再声明 resource(避免宿主在右上角显示「文本N」资源角标),也不再衍生节点。
import { definePlugin, useEffect, useRef, useState } from "@infinite-canvas/plugin-sdk";
import type { CanvasNodeContentProps } from "@infinite-canvas/plugin-sdk";
// 预设便签色(点选切换),并额外提供自定义取色
const PRESET_COLORS = ["#fde68a", "#fca5a5", "#fdba74", "#a7f3d0", "#bfdbfe", "#ddd6fe", "#f9a8d4", "#e7e5e4"];
const DEFAULT_COLOR = PRESET_COLORS[0];
function StickyNoteContent({ ctx }: CanvasNodeContentProps) {
const [editing, setEditing] = useState(false);
const [paletteOpen, setPaletteOpen] = useState(false);
// 取色时的本地预览色:仅本组件即时重渲染,避免每次都写宿主 store
const [draftColor, setDraftColor] = useState<string | null>(null);
const rootRef = useRef<HTMLDivElement>(null);
const commitTimerRef = useRef<number | null>(null);
// pluginColor 是插件自定义 metadata 字段(读出为 unknown,按需断言)
const committedColor = (ctx.node.metadata?.pluginColor as string | undefined) || DEFAULT_COLOR;
const color = draftColor ?? committedColor;
const content = (ctx.node.metadata?.content as string | undefined) || "";
// 点击便利贴外部时:退出编辑并收起调色板。
// 监听 pointerdown 的 capture 阶段:宿主画布在 pointerdown 上 preventDefault(会抑制
// 兼容 mousedown 事件),所以必须用 pointerdown;capture 阶段又能先于宿主的 stopPropagation
// 触发,避免「按 Esc 能退出、点别处退不出」。
useEffect(() => {
if (!editing && !paletteOpen) return;
const onDocDown = (e: PointerEvent) => {
if (rootRef.current && !rootRef.current.contains(e.target as Node)) {
setEditing(false);
setPaletteOpen(false);
}
};
document.addEventListener("pointerdown", onDocDown, true);
return () => document.removeEventListener("pointerdown", onDocDown, true);
}, [editing, paletteOpen]);
const pickColor = (next: string) => ctx.updateMetadata({ pluginColor: next });
// 连续取色(系统取色器拖动时 onChange 高频触发)会不停调用 updateMetadata,
// 而宿主每次都会整表重建 + 全画布重渲染 + 持久化,导致卡顿。
// 这里先本地预览(setDraftColor),再节流提交到宿主。
const previewColor = (next: string) => {
setDraftColor(next);
if (commitTimerRef.current) clearTimeout(commitTimerRef.current);
commitTimerRef.current = window.setTimeout(() => {
commitTimerRef.current = null;
pickColor(next);
}, 150);
};
// 立即提交(点击预设色、取色器关闭时用):清掉待提交的节流并写入。
const commitColor = (next: string) => {
if (commitTimerRef.current) {
clearTimeout(commitTimerRef.current);
commitTimerRef.current = null;
}
setDraftColor(next);
pickColor(next);
};
useEffect(() => () => {
if (commitTimerRef.current) clearTimeout(commitTimerRef.current);
}, []);
// 宿主里的颜色一旦真正变化(提交完成 / 撤销重做),清掉本地预览,回到 store 为准。
// 拖动取色期间 committedColor 不变,draftColor 得以保留,故不影响即时预览。
useEffect(() => {
setDraftColor(null);
}, [committedColor]);
// 交互控件上按下时阻止冒泡,避免误触发节点拖动/双击编辑
const stop = (e: { stopPropagation: () => void }) => e.stopPropagation();
return (
<div
ref={rootRef}
data-canvas-no-zoom
style={{ position: "relative", height: "100%", width: "100%", display: "flex", flexDirection: "column", background: color, borderRadius: 16, padding: 14, boxSizing: "border-box", cursor: editing ? "text" : "move", boxShadow: "inset 0 1px 0 rgba(255,255,255,.45)" }}
onDoubleClick={(e) => {
e.stopPropagation();
setEditing(true);
}}
>
{/* 右上角:当前颜色小圆点,点开后自选颜色(预设 + 自定义) */}
<div style={{ position: "absolute", top: 8, right: 8, zIndex: 5 }} onMouseDown={stop} onDoubleClick={stop}>
<button
type="button"
title="选择颜色"
onClick={() => setPaletteOpen((v) => !v)}
style={{ width: 22, height: 22, borderRadius: "50%", border: "2px solid rgba(0,0,0,.25)", background: color, cursor: "pointer", padding: 0, boxShadow: "0 1px 3px rgba(0,0,0,.2)" }}
/>
{paletteOpen ? (
<div style={{ position: "absolute", top: 28, right: 0, display: "grid", gridTemplateColumns: "repeat(4, 20px)", gap: 6, padding: 8, borderRadius: 12, background: "#fff", boxShadow: "0 8px 24px rgba(0,0,0,.18)" }}>
{PRESET_COLORS.map((c) => (
<button
key={c}
type="button"
title={c}
onClick={() => {
commitColor(c);
setPaletteOpen(false);
}}
style={{ width: 20, height: 20, borderRadius: "50%", border: c === color ? "2px solid #1c1917" : "2px solid rgba(0,0,0,.12)", background: c, cursor: "pointer", padding: 0 }}
/>
))}
{/* 自定义取色:点击彩环唤起系统取色器。拖动时本地预览 + 节流提交,松开(blur)时立即提交 */}
<label title="自定义颜色" style={{ position: "relative", width: 20, height: 20, borderRadius: "50%", cursor: "pointer", background: "conic-gradient(red, yellow, lime, aqua, blue, magenta, red)", border: "2px solid rgba(0,0,0,.12)", boxSizing: "border-box" }}>
<input type="color" value={color} onChange={(e) => previewColor(e.target.value)} onBlur={(e) => commitColor(e.target.value)} style={{ position: "absolute", inset: 0, opacity: 0, width: "100%", height: "100%", border: "none", padding: 0, cursor: "pointer" }} />
</label>
</div>
) : null}
</div>
{/* 内容区:双击进入编辑;非编辑态整块可直接拖动移动节点 */}
{editing ? (
<textarea
autoFocus
value={content}
placeholder="输入便利贴内容…(点击别处或按 Esc 退出编辑)"
onChange={(e) => ctx.updateMetadata({ content: e.target.value })}
onBlur={() => setEditing(false)}
onKeyDown={(e) => {
if (e.key === "Escape") {
e.stopPropagation();
setEditing(false);
}
}}
onMouseDown={stop}
onPointerDown={stop}
onWheel={stop}
style={{ flex: 1, width: "100%", resize: "none", border: "none", outline: "none", background: "transparent", color: "#1c1917", fontSize: 15, lineHeight: 1.5, fontFamily: "inherit" }}
/>
) : (
<div style={{ flex: 1, whiteSpace: "pre-wrap", overflow: "hidden", color: content ? "#1c1917" : "rgba(28,25,23,.45)", fontSize: 15, lineHeight: 1.5, userSelect: "none", paddingRight: 22 }}>{content || "双击编辑便利贴"}</div>
)}
</div>
);
}
export default definePlugin({
id: "sticky-note",
name: "便利贴节点",
version: "1.1.0",
description: "可自选颜色、双击编辑、拖动即可移动的便利贴",
nodes: [
{
type: "sticky-note:note",
title: "便利贴",
icon: "📌",
description: "彩色便利贴",
defaultSize: { width: 240, height: 200 },
defaultMetadata: { content: "", pluginColor: DEFAULT_COLOR },
minimapColor: "#f59e0b",
// 纯记事节点:不弹出下方生成面板(默认会是「生成图片」的提示词面板)
hidePanel: true,
Content: StickyNoteContent,
},
],
});
+17
View File
@@ -0,0 +1,17 @@
{
"compilerOptions": {
"target": "es2020",
"module": "esnext",
"moduleResolution": "bundler",
"lib": ["dom", "dom.iterable", "esnext"],
"jsx": "react-jsx",
"jsxImportSource": "@infinite-canvas/plugin-sdk",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"esModuleInterop": true,
"isolatedModules": true,
"types": []
},
"include": ["src"]
}
+17
View File
@@ -0,0 +1,17 @@
# SVG 节点插件
Infinite Canvas 画布节点插件:编辑与渲染 SVG,无自身内容时自动取上游文本节点里的 SVG 源码。
## 构建
```bash
npm install
npm run build # 产物 dist/svg.js,并同步到 web/public/plugins/svg.js
npm run dev # watch
```
## 安装
画布 → 左上菜单「节点插件」→ 安装 URL 填 `/plugins/svg.js`(或托管后的公网 URL)。
插件契约见 `plugins/canvas/README.md`
+3
View File
@@ -0,0 +1,3 @@
import { buildPlugin } from "@infinite-canvas/plugin-sdk/build";
await buildPlugin(import.meta.url);
+65
View File
@@ -0,0 +1,65 @@
{
"name": "canvas-plugin-svg",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "canvas-plugin-svg",
"version": "1.0.0",
"devDependencies": {
"@infinite-canvas/plugin-sdk": "file:../sdk",
"@types/react": "19.1.12",
"typescript": "^5"
}
},
"../sdk": {
"name": "@infinite-canvas/plugin-sdk",
"version": "0.1.0",
"dev": true,
"devDependencies": {
"@types/react": "19.1.12",
"esbuild": "^0.25.0",
"typescript": "^5"
},
"peerDependencies": {
"@types/react": ">=18"
}
},
"node_modules/@infinite-canvas/plugin-sdk": {
"resolved": "../sdk",
"link": true
},
"node_modules/@types/react": {
"version": "19.1.12",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.12.tgz",
"integrity": "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==",
"dev": true,
"license": "MIT",
"dependencies": {
"csstype": "^3.0.2"
}
},
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"dev": true,
"license": "MIT"
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
}
}
}
+17
View File
@@ -0,0 +1,17 @@
{
"name": "canvas-plugin-svg",
"version": "1.1.0",
"private": true,
"type": "module",
"description": "Infinite Canvas SVG 节点插件",
"scripts": {
"build": "node build.mjs",
"dev": "node build.mjs --watch",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"@infinite-canvas/plugin-sdk": "file:../sdk",
"@types/react": "19.1.12",
"typescript": "^5"
}
}
+97
View File
@@ -0,0 +1,97 @@
// SVG 节点:编辑与渲染 SVG,透明背景直接融入画布;无自身内容时可取上游文本节点里的 SVG 源码。
import { definePlugin, useEffect, useRef, useState } from "@infinite-canvas/plugin-sdk";
import type { CanvasNodeContentProps } from "@infinite-canvas/plugin-sdk";
function SvgContent({ ctx }: CanvasNodeContentProps) {
const [editing, setEditing] = useState(false);
const rootRef = useRef<HTMLDivElement>(null);
const stored = ctx.node.metadata?.content as string | undefined;
const upstream = ctx
.getUpstream()
.map((node) => node.metadata?.content)
.find((text): text is string => typeof text === "string" && text.trim().startsWith("<svg"));
const value = stored ?? "";
const svg = value.trim() || upstream || "";
// 无自身内容但有上游 SVG:自动采用上游源码
useEffect(() => {
if (stored === undefined && upstream) ctx.updateMetadata({ content: upstream });
}, [ctx, stored, upstream]);
// 点击节点外部时退出编辑。用 pointerdown 的 capture 阶段:宿主画布在 pointerdown 上
// preventDefault 会抑制 mousedown,capture 又能先于宿主 stopPropagation 触发。
useEffect(() => {
if (!editing) return;
const onDocDown = (e: PointerEvent) => {
if (rootRef.current && !rootRef.current.contains(e.target as Node)) setEditing(false);
};
document.addEventListener("pointerdown", onDocDown, true);
return () => document.removeEventListener("pointerdown", onDocDown, true);
}, [editing]);
// 交互控件上按下时阻止冒泡,避免误触发节点拖动/双击编辑
const stop = (e: { stopPropagation: () => void }) => e.stopPropagation();
const toggle = { position: "absolute", right: 8, top: 8, zIndex: 20, width: 32, height: 32, display: "grid", placeItems: "center", borderRadius: 8, border: `1px solid ${ctx.theme.node.stroke}`, background: `${ctx.theme.toolbar.panel}dd`, color: ctx.theme.node.text, cursor: "pointer" } as const;
return (
<div
ref={rootRef}
data-canvas-no-zoom
style={{ position: "relative", height: "100%", width: "100%", display: "flex", flexDirection: "column", cursor: editing ? "text" : "move" }}
onDoubleClick={(e) => {
e.stopPropagation();
setEditing(true);
}}
>
<button type="button" style={toggle} onMouseDown={stop} onClick={() => setEditing((v) => !v)} title={editing ? "预览" : "编辑源码"}>
{editing ? "👁" : "✎"}
</button>
{editing ? (
<textarea
autoFocus
value={value}
placeholder="粘贴 SVG 源码,如 <svg …>…</svg>"
onChange={(e) => ctx.updateMetadata({ content: e.target.value })}
onKeyDown={(e) => {
if (e.key === "Escape") {
e.stopPropagation();
setEditing(false);
}
}}
onMouseDown={stop}
onPointerDown={stop}
onWheel={stop}
style={{ height: "100%", width: "100%", resize: "none", background: ctx.theme.node.fill, borderRadius: 16, padding: 16, boxSizing: "border-box", fontFamily: "monospace", fontSize: 12, outline: "none", border: `1px solid ${ctx.theme.node.stroke}`, color: ctx.theme.node.text }}
/>
) : svg ? (
// pointerEvents:none 让整块可拖动移动,双击/拖拽都命中外层节点
<div style={{ height: "100%", width: "100%", display: "flex", alignItems: "center", justifyContent: "center", padding: 12, boxSizing: "border-box", pointerEvents: "none" }} dangerouslySetInnerHTML={{ __html: svg }} />
) : (
<div style={{ height: "100%", width: "100%", display: "flex", alignItems: "center", justifyContent: "center", padding: 16, boxSizing: "border-box", color: ctx.theme.node.placeholder, fontSize: 13, textAlign: "center", pointerEvents: "none" }}>, SVG </div>
)}
</div>
);
}
export default definePlugin({
id: "svg",
name: "SVG 节点",
version: "1.1.0",
description: "透明背景渲染 SVG 矢量图,可接收上游文本节点的 SVG 源码",
nodes: [
{
type: "svg:vector",
title: "SVG",
icon: "🔷",
description: "渲染 SVG 矢量图",
defaultSize: { width: 320, height: 320 },
defaultMetadata: {},
minimapColor: "#14b8a6",
// 背景/边框透明,矢量图直接融入画布
transparentBackground: true,
Content: SvgContent,
},
],
});
+17
View File
@@ -0,0 +1,17 @@
{
"compilerOptions": {
"target": "es2020",
"module": "esnext",
"moduleResolution": "bundler",
"lib": ["dom", "dom.iterable", "esnext"],
"jsx": "react-jsx",
"jsxImportSource": "@infinite-canvas/plugin-sdk",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"esModuleInterop": true,
"isolatedModules": true,
"types": []
},
"include": ["src"]
}
+25
View File
@@ -0,0 +1,25 @@
# 插件模板(TypeScript + SDK)
复制本目录即可开始写一个新的画布节点插件。
## 上手
```bash
cp -r plugins/canvas/template plugins/canvas/my-plugin
cd plugins/canvas/my-plugin
# 改 package.json 的 name;改 src/index.tsx 里的 id / name / type
npm install
npm run dev # watch 构建,产物同步到 web/public/plugins/my-plugin.js
npm run typecheck
```
然后启动画布 `web`,在「节点插件」管理器里启用(自动发现)即可看到你的节点。发布时把 `dist/<name>.js` 托管到任意静态地址,用户填 URL 安装。
> 产物名取**目录名**(`my-plugin/` → `my-plugin.js`),所以复制后记得把目录改成你的插件名。
## 你只需要关心
- `src/index.tsx``Content({ ctx })` 的节点 UI 与逻辑;
- `definePlugin({...})` 里的 `id` / `nodes[].type` / `defaultSize` 等元信息。
类型、JSX、宿主 React、构建都由 `@infinite-canvas/plugin-sdk` 提供,写 TSX 全程有补全。`ctx` 的完整能力见 `plugins/canvas/README.md` 与 SDK 的类型定义。
+3
View File
@@ -0,0 +1,3 @@
import { buildPlugin } from "@infinite-canvas/plugin-sdk/build";
await buildPlugin(import.meta.url);
+65
View File
@@ -0,0 +1,65 @@
{
"name": "canvas-plugin-template",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "canvas-plugin-template",
"version": "1.0.0",
"devDependencies": {
"@infinite-canvas/plugin-sdk": "file:../sdk",
"@types/react": "19.1.12",
"typescript": "^5"
}
},
"../sdk": {
"name": "@infinite-canvas/plugin-sdk",
"version": "0.1.0",
"dev": true,
"devDependencies": {
"@types/react": "19.1.12",
"esbuild": "^0.25.0",
"typescript": "^5"
},
"peerDependencies": {
"@types/react": ">=18"
}
},
"node_modules/@infinite-canvas/plugin-sdk": {
"resolved": "../sdk",
"link": true
},
"node_modules/@types/react": {
"version": "19.1.12",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.12.tgz",
"integrity": "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==",
"dev": true,
"license": "MIT",
"dependencies": {
"csstype": "^3.0.2"
}
},
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"dev": true,
"license": "MIT"
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
}
}
}
+17
View File
@@ -0,0 +1,17 @@
{
"name": "canvas-plugin-template",
"version": "1.0.0",
"private": true,
"type": "module",
"description": "Infinite Canvas 插件起步模板(TypeScript + SDK)",
"scripts": {
"build": "node build.mjs",
"dev": "node build.mjs --watch",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"@infinite-canvas/plugin-sdk": "file:../sdk",
"@types/react": "19.1.12",
"typescript": "^5"
}
}
+69
View File
@@ -0,0 +1,69 @@
// 插件模板:复制本目录 → 改 id/name/type → 写你的节点。
// 这是一个演示节点:编辑文本、跟随主题、读取上游节点、用画布指令衍生新节点。
import { definePlugin, useState } from "@infinite-canvas/plugin-sdk";
import type { CanvasNodeContentProps } from "@infinite-canvas/plugin-sdk";
function TemplateContent({ ctx }: CanvasNodeContentProps) {
const [editing, setEditing] = useState(false);
const content = ctx.node.metadata?.content || "";
// 读取上游相连节点的文本内容(演示 ctx.getUpstream)
const upstreamText = ctx
.getUpstream()
.map((node) => node.metadata?.content)
.filter(Boolean)
.join("\n");
// 用画布指令集衍生一个文本节点并连线(演示 ctx.applyOps)
const spawnBelow = () => {
const id = `template-${ctx.node.id}-${ctx.getNodes().length}`;
ctx.applyOps([
{ type: "add_node", id, nodeType: "text", title: "衍生节点", x: ctx.node.position.x, y: ctx.node.position.y + ctx.node.height + 40, metadata: { content, status: "success" } },
{ type: "connect_nodes", fromNodeId: ctx.node.id, toNodeId: id },
]);
};
const btn = { padding: "4px 10px", borderRadius: 8, border: `1px solid ${ctx.theme.node.stroke}`, background: ctx.theme.toolbar.panel, color: ctx.theme.node.text, cursor: "pointer", fontSize: 12 } as const;
return (
// data-canvas-no-zoom + stopPropagation:交互控件避免触发画布拖拽/缩放
<div data-canvas-no-zoom onMouseDown={(e) => e.stopPropagation()} style={{ height: "100%", width: "100%", display: "flex", flexDirection: "column", gap: 8, padding: 12, boxSizing: "border-box", color: ctx.theme.node.text }}>
<div style={{ display: "flex", gap: 6 }}>
<button type="button" style={btn} onClick={() => setEditing((v) => !v)}>
{editing ? "完成" : "编辑"}
</button>
<button type="button" style={btn} onClick={spawnBelow}>
</button>
</div>
{editing ? (
<textarea autoFocus value={content} placeholder="输入内容…" onChange={(e) => ctx.updateMetadata({ content: e.target.value })} onWheel={(e) => e.stopPropagation()} style={{ flex: 1, resize: "none", border: "none", outline: "none", background: "transparent", color: ctx.theme.node.text, fontSize: 14, lineHeight: 1.5 }} />
) : (
<div onWheel={(e) => e.stopPropagation()} style={{ flex: 1, overflow: "auto", whiteSpace: "pre-wrap", fontSize: 14, lineHeight: 1.5 }}>
{content || upstreamText || <span style={{ color: ctx.theme.node.placeholder }}>,</span>}
</div>
)}
</div>
);
}
export default definePlugin({
id: "template", // ← 改成你的唯一 id(kebab-case)
name: "模板节点",
version: "1.0.0",
description: "插件起步模板:改我。",
nodes: [
{
type: "template:node", // ← 建议 "<id>:<name>",全局唯一
title: "模板",
icon: "✨",
description: "起步示例节点",
defaultSize: { width: 280, height: 200 },
defaultMetadata: { content: "" },
minimapColor: "#8b5cf6",
// 作为上游输入被消费时输出文本(可连给生成/其它节点);不需要可删
resource: (node) => ({ kind: "text", text: node.metadata?.content }),
Content: TemplateContent,
},
],
});
+17
View File
@@ -0,0 +1,17 @@
{
"compilerOptions": {
"target": "es2020",
"module": "esnext",
"moduleResolution": "bundler",
"lib": ["dom", "dom.iterable", "esnext"],
"jsx": "react-jsx",
"jsxImportSource": "@infinite-canvas/plugin-sdk",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"esModuleInterop": true,
"isolatedModules": true,
"types": []
},
"include": ["src"]
}
@@ -3,8 +3,10 @@
"version": "0.1.0", "version": "0.1.0",
"description": "Use Codex to read and operate Infinite Canvas through the local Canvas Agent MCP server.", "description": "Use Codex to read and operate Infinite Canvas through the local Canvas Agent MCP server.",
"author": { "author": {
"name": "Infinite Canvas" "name": "basketikun",
"url": "https://canvas.best/"
}, },
"homepage": "https://canvas.best/",
"repository": "https://github.com/basketikun/infinite-canvas", "repository": "https://github.com/basketikun/infinite-canvas",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"keywords": ["infinite-canvas", "canvas", "mcp", "agent"], "keywords": ["infinite-canvas", "canvas", "mcp", "agent"],
@@ -14,9 +16,10 @@
"displayName": "Infinite Canvas", "displayName": "Infinite Canvas",
"shortDescription": "Use Codex to operate the current Infinite Canvas board.", "shortDescription": "Use Codex to operate the current Infinite Canvas board.",
"longDescription": "Connects Codex to the local Canvas Agent MCP server so Codex can inspect the current Infinite Canvas board, create nodes, connect flows, and trigger generation through the web canvas confirmation flow.", "longDescription": "Connects Codex to the local Canvas Agent MCP server so Codex can inspect the current Infinite Canvas board, create nodes, connect flows, and trigger generation through the web canvas confirmation flow.",
"developerName": "Infinite Canvas", "developerName": "basketikun",
"category": "Productivity", "category": "Productivity",
"capabilities": ["Canvas", "MCP", "Creative Workflow"], "capabilities": ["Canvas", "MCP", "Creative Workflow"],
"websiteURL": "https://canvas.best/",
"defaultPrompt": [ "defaultPrompt": [
"打开 Infinite Canvas", "打开 Infinite Canvas",
"读取当前画布并总结节点结构", "读取当前画布并总结节点结构",
+15 -95
View File
@@ -1,111 +1,31 @@
# Infinite Canvas Codex Plugin # Infinite Canvas Codex Plugin
这个插件把 Infinite Canvas 的本地 Canvas Agent MCP 打包给 Codex app 使用,让 Codex 能打开本地画布、读取当前节点、创建内容并触发生成流程 让 Codex 可以打开并操作 Infinite Canvas。
## 安装 ## 安装
### AI 自动安装 macOS / Linux
把下面这段发给 Codex
```text
请从 https://github.com/basketikun/infinite-canvas.git 安装 Infinite Canvas Codex 插件。
请 clone 仓库到 ~/plugins/infinite-canvas,确认 plugins/infinite-canvas/.codex-plugin/plugin.json 存在,
把 plugins/infinite-canvas 加入 personal marketplace,先运行 codex plugin marketplace add ~
再运行 codex plugin add infinite-canvas@personal。
安装后请校验插件,并告诉我是否需要开启一个新对话来加载新技能和 MCP 工具。
```
### 手动安装
推荐把仓库 clone 到 Codex personal marketplace 默认会引用的位置:
```bash ```bash
mkdir -p ~/plugins git clone https://github.com/basketikun/infinite-canvas.git
git clone https://github.com/basketikun/infinite-canvas.git ~/plugins/infinite-canvas cd infinite-canvas
```
确保 `~/.agents/plugins/marketplace.json` 中有 Infinite Canvas 条目,注意 `path` 指向仓库里的插件子目录:
```json
{
"name": "personal",
"interface": {
"displayName": "Personal"
},
"plugins": [
{
"name": "infinite-canvas",
"source": {
"source": "local",
"path": "./plugins/infinite-canvas/plugins/infinite-canvas"
},
"policy": {
"installation": "AVAILABLE",
"authentication": "ON_INSTALL"
},
"category": "Productivity"
}
]
}
```
然后注册 personal marketplace 并安装插件:
```bash
codex plugin marketplace add ~
codex plugin add infinite-canvas@personal
```
安装后建议开启一个新的 Codex 对话,让新的 skill 和 MCP 工具完整加载。
### 本仓库开发调试
如果你就在 Infinite Canvas 仓库中调试插件,可以直接添加仓库自带 marketplace。建议使用仓库绝对路径,避免 Codex 从其他工作目录解析失败:
```bash
cd /path/to/infinite-canvas
codex plugin marketplace add "$(pwd)" codex plugin marketplace add "$(pwd)"
codex plugin add infinite-canvas@infinite-canvas-local codex plugin add infinite-canvas@infinite-canvas-local
``` ```
## 使用 Windows PowerShell
1. 新建 Codex 线程后说“打开 Infinite Canvas”。 ```powershell
2. 插件会确认当前仓库的本地画布服务是否已运行;端口被占用时会检查进程归属,不会把其他项目的 `3000` 当作 Infinite Canvas。 git clone https://github.com/basketikun/infinite-canvas.git
3. 确认或启动后,插件会直接打开新建画布 URL,并自动尝试连接本地 Agent。 cd infinite-canvas
4. 画布打开后,让 Codex 读取或操作当前画布。 codex plugin marketplace add "$PWD"
codex plugin add infinite-canvas@infinite-canvas-local
```
常用提示: Windows CMD 将 `$PWD` 替换为 `%cd%`
安装后新建一个 Codex 任务,然后输入:
```text ```text
打开 Infinite Canvas 帮我打开并连接到 Infinite Canvas
读取当前画布并总结节点结构
根据选中节点创建一组生图提示词
``` ```
## 工作机制
插件默认通过以下命令启动 MCP,并会在 MCP 启动时自动尝试拉起本地 Agent:
```bash
npx -y @basketikun/canvas-agent mcp
```
## 手动排查
优先本地启动画布:
```bash
cd web
bun install
bun run dev
```
然后启动本地 Agent。端口不是 `3000` 时,把 `CANVAS_URL` 换成真实本地画布地址:
```bash
CANVAS_URL=http://localhost:3000 npx -y @basketikun/canvas-agent
```
手动排查时先从 Agent 输出或 `http://127.0.0.1:17371/config` 读取本地地址和 token,然后直接打开 `<画布网页地址>/canvas?mode=new&agentUrl=<Local URL>&agentToken=<Connect token>`。不要通过页面点击来新建画布;`mode=new` 会让网页自动创建具体画布并连接本地 Agent。
@@ -1,36 +1,58 @@
--- ---
name: open-canvas name: open-canvas
description: 打开 Infinite Canvas 网页画布并自动连接本地 Canvas Agent。用户要求打开、启动、进入使用 Infinite Canvas 画布时使用。 description: 打开 Infinite Canvas 在线或本地画布并自动连接本地 Canvas Agent。用户要求打开、启动、进入使用 Infinite Canvas 画布时使用。
--- ---
# Open Infinite Canvas # Open Infinite Canvas
当用户要求打开、启动、进入或使用 Infinite Canvas 时,不要把 URL 交给用户手动复制,不要通过浏览器点击“新建画布”。优先快速拉起本地画布和本地 Canvas Agent,然后直接打开带 `mode``agentUrl``agentToken` 的 URL,让网页自动创建或选择画布并连接 Agent 默认打开在线版。只有用户明确要求使用本地项目时,才启动本地前端
## 默认打开方式 ## 在线版
- 新建画布:`<画布网页地址>/canvas?mode=new&agentUrl=<Local URL>&agentToken=<Connect token>` 1. 启动本地 Canvas Agent 并保持运行:
- 最近画布:`<画布网页地址>/canvas?mode=recent&agentUrl=<Local URL>&agentToken=<Connect token>`
- 自己选择:`<画布网页地址>/canvas?mode=choose&agentUrl=<Local URL>&agentToken=<Connect token>`
默认打开新建本地画布;只有用户明确要求线上地址、最近画布或自己选择时,才改用对应模式。 ```bash
npx -y @basketikun/canvas-agent
```
## 工作流 2. 从启动输出取得 `Local URL``Connect token`
1. 如果当前仓库是 Infinite Canvas 项目,优先使用当前仓库的 `web/` 前端。 3. 在 Codex 右侧浏览器打开:
2. 先检查本地端口归属:如果 `3000``3001` 等端口已被占用,必须用 `lsof`/`ps` 或服务输出确认监听进程的工作目录属于当前仓库的 `web/`,不能只因为端口存在就当成本地画布。
3. 如果已有当前仓库的 Next dev 服务,复用它并记录真实画布地址,例如 `http://localhost:3001`
4. 如果没有当前仓库的服务,启动本地画布开发服务,默认在 `web/` 下运行 `bun run dev`;若默认端口被其他项目占用,改用空闲端口启动,例如 `bunx next dev --webpack -H 0.0.0.0 -p <空闲端口>`。不要执行构建或测试。
5. 启动本地 Canvas Agent,必须带上第 3/4 步得到的真实画布地址:`CANVAS_URL=<真实画布地址> npx -y @basketikun/canvas-agent`。如果 Agent 已经在运行,则读取 `~/.infinite-canvas/canvas-agent.json``/config` 获取 `Local URL` 和 token。
6. 读取 Agent 输出或配置中的 `Local URL``Connect token`,不要让用户手动复制。
7. 不走本地 Agent 的 `/open` 跳转;直接构造并打开最终 URL`<真实画布地址>/canvas?mode=new&agentUrl=<Local URL>&agentToken=<Connect token>`
8. 画布网页会自动新建具体画布、打开本机 Agent 面板并连接本地 Agent;不要用浏览器点击新建画布。
9. 打开后再使用 `canvas_get_state` 检查画布是否已经连接;如果尚未连接,等待片刻再检查,不要改用线上站点,除非用户明确要求。
## 用户只安装插件时 ```text
https://canvas.best/canvas?mode=new&agentUrl=<Local URL>&agentToken=<Connect token>
```
- 如果当前工作区不是 Infinite Canvas 源码仓库,优先提示用户先打开或启动 Infinite Canvas 网页,再连接本地 Agent。 ## 本地版
- 可以使用线上画布地址或用户给出的本地地址作为 `<画布网页地址>`,但仍要通过本地 Canvas Agent 获取 token 后再打开最终 URL。
- 不要假设用户已经安装本仓库依赖;插件的 MCP 会通过 `npx -y @basketikun/canvas-agent mcp` 使用已发布的 Canvas Agent。
不要要求用户手动填写 URL、token 或复制 JSON。 1. 在 Infinite Canvas 项目中启动前端,并使用 Vite 输出的 `Local` 地址:
```bash
cd web
bun install
bun run dev
```
2. 启动本地 Canvas Agent
```bash
npx -y @basketikun/canvas-agent
```
3. 从启动输出取得 `Local URL``Connect token`,在 Codex 右侧浏览器打开:
```text
<Vite Local 地址>/canvas?mode=new&agentUrl=<Local URL>&agentToken=<Connect token>
```
## MCP 与连接地址
插件在新的 Codex 任务中加载时会自动启动 `npx -y @basketikun/canvas-agent mcp`。这个 MCP 进程负责提供画布工具,不提供网页连接服务;
上面启动的普通 Canvas Agent 负责提供 `Local URL``Connect token`。两个进程读取同一份本地配置,因此不需要用户手动填写地址或 token。
## 打开模式
用户没有明确指定打开方式时,始终使用 `mode=new` 新建画布。只有用户明确要求时才替换为:
- 最近画布:`mode=recent`
- 自己选择:`mode=choose`
+567 -33
View File
@@ -7,6 +7,7 @@
"dependencies": { "dependencies": {
"@ant-design/icons": "^6.1.1", "@ant-design/icons": "^6.1.1",
"@ant-design/pro-components": "3.0.0-beta.3", "@ant-design/pro-components": "3.0.0-beta.3",
"@codemirror/lang-javascript": "^6.2.5",
"@codemirror/lang-json": "^6.0.2", "@codemirror/lang-json": "^6.0.2",
"@tanstack/react-query": "^5.100.9", "@tanstack/react-query": "^5.100.9",
"@uiw/react-codemirror": "^4.25.9", "@uiw/react-codemirror": "^4.25.9",
@@ -28,6 +29,7 @@
"react-router": "^7.12.0", "react-router": "^7.12.0",
"react-router-dom": "^7.12.0", "react-router-dom": "^7.12.0",
"shadcn": "^4.7.0", "shadcn": "^4.7.0",
"streamdown": "^2.5.0",
"tailwind-merge": "^3.6.0", "tailwind-merge": "^3.6.0",
"tailwindcss": "^4", "tailwindcss": "^4",
"tw-animate-css": "^1.4.0", "tw-animate-css": "^1.4.0",
@@ -65,6 +67,8 @@
"@ant-design/react-slick": ["@ant-design/react-slick@2.0.0", "https://registry.npmmirror.com/@ant-design/react-slick/-/react-slick-2.0.0.tgz", { "dependencies": { "@babel/runtime": "^7.28.4", "clsx": "^2.1.1", "json2mq": "^0.2.0", "throttle-debounce": "^5.0.0" }, "peerDependencies": { "react": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-HMS9sRoEmZey8LsE/Yo6+klhlzU12PisjrVcydW3So7RdklyEd2qehyU6a7Yp+OYN72mgsYs3NFCyP2lCPFVqg=="], "@ant-design/react-slick": ["@ant-design/react-slick@2.0.0", "https://registry.npmmirror.com/@ant-design/react-slick/-/react-slick-2.0.0.tgz", { "dependencies": { "@babel/runtime": "^7.28.4", "clsx": "^2.1.1", "json2mq": "^0.2.0", "throttle-debounce": "^5.0.0" }, "peerDependencies": { "react": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-HMS9sRoEmZey8LsE/Yo6+klhlzU12PisjrVcydW3So7RdklyEd2qehyU6a7Yp+OYN72mgsYs3NFCyP2lCPFVqg=="],
"@antfu/install-pkg": ["@antfu/install-pkg@1.1.0", "", { "dependencies": { "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" } }, "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ=="],
"@babel/code-frame": ["@babel/code-frame@7.29.0", "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.29.0.tgz", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="], "@babel/code-frame": ["@babel/code-frame@7.29.0", "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.29.0.tgz", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],
"@babel/compat-data": ["@babel/compat-data@7.29.3", "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.29.3.tgz", {}, "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg=="], "@babel/compat-data": ["@babel/compat-data@7.29.3", "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.29.3.tgz", {}, "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg=="],
@@ -127,10 +131,16 @@
"@babel/types": ["@babel/types@7.29.0", "https://registry.npmmirror.com/@babel/types/-/types-7.29.0.tgz", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], "@babel/types": ["@babel/types@7.29.0", "https://registry.npmmirror.com/@babel/types/-/types-7.29.0.tgz", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
"@braintree/sanitize-url": ["@braintree/sanitize-url@7.1.2", "", {}, "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA=="],
"@chevrotain/types": ["@chevrotain/types@11.1.2", "", {}, "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw=="],
"@codemirror/autocomplete": ["@codemirror/autocomplete@6.20.2", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0" } }, "sha512-G5FPkgIiLjOgZMjqVjvuKQ1rGPtHogLldJr33eFJdVLtmwY+giGrlv/ewljLz6b9BSQLkjxuwBc6g6omDM+YxQ=="], "@codemirror/autocomplete": ["@codemirror/autocomplete@6.20.2", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0" } }, "sha512-G5FPkgIiLjOgZMjqVjvuKQ1rGPtHogLldJr33eFJdVLtmwY+giGrlv/ewljLz6b9BSQLkjxuwBc6g6omDM+YxQ=="],
"@codemirror/commands": ["@codemirror/commands@6.10.3", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.6.0", "@codemirror/view": "^6.27.0", "@lezer/common": "^1.1.0" } }, "sha512-JFRiqhKu+bvSkDLI+rUhJwSxQxYb759W5GBezE8Uc8mHLqC9aV/9aTC7yJSqCtB3F00pylrLCwnyS91Ap5ej4Q=="], "@codemirror/commands": ["@codemirror/commands@6.10.3", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.6.0", "@codemirror/view": "^6.27.0", "@lezer/common": "^1.1.0" } }, "sha512-JFRiqhKu+bvSkDLI+rUhJwSxQxYb759W5GBezE8Uc8mHLqC9aV/9aTC7yJSqCtB3F00pylrLCwnyS91Ap5ej4Q=="],
"@codemirror/lang-javascript": ["@codemirror/lang-javascript@6.2.5", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/language": "^6.6.0", "@codemirror/lint": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0", "@lezer/javascript": "^1.0.0" } }, "sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A=="],
"@codemirror/lang-json": ["@codemirror/lang-json@6.0.2", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@lezer/json": "^1.0.0" } }, "sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ=="], "@codemirror/lang-json": ["@codemirror/lang-json@6.0.2", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@lezer/json": "^1.0.0" } }, "sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ=="],
"@codemirror/language": ["@codemirror/language@6.12.3", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.23.0", "@lezer/common": "^1.5.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0", "style-mod": "^4.0.0" } }, "sha512-QwCZW6Tt1siP37Jet9Tb02Zs81TQt6qQrZR2H+eGMcFsL1zMrk2/b9CLC7/9ieP1fjIUMgviLWMmgiHoJrj+ZA=="], "@codemirror/language": ["@codemirror/language@6.12.3", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.23.0", "@lezer/common": "^1.5.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0", "style-mod": "^4.0.0" } }, "sha512-QwCZW6Tt1siP37Jet9Tb02Zs81TQt6qQrZR2H+eGMcFsL1zMrk2/b9CLC7/9ieP1fjIUMgviLWMmgiHoJrj+ZA=="],
@@ -243,6 +253,10 @@
"@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=="], "@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=="],
"@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="],
"@iconify/utils": ["@iconify/utils@3.1.4", "", { "dependencies": { "@antfu/install-pkg": "^1.1.0", "@iconify/types": "^2.0.0", "import-meta-resolve": "^4.2.0" } }, "sha512-b1S7B1k9ohZ+iNTi2ATxbRYG9fTrJmUT0rc46bvVnNxqNRGW7dyo/vRREwyniI5IRN2RSJHDcm+s3BjWrSAjHw=="],
"@inquirer/ansi": ["@inquirer/ansi@2.0.5", "https://registry.npmmirror.com/@inquirer/ansi/-/ansi-2.0.5.tgz", {}, "sha512-doc2sWgJpbFQ64UflSVd17ibMGDuxO1yKgOgLMwavzESnXjFWJqUeG8saYosqKpHp4kWiM5x1nXvEjbpx90gzw=="], "@inquirer/ansi": ["@inquirer/ansi@2.0.5", "https://registry.npmmirror.com/@inquirer/ansi/-/ansi-2.0.5.tgz", {}, "sha512-doc2sWgJpbFQ64UflSVd17ibMGDuxO1yKgOgLMwavzESnXjFWJqUeG8saYosqKpHp4kWiM5x1nXvEjbpx90gzw=="],
"@inquirer/confirm": ["@inquirer/confirm@6.0.13", "https://registry.npmmirror.com/@inquirer/confirm/-/confirm-6.0.13.tgz", { "dependencies": { "@inquirer/core": "^11.1.10", "@inquirer/type": "^4.0.5" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-wkGPC7yJ5WJk1DJ5SX7fzk+gfj4BM8cf5dDDi71B/551xHrdsZVRJOC0WyikXd0pEsb/9cLniuE4atbsMqmFkw=="], "@inquirer/confirm": ["@inquirer/confirm@6.0.13", "https://registry.npmmirror.com/@inquirer/confirm/-/confirm-6.0.13.tgz", { "dependencies": { "@inquirer/core": "^11.1.10", "@inquirer/type": "^4.0.5" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-wkGPC7yJ5WJk1DJ5SX7fzk+gfj4BM8cf5dDDi71B/551xHrdsZVRJOC0WyikXd0pEsb/9cLniuE4atbsMqmFkw=="],
@@ -267,12 +281,16 @@
"@lezer/highlight": ["@lezer/highlight@1.2.3", "", { "dependencies": { "@lezer/common": "^1.3.0" } }, "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g=="], "@lezer/highlight": ["@lezer/highlight@1.2.3", "", { "dependencies": { "@lezer/common": "^1.3.0" } }, "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g=="],
"@lezer/javascript": ["@lezer/javascript@1.5.4", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.1.3", "@lezer/lr": "^1.3.0" } }, "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA=="],
"@lezer/json": ["@lezer/json@1.0.3", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ=="], "@lezer/json": ["@lezer/json@1.0.3", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ=="],
"@lezer/lr": ["@lezer/lr@1.4.10", "", { "dependencies": { "@lezer/common": "^1.0.0" } }, "sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A=="], "@lezer/lr": ["@lezer/lr@1.4.10", "", { "dependencies": { "@lezer/common": "^1.0.0" } }, "sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A=="],
"@marijn/find-cluster-break": ["@marijn/find-cluster-break@1.0.2", "", {}, "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g=="], "@marijn/find-cluster-break": ["@marijn/find-cluster-break@1.0.2", "", {}, "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g=="],
"@mermaid-js/parser": ["@mermaid-js/parser@1.2.0", "", { "dependencies": { "@chevrotain/types": "~11.1.2" } }, "sha512-oYPyv8A4As1yH5Bx+04iQEQxXuIQDe0GKCNSRgao6z8AM9jixXIfP0vsppRLvGf+nKIOb9/LdpWA4YuJiVvESA=="],
"@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=="], "@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=="],
"@mswjs/interceptors": ["@mswjs/interceptors@0.41.9", "https://registry.npmmirror.com/@mswjs/interceptors/-/interceptors-0.41.9.tgz", { "dependencies": { "@open-draft/deferred-promise": "^2.2.0", "@open-draft/logger": "^0.3.0", "@open-draft/until": "^2.0.0", "is-node-process": "^1.2.0", "outvariant": "^1.4.3", "strict-event-emitter": "^0.5.1" } }, "sha512-VVPPgHyQ6ShqnrmDWuxjmUIsO9gWyOZFmuOfLd9LfBGQJwZfy0gvv9pbHSJuoFNIYC7ZDX9aoFwowjcdSC4E8w=="], "@mswjs/interceptors": ["@mswjs/interceptors@0.41.9", "https://registry.npmmirror.com/@mswjs/interceptors/-/interceptors-0.41.9.tgz", { "dependencies": { "@open-draft/deferred-promise": "^2.2.0", "@open-draft/logger": "^0.3.0", "@open-draft/until": "^2.0.0", "is-node-process": "^1.2.0", "outvariant": "^1.4.3", "strict-event-emitter": "^0.5.1" } }, "sha512-VVPPgHyQ6ShqnrmDWuxjmUIsO9gWyOZFmuOfLd9LfBGQJwZfy0gvv9pbHSJuoFNIYC7ZDX9aoFwowjcdSC4E8w=="],
@@ -599,15 +617,81 @@
"@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
"@types/d3": ["@types/d3@7.4.3", "", { "dependencies": { "@types/d3-array": "*", "@types/d3-axis": "*", "@types/d3-brush": "*", "@types/d3-chord": "*", "@types/d3-color": "*", "@types/d3-contour": "*", "@types/d3-delaunay": "*", "@types/d3-dispatch": "*", "@types/d3-drag": "*", "@types/d3-dsv": "*", "@types/d3-ease": "*", "@types/d3-fetch": "*", "@types/d3-force": "*", "@types/d3-format": "*", "@types/d3-geo": "*", "@types/d3-hierarchy": "*", "@types/d3-interpolate": "*", "@types/d3-path": "*", "@types/d3-polygon": "*", "@types/d3-quadtree": "*", "@types/d3-random": "*", "@types/d3-scale": "*", "@types/d3-scale-chromatic": "*", "@types/d3-selection": "*", "@types/d3-shape": "*", "@types/d3-time": "*", "@types/d3-time-format": "*", "@types/d3-timer": "*", "@types/d3-transition": "*", "@types/d3-zoom": "*" } }, "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww=="],
"@types/d3-array": ["@types/d3-array@3.2.2", "", {}, "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="],
"@types/d3-axis": ["@types/d3-axis@3.0.6", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw=="],
"@types/d3-brush": ["@types/d3-brush@3.0.6", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A=="],
"@types/d3-chord": ["@types/d3-chord@3.0.6", "", {}, "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg=="],
"@types/d3-color": ["@types/d3-color@3.1.3", "", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="],
"@types/d3-contour": ["@types/d3-contour@3.0.6", "", { "dependencies": { "@types/d3-array": "*", "@types/geojson": "*" } }, "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg=="],
"@types/d3-delaunay": ["@types/d3-delaunay@6.0.4", "", {}, "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw=="],
"@types/d3-dispatch": ["@types/d3-dispatch@3.0.7", "", {}, "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA=="],
"@types/d3-drag": ["@types/d3-drag@3.0.7", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ=="],
"@types/d3-dsv": ["@types/d3-dsv@3.0.7", "", {}, "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g=="],
"@types/d3-ease": ["@types/d3-ease@3.0.2", "", {}, "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA=="],
"@types/d3-fetch": ["@types/d3-fetch@3.0.7", "", { "dependencies": { "@types/d3-dsv": "*" } }, "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA=="],
"@types/d3-force": ["@types/d3-force@3.0.10", "", {}, "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw=="],
"@types/d3-format": ["@types/d3-format@3.0.4", "", {}, "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g=="],
"@types/d3-geo": ["@types/d3-geo@3.1.0", "", { "dependencies": { "@types/geojson": "*" } }, "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ=="],
"@types/d3-hierarchy": ["@types/d3-hierarchy@3.1.7", "", {}, "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg=="],
"@types/d3-interpolate": ["@types/d3-interpolate@3.0.4", "", { "dependencies": { "@types/d3-color": "*" } }, "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA=="],
"@types/d3-path": ["@types/d3-path@3.1.1", "", {}, "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg=="],
"@types/d3-polygon": ["@types/d3-polygon@3.0.2", "", {}, "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA=="],
"@types/d3-quadtree": ["@types/d3-quadtree@3.0.6", "", {}, "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg=="],
"@types/d3-random": ["@types/d3-random@3.0.4", "", {}, "sha512-UHYId5WTCx4L4YNel7NU00XUXXgvgpgZOvp10PuvsQENjMDXhh2RyFc0KBjO7B45ne4Ha1yVH7ii0vnzKkuzWA=="],
"@types/d3-scale": ["@types/d3-scale@4.0.9", "", { "dependencies": { "@types/d3-time": "*" } }, "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw=="],
"@types/d3-scale-chromatic": ["@types/d3-scale-chromatic@3.1.0", "", {}, "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ=="],
"@types/d3-selection": ["@types/d3-selection@3.0.11", "", {}, "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w=="],
"@types/d3-shape": ["@types/d3-shape@3.1.8", "", { "dependencies": { "@types/d3-path": "*" } }, "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w=="],
"@types/d3-time": ["@types/d3-time@3.0.4", "", {}, "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g=="],
"@types/d3-time-format": ["@types/d3-time-format@4.0.3", "", {}, "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg=="],
"@types/d3-timer": ["@types/d3-timer@3.0.2", "", {}, "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="],
"@types/d3-transition": ["@types/d3-transition@3.0.9", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg=="],
"@types/d3-zoom": ["@types/d3-zoom@3.0.8", "", { "dependencies": { "@types/d3-interpolate": "*", "@types/d3-selection": "*" } }, "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw=="],
"@types/debug": ["@types/debug@4.1.13", "https://registry.npmmirror.com/@types/debug/-/debug-4.1.13.tgz", { "dependencies": { "@types/ms": "*" } }, "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw=="], "@types/debug": ["@types/debug@4.1.13", "https://registry.npmmirror.com/@types/debug/-/debug-4.1.13.tgz", { "dependencies": { "@types/ms": "*" } }, "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw=="],
"@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="],
"@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="],
"@types/file-saver": ["@types/file-saver@2.0.7", "", {}, "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A=="], "@types/file-saver": ["@types/file-saver@2.0.7", "", {}, "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A=="],
"@types/geojson": ["@types/geojson@7946.0.16", "", {}, "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg=="],
"@types/hast": ["@types/hast@2.3.10", "https://registry.npmmirror.com/@types/hast/-/hast-2.3.10.tgz", { "dependencies": { "@types/unist": "^2" } }, "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw=="], "@types/hast": ["@types/hast@2.3.10", "https://registry.npmmirror.com/@types/hast/-/hast-2.3.10.tgz", { "dependencies": { "@types/unist": "^2" } }, "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw=="],
"@types/mdast": ["@types/mdast@3.0.15", "https://registry.npmmirror.com/@types/mdast/-/mdast-3.0.15.tgz", { "dependencies": { "@types/unist": "^2" } }, "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ=="], "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="],
"@types/ms": ["@types/ms@2.1.0", "https://registry.npmmirror.com/@types/ms/-/ms-2.1.0.tgz", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], "@types/ms": ["@types/ms@2.1.0", "https://registry.npmmirror.com/@types/ms/-/ms-2.1.0.tgz", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
@@ -625,6 +709,8 @@
"@types/statuses": ["@types/statuses@2.0.6", "https://registry.npmmirror.com/@types/statuses/-/statuses-2.0.6.tgz", {}, "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA=="], "@types/statuses": ["@types/statuses@2.0.6", "https://registry.npmmirror.com/@types/statuses/-/statuses-2.0.6.tgz", {}, "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA=="],
"@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="],
"@types/unist": ["@types/unist@2.0.11", "https://registry.npmmirror.com/@types/unist/-/unist-2.0.11.tgz", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], "@types/unist": ["@types/unist@2.0.11", "https://registry.npmmirror.com/@types/unist/-/unist-2.0.11.tgz", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
"@types/validate-npm-package-name": ["@types/validate-npm-package-name@4.0.2", "https://registry.npmmirror.com/@types/validate-npm-package-name/-/validate-npm-package-name-4.0.2.tgz", {}, "sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw=="], "@types/validate-npm-package-name": ["@types/validate-npm-package-name@4.0.2", "https://registry.npmmirror.com/@types/validate-npm-package-name/-/validate-npm-package-name-4.0.2.tgz", {}, "sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw=="],
@@ -637,6 +723,10 @@
"@umijs/use-params": ["@umijs/use-params@1.0.9", "https://registry.npmmirror.com/@umijs/use-params/-/use-params-1.0.9.tgz", { "peerDependencies": { "react": "*" } }, "sha512-QlN0RJSBVQBwLRNxbxjQ5qzqYIGn+K7USppMoIOVlf7fxXHsnQZ2bEsa6Pm74bt6DVQxpUE8HqvdStn6Y9FV1w=="], "@umijs/use-params": ["@umijs/use-params@1.0.9", "https://registry.npmmirror.com/@umijs/use-params/-/use-params-1.0.9.tgz", { "peerDependencies": { "react": "*" } }, "sha512-QlN0RJSBVQBwLRNxbxjQ5qzqYIGn+K7USppMoIOVlf7fxXHsnQZ2bEsa6Pm74bt6DVQxpUE8HqvdStn6Y9FV1w=="],
"@ungap/structured-clone": ["@ungap/structured-clone@1.3.3", "", {}, "sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg=="],
"@upsetjs/venn.js": ["@upsetjs/venn.js@2.0.0", "", { "optionalDependencies": { "d3-selection": "^3.0.0", "d3-transition": "^3.0.1" } }, "sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw=="],
"@vitejs/plugin-react": ["@vitejs/plugin-react@5.2.0", "", { "dependencies": { "@babel/core": "^7.29.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-rc.3", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw=="], "@vitejs/plugin-react": ["@vitejs/plugin-react@5.2.0", "", { "dependencies": { "@babel/core": "^7.29.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-rc.3", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw=="],
"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=="], "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=="],
@@ -691,10 +781,14 @@
"caniuse-lite": ["caniuse-lite@1.0.30001791", "", {}, "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ=="], "caniuse-lite": ["caniuse-lite@1.0.30001791", "", {}, "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ=="],
"ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
"chalk": ["chalk@5.6.2", "https://registry.npmmirror.com/chalk/-/chalk-5.6.2.tgz", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], "chalk": ["chalk@5.6.2", "https://registry.npmmirror.com/chalk/-/chalk-5.6.2.tgz", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
"character-entities": ["character-entities@1.2.4", "https://registry.npmmirror.com/character-entities/-/character-entities-1.2.4.tgz", {}, "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw=="], "character-entities": ["character-entities@1.2.4", "https://registry.npmmirror.com/character-entities/-/character-entities-1.2.4.tgz", {}, "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw=="],
"character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="],
"character-entities-legacy": ["character-entities-legacy@1.1.4", "https://registry.npmmirror.com/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", {}, "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA=="], "character-entities-legacy": ["character-entities-legacy@1.1.4", "https://registry.npmmirror.com/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", {}, "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA=="],
"character-reference-invalid": ["character-reference-invalid@1.1.4", "https://registry.npmmirror.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", {}, "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg=="], "character-reference-invalid": ["character-reference-invalid@1.1.4", "https://registry.npmmirror.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", {}, "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg=="],
@@ -743,6 +837,8 @@
"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=="], "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=="],
"cose-base": ["cose-base@1.0.3", "", { "dependencies": { "layout-base": "^1.0.0" } }, "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg=="],
"cosmiconfig": ["cosmiconfig@9.0.1", "https://registry.npmmirror.com/cosmiconfig/-/cosmiconfig-9.0.1.tgz", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ=="], "cosmiconfig": ["cosmiconfig@9.0.1", "https://registry.npmmirror.com/cosmiconfig/-/cosmiconfig-9.0.1.tgz", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ=="],
"crelt": ["crelt@1.0.6", "", {}, "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="], "crelt": ["crelt@1.0.6", "", {}, "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="],
@@ -753,6 +849,78 @@
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
"cytoscape": ["cytoscape@3.34.0", "", {}, "sha512-62rNSrioXw93uliKFBwjukeQyeWwH2PqDrTac31r2P6464u3AUvTk0xS4LVvT251g7IgkFunrI48ZEZGjywSOg=="],
"cytoscape-cose-bilkent": ["cytoscape-cose-bilkent@4.1.0", "", { "dependencies": { "cose-base": "^1.0.0" }, "peerDependencies": { "cytoscape": "^3.2.0" } }, "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ=="],
"cytoscape-fcose": ["cytoscape-fcose@2.2.0", "", { "dependencies": { "cose-base": "^2.2.0" }, "peerDependencies": { "cytoscape": "^3.2.0" } }, "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ=="],
"d3": ["d3@7.9.0", "", { "dependencies": { "d3-array": "3", "d3-axis": "3", "d3-brush": "3", "d3-chord": "3", "d3-color": "3", "d3-contour": "4", "d3-delaunay": "6", "d3-dispatch": "3", "d3-drag": "3", "d3-dsv": "3", "d3-ease": "3", "d3-fetch": "3", "d3-force": "3", "d3-format": "3", "d3-geo": "3", "d3-hierarchy": "3", "d3-interpolate": "3", "d3-path": "3", "d3-polygon": "3", "d3-quadtree": "3", "d3-random": "3", "d3-scale": "4", "d3-scale-chromatic": "3", "d3-selection": "3", "d3-shape": "3", "d3-time": "3", "d3-time-format": "4", "d3-timer": "3", "d3-transition": "3", "d3-zoom": "3" } }, "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA=="],
"d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="],
"d3-axis": ["d3-axis@3.0.0", "", {}, "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw=="],
"d3-brush": ["d3-brush@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", "d3-interpolate": "1 - 3", "d3-selection": "3", "d3-transition": "3" } }, "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ=="],
"d3-chord": ["d3-chord@3.0.1", "", { "dependencies": { "d3-path": "1 - 3" } }, "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g=="],
"d3-color": ["d3-color@3.1.0", "", {}, "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="],
"d3-contour": ["d3-contour@4.0.2", "", { "dependencies": { "d3-array": "^3.2.0" } }, "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA=="],
"d3-delaunay": ["d3-delaunay@6.0.4", "", { "dependencies": { "delaunator": "5" } }, "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A=="],
"d3-dispatch": ["d3-dispatch@3.0.1", "", {}, "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg=="],
"d3-drag": ["d3-drag@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-selection": "3" } }, "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg=="],
"d3-dsv": ["d3-dsv@3.0.1", "", { "dependencies": { "commander": "7", "iconv-lite": "0.6", "rw": "1" }, "bin": { "csv2json": "bin/dsv2json.js", "csv2tsv": "bin/dsv2dsv.js", "dsv2dsv": "bin/dsv2dsv.js", "dsv2json": "bin/dsv2json.js", "json2csv": "bin/json2dsv.js", "json2dsv": "bin/json2dsv.js", "json2tsv": "bin/json2dsv.js", "tsv2csv": "bin/dsv2dsv.js", "tsv2json": "bin/dsv2json.js" } }, "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q=="],
"d3-ease": ["d3-ease@3.0.1", "", {}, "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w=="],
"d3-fetch": ["d3-fetch@3.0.1", "", { "dependencies": { "d3-dsv": "1 - 3" } }, "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw=="],
"d3-force": ["d3-force@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-quadtree": "1 - 3", "d3-timer": "1 - 3" } }, "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg=="],
"d3-format": ["d3-format@3.1.2", "", {}, "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg=="],
"d3-geo": ["d3-geo@3.1.1", "", { "dependencies": { "d3-array": "2.5.0 - 3" } }, "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q=="],
"d3-hierarchy": ["d3-hierarchy@3.1.2", "", {}, "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA=="],
"d3-interpolate": ["d3-interpolate@3.0.1", "", { "dependencies": { "d3-color": "1 - 3" } }, "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g=="],
"d3-path": ["d3-path@3.1.0", "", {}, "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ=="],
"d3-polygon": ["d3-polygon@3.0.1", "", {}, "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg=="],
"d3-quadtree": ["d3-quadtree@3.0.1", "", {}, "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw=="],
"d3-random": ["d3-random@3.0.1", "", {}, "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ=="],
"d3-sankey": ["d3-sankey@0.12.3", "", { "dependencies": { "d3-array": "1 - 2", "d3-shape": "^1.2.0" } }, "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ=="],
"d3-scale": ["d3-scale@4.0.2", "", { "dependencies": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", "d3-interpolate": "1.2.0 - 3", "d3-time": "2.1.1 - 3", "d3-time-format": "2 - 4" } }, "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ=="],
"d3-scale-chromatic": ["d3-scale-chromatic@3.1.0", "", { "dependencies": { "d3-color": "1 - 3", "d3-interpolate": "1 - 3" } }, "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ=="],
"d3-selection": ["d3-selection@3.0.0", "", {}, "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ=="],
"d3-shape": ["d3-shape@3.2.0", "", { "dependencies": { "d3-path": "^3.1.0" } }, "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA=="],
"d3-time": ["d3-time@3.1.0", "", { "dependencies": { "d3-array": "2 - 3" } }, "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q=="],
"d3-time-format": ["d3-time-format@4.1.0", "", { "dependencies": { "d3-time": "1 - 3" } }, "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg=="],
"d3-timer": ["d3-timer@3.0.1", "", {}, "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA=="],
"d3-transition": ["d3-transition@3.0.1", "", { "dependencies": { "d3-color": "1 - 3", "d3-dispatch": "1 - 3", "d3-ease": "1 - 3", "d3-interpolate": "1 - 3", "d3-timer": "1 - 3" }, "peerDependencies": { "d3-selection": "2 - 3" } }, "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w=="],
"d3-zoom": ["d3-zoom@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", "d3-interpolate": "1 - 3", "d3-selection": "2 - 3", "d3-transition": "2 - 3" } }, "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw=="],
"dagre-d3-es": ["dagre-d3-es@7.0.14", "", { "dependencies": { "d3": "^7.9.0", "lodash-es": "^4.17.21" } }, "sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg=="],
"data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "https://registry.npmmirror.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="], "data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "https://registry.npmmirror.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="],
"dayjs": ["dayjs@1.11.20", "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.20.tgz", {}, "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ=="], "dayjs": ["dayjs@1.11.20", "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.20.tgz", {}, "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ=="],
@@ -771,6 +939,8 @@
"define-lazy-prop": ["define-lazy-prop@3.0.0", "https://registry.npmmirror.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="], "define-lazy-prop": ["define-lazy-prop@3.0.0", "https://registry.npmmirror.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="],
"delaunator": ["delaunator@5.1.0", "", { "dependencies": { "robust-predicates": "^3.0.2" } }, "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ=="],
"delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
"depd": ["depd@2.0.0", "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], "depd": ["depd@2.0.0", "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
@@ -781,8 +951,12 @@
"detect-node-es": ["detect-node-es@1.1.0", "https://registry.npmmirror.com/detect-node-es/-/detect-node-es-1.1.0.tgz", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="], "detect-node-es": ["detect-node-es@1.1.0", "https://registry.npmmirror.com/detect-node-es/-/detect-node-es-1.1.0.tgz", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="],
"devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="],
"diff": ["diff@8.0.4", "https://registry.npmmirror.com/diff/-/diff-8.0.4.tgz", {}, "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw=="], "diff": ["diff@8.0.4", "https://registry.npmmirror.com/diff/-/diff-8.0.4.tgz", {}, "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw=="],
"dompurify": ["dompurify@3.4.12", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-zQvGet8Z2sWbQhCmfFz/T5QWH2oBmjnqK3qvOjaqaNLrLEF912WamU+ohnTp0TCep/MFVHpdJuCZEdFOdTnEFg=="],
"dotenv": ["dotenv@17.4.2", "https://registry.npmmirror.com/dotenv/-/dotenv-17.4.2.tgz", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="], "dotenv": ["dotenv@17.4.2", "https://registry.npmmirror.com/dotenv/-/dotenv-17.4.2.tgz", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="],
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
@@ -799,6 +973,8 @@
"enhanced-resolve": ["enhanced-resolve@5.21.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA=="], "enhanced-resolve": ["enhanced-resolve@5.21.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA=="],
"entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
"env-paths": ["env-paths@2.2.1", "https://registry.npmmirror.com/env-paths/-/env-paths-2.2.1.tgz", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], "env-paths": ["env-paths@2.2.1", "https://registry.npmmirror.com/env-paths/-/env-paths-2.2.1.tgz", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="],
"error-ex": ["error-ex@1.3.4", "https://registry.npmmirror.com/error-ex/-/error-ex-1.3.4.tgz", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ=="], "error-ex": ["error-ex@1.3.4", "https://registry.npmmirror.com/error-ex/-/error-ex-1.3.4.tgz", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ=="],
@@ -811,6 +987,8 @@
"es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],
"es-toolkit": ["es-toolkit@1.49.0", "", {}, "sha512-G5iZ6Pc/FNRY/soKZHC+TxGDD83rHUDXxzaWhGCX44vAv/tMs56WMusnm/KMNK+luUPsgA9U28cGr4RDlSzL2g=="],
"esbuild": ["esbuild@0.28.1", "", { "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=="], "esbuild": ["esbuild@0.28.1", "", { "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=="],
"escalade": ["escalade@3.2.0", "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], "escalade": ["escalade@3.2.0", "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
@@ -821,6 +999,8 @@
"esprima": ["esprima@4.0.1", "https://registry.npmmirror.com/esprima/-/esprima-4.0.1.tgz", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], "esprima": ["esprima@4.0.1", "https://registry.npmmirror.com/esprima/-/esprima-4.0.1.tgz", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="],
"estree-util-is-identifier-name": ["estree-util-is-identifier-name@3.0.0", "", {}, "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg=="],
"etag": ["etag@1.8.1", "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="], "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": ["eventsource@3.0.7", "https://registry.npmmirror.com/eventsource/-/eventsource-3.0.7.tgz", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="],
@@ -913,14 +1093,26 @@
"graphql": ["graphql@16.14.0", "https://registry.npmmirror.com/graphql/-/graphql-16.14.0.tgz", {}, "sha512-BBvQ/406p+4CZbTpCbVPSxfzrZrbnuWSP1ELYgyS6B+hNeKzgrdB4JczCa5VZUBQrDa9hUngm0KnexY6pJRN5Q=="], "graphql": ["graphql@16.14.0", "https://registry.npmmirror.com/graphql/-/graphql-16.14.0.tgz", {}, "sha512-BBvQ/406p+4CZbTpCbVPSxfzrZrbnuWSP1ELYgyS6B+hNeKzgrdB4JczCa5VZUBQrDa9hUngm0KnexY6pJRN5Q=="],
"hachure-fill": ["hachure-fill@0.5.2", "", {}, "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg=="],
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
"has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
"hasown": ["hasown@2.0.3", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg=="], "hasown": ["hasown@2.0.3", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg=="],
"hast-util-from-parse5": ["hast-util-from-parse5@8.0.3", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "devlop": "^1.0.0", "hastscript": "^9.0.0", "property-information": "^7.0.0", "vfile": "^6.0.0", "vfile-location": "^5.0.0", "web-namespaces": "^2.0.0" } }, "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg=="],
"hast-util-parse-selector": ["hast-util-parse-selector@2.2.5", "https://registry.npmmirror.com/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz", {}, "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ=="], "hast-util-parse-selector": ["hast-util-parse-selector@2.2.5", "https://registry.npmmirror.com/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz", {}, "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ=="],
"hast-util-raw": ["hast-util-raw@9.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "@ungap/structured-clone": "^1.0.0", "hast-util-from-parse5": "^8.0.0", "hast-util-to-parse5": "^8.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "parse5": "^7.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" } }, "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw=="],
"hast-util-sanitize": ["hast-util-sanitize@5.0.2", "", { "dependencies": { "@types/hast": "^3.0.0", "@ungap/structured-clone": "^1.0.0", "unist-util-position": "^5.0.0" } }, "sha512-3yTWghByc50aGS7JlGhk61SPenfE/p1oaFeNwkOOyrscaOkMGrcW9+Cy/QAIOBpZxP1yqDIzFMR0+Np0i0+usg=="],
"hast-util-to-jsx-runtime": ["hast-util-to-jsx-runtime@2.3.6", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "hast-util-whitespace": "^3.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "style-to-js": "^1.0.0", "unist-util-position": "^5.0.0", "vfile-message": "^4.0.0" } }, "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg=="],
"hast-util-to-parse5": ["hast-util-to-parse5@8.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" } }, "sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA=="],
"hast-util-whitespace": ["hast-util-whitespace@2.0.1", "https://registry.npmmirror.com/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz", {}, "sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng=="], "hast-util-whitespace": ["hast-util-whitespace@2.0.1", "https://registry.npmmirror.com/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz", {}, "sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng=="],
"hastscript": ["hastscript@6.0.0", "https://registry.npmmirror.com/hastscript/-/hastscript-6.0.0.tgz", { "dependencies": { "@types/hast": "^2.0.0", "comma-separated-tokens": "^1.0.0", "hast-util-parse-selector": "^2.0.0", "property-information": "^5.0.0", "space-separated-tokens": "^1.0.0" } }, "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w=="], "hastscript": ["hastscript@6.0.0", "https://registry.npmmirror.com/hastscript/-/hastscript-6.0.0.tgz", { "dependencies": { "@types/hast": "^2.0.0", "comma-separated-tokens": "^1.0.0", "hast-util-parse-selector": "^2.0.0", "property-information": "^5.0.0", "space-separated-tokens": "^1.0.0" } }, "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w=="],
@@ -933,6 +1125,10 @@
"hono": ["hono@4.12.18", "https://registry.npmmirror.com/hono/-/hono-4.12.18.tgz", {}, "sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ=="], "hono": ["hono@4.12.18", "https://registry.npmmirror.com/hono/-/hono-4.12.18.tgz", {}, "sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ=="],
"html-url-attributes": ["html-url-attributes@3.0.1", "", {}, "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ=="],
"html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="],
"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=="], "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=="],
"https-proxy-agent": ["https-proxy-agent@7.0.6", "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], "https-proxy-agent": ["https-proxy-agent@7.0.6", "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
@@ -947,10 +1143,14 @@
"import-fresh": ["import-fresh@3.3.1", "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.1.tgz", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], "import-fresh": ["import-fresh@3.3.1", "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.1.tgz", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
"import-meta-resolve": ["import-meta-resolve@4.2.0", "", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="],
"inherits": ["inherits@2.0.4", "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], "inherits": ["inherits@2.0.4", "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"inline-style-parser": ["inline-style-parser@0.1.1", "https://registry.npmmirror.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz", {}, "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q=="], "inline-style-parser": ["inline-style-parser@0.1.1", "https://registry.npmmirror.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz", {}, "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q=="],
"internmap": ["internmap@1.0.1", "", {}, "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="],
"ip-address": ["ip-address@10.2.0", "https://registry.npmmirror.com/ip-address/-/ip-address-10.2.0.tgz", {}, "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA=="], "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=="], "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=="],
@@ -1027,8 +1227,14 @@
"jsonfile": ["jsonfile@6.2.1", "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.2.1.tgz", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="], "jsonfile": ["jsonfile@6.2.1", "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.2.1.tgz", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="],
"katex": ["katex@0.16.47", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg=="],
"khroma": ["khroma@2.1.0", "", {}, "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw=="],
"kleur": ["kleur@4.1.5", "https://registry.npmmirror.com/kleur/-/kleur-4.1.5.tgz", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], "kleur": ["kleur@4.1.5", "https://registry.npmmirror.com/kleur/-/kleur-4.1.5.tgz", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
"layout-base": ["layout-base@1.0.2", "", {}, "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg=="],
"lie": ["lie@3.1.1", "", { "dependencies": { "immediate": "~3.0.5" } }, "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw=="], "lie": ["lie@3.1.1", "", { "dependencies": { "immediate": "~3.0.5" } }, "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw=="],
"lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="],
@@ -1063,6 +1269,8 @@
"log-symbols": ["log-symbols@6.0.0", "https://registry.npmmirror.com/log-symbols/-/log-symbols-6.0.0.tgz", { "dependencies": { "chalk": "^5.3.0", "is-unicode-supported": "^1.3.0" } }, "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw=="], "log-symbols": ["log-symbols@6.0.0", "https://registry.npmmirror.com/log-symbols/-/log-symbols-6.0.0.tgz", { "dependencies": { "chalk": "^5.3.0", "is-unicode-supported": "^1.3.0" } }, "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw=="],
"longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="],
"loose-envify": ["loose-envify@1.4.0", "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], "loose-envify": ["loose-envify@1.4.0", "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
"lowlight": ["lowlight@1.20.0", "https://registry.npmmirror.com/lowlight/-/lowlight-1.20.0.tgz", { "dependencies": { "fault": "^1.0.0", "highlight.js": "~10.7.0" } }, "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw=="], "lowlight": ["lowlight@1.20.0", "https://registry.npmmirror.com/lowlight/-/lowlight-1.20.0.tgz", { "dependencies": { "fault": "^1.0.0", "highlight.js": "~10.7.0" } }, "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw=="],
@@ -1073,15 +1281,43 @@
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
"markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="],
"marked": ["marked@17.0.6", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-gB0gkNafnonOw0obSTEGZTT86IuhILt2Wfx0mWH/1Au83kybTayroZ/V6nS25mN7u8ASy+5fMhgB3XPNrOZdmA=="],
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
"mdast-util-definitions": ["mdast-util-definitions@5.1.2", "https://registry.npmmirror.com/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz", { "dependencies": { "@types/mdast": "^3.0.0", "@types/unist": "^2.0.0", "unist-util-visit": "^4.0.0" } }, "sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA=="], "mdast-util-definitions": ["mdast-util-definitions@5.1.2", "https://registry.npmmirror.com/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz", { "dependencies": { "@types/mdast": "^3.0.0", "@types/unist": "^2.0.0", "unist-util-visit": "^4.0.0" } }, "sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA=="],
"mdast-util-from-markdown": ["mdast-util-from-markdown@1.3.1", "https://registry.npmmirror.com/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz", { "dependencies": { "@types/mdast": "^3.0.0", "@types/unist": "^2.0.0", "decode-named-character-reference": "^1.0.0", "mdast-util-to-string": "^3.1.0", "micromark": "^3.0.0", "micromark-util-decode-numeric-character-reference": "^1.0.0", "micromark-util-decode-string": "^1.0.0", "micromark-util-normalize-identifier": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0", "unist-util-stringify-position": "^3.0.0", "uvu": "^0.5.0" } }, "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww=="], "mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg=="],
"mdast-util-to-hast": ["mdast-util-to-hast@12.3.0", "https://registry.npmmirror.com/mdast-util-to-hast/-/mdast-util-to-hast-12.3.0.tgz", { "dependencies": { "@types/hast": "^2.0.0", "@types/mdast": "^3.0.0", "mdast-util-definitions": "^5.0.0", "micromark-util-sanitize-uri": "^1.1.0", "trim-lines": "^3.0.0", "unist-util-generated": "^2.0.0", "unist-util-position": "^4.0.0", "unist-util-visit": "^4.0.0" } }, "sha512-pits93r8PhnIoU4Vy9bjW39M2jJ6/tdHyja9rrot9uujkN7UTU9SDnE6WNJz/IGyQk3XHX6yNNtrBH6cQzm8Hw=="], "mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.3", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q=="],
"mdast-util-to-string": ["mdast-util-to-string@3.2.0", "https://registry.npmmirror.com/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", { "dependencies": { "@types/mdast": "^3.0.0" } }, "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg=="], "mdast-util-gfm": ["mdast-util-gfm@3.1.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ=="],
"mdast-util-gfm-autolink-literal": ["mdast-util-gfm-autolink-literal@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "ccount": "^2.0.0", "devlop": "^1.0.0", "mdast-util-find-and-replace": "^3.0.0", "micromark-util-character": "^2.0.0" } }, "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ=="],
"mdast-util-gfm-footnote": ["mdast-util-gfm-footnote@2.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0" } }, "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ=="],
"mdast-util-gfm-strikethrough": ["mdast-util-gfm-strikethrough@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg=="],
"mdast-util-gfm-table": ["mdast-util-gfm-table@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "markdown-table": "^3.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg=="],
"mdast-util-gfm-task-list-item": ["mdast-util-gfm-task-list-item@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ=="],
"mdast-util-mdx-expression": ["mdast-util-mdx-expression@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ=="],
"mdast-util-mdx-jsx": ["mdast-util-mdx-jsx@3.2.0", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "parse-entities": "^4.0.0", "stringify-entities": "^4.0.0", "unist-util-stringify-position": "^4.0.0", "vfile-message": "^4.0.0" } }, "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q=="],
"mdast-util-mdxjs-esm": ["mdast-util-mdxjs-esm@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg=="],
"mdast-util-phrasing": ["mdast-util-phrasing@4.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "unist-util-is": "^6.0.0" } }, "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w=="],
"mdast-util-to-hast": ["mdast-util-to-hast@13.2.1", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@ungap/structured-clone": "^1.0.0", "devlop": "^1.0.0", "micromark-util-sanitize-uri": "^2.0.0", "trim-lines": "^3.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA=="],
"mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA=="],
"mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="],
"media-typer": ["media-typer@1.1.0", "https://registry.npmmirror.com/media-typer/-/media-typer-1.1.0.tgz", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="], "media-typer": ["media-typer@1.1.0", "https://registry.npmmirror.com/media-typer/-/media-typer-1.1.0.tgz", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
@@ -1091,47 +1327,63 @@
"merge2": ["merge2@1.4.1", "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], "merge2": ["merge2@1.4.1", "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
"micromark": ["micromark@3.2.0", "https://registry.npmmirror.com/micromark/-/micromark-3.2.0.tgz", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "micromark-core-commonmark": "^1.0.1", "micromark-factory-space": "^1.0.0", "micromark-util-character": "^1.0.0", "micromark-util-chunked": "^1.0.0", "micromark-util-combine-extensions": "^1.0.0", "micromark-util-decode-numeric-character-reference": "^1.0.0", "micromark-util-encode": "^1.0.0", "micromark-util-normalize-identifier": "^1.0.0", "micromark-util-resolve-all": "^1.0.0", "micromark-util-sanitize-uri": "^1.0.0", "micromark-util-subtokenize": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.1", "uvu": "^0.5.0" } }, "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA=="], "mermaid": ["mermaid@11.16.0", "", { "dependencies": { "@braintree/sanitize-url": "^7.1.2", "@iconify/utils": "^3.0.2", "@mermaid-js/parser": "^1.2.0", "@types/d3": "^7.4.3", "@upsetjs/venn.js": "^2.0.0", "cytoscape": "^3.33.3", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-fcose": "^2.2.0", "d3": "^7.9.0", "d3-sankey": "^0.12.3", "dagre-d3-es": "7.0.14", "dayjs": "^1.11.20", "dompurify": "^3.3.3", "es-toolkit": "^1.45.1", "katex": "^0.16.45", "khroma": "^2.1.0", "marked": "^16.3.0", "roughjs": "^4.6.6", "stylis": "^4.3.6", "ts-dedent": "^2.2.0", "uuid": "^11.1.0 || ^12 || ^13 || ^14.0.0" } }, "sha512-Zvm3kbstgdpvIJPPItlL7fppIZ3kibvc1oZIGxdvk9t6UFz6flv+Jw7FtRGKwfcI8OckmH04LqG6LlS6X4B1pA=="],
"micromark-core-commonmark": ["micromark-core-commonmark@1.1.0", "https://registry.npmmirror.com/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz", { "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-factory-destination": "^1.0.0", "micromark-factory-label": "^1.0.0", "micromark-factory-space": "^1.0.0", "micromark-factory-title": "^1.0.0", "micromark-factory-whitespace": "^1.0.0", "micromark-util-character": "^1.0.0", "micromark-util-chunked": "^1.0.0", "micromark-util-classify-character": "^1.0.0", "micromark-util-html-tag-name": "^1.0.0", "micromark-util-normalize-identifier": "^1.0.0", "micromark-util-resolve-all": "^1.0.0", "micromark-util-subtokenize": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.1", "uvu": "^0.5.0" } }, "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw=="], "micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="],
"micromark-factory-destination": ["micromark-factory-destination@1.1.0", "https://registry.npmmirror.com/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz", { "dependencies": { "micromark-util-character": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg=="], "micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="],
"micromark-factory-label": ["micromark-factory-label@1.1.0", "https://registry.npmmirror.com/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz", { "dependencies": { "micromark-util-character": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0", "uvu": "^0.5.0" } }, "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w=="], "micromark-extension-gfm": ["micromark-extension-gfm@3.0.0", "", { "dependencies": { "micromark-extension-gfm-autolink-literal": "^2.0.0", "micromark-extension-gfm-footnote": "^2.0.0", "micromark-extension-gfm-strikethrough": "^2.0.0", "micromark-extension-gfm-table": "^2.0.0", "micromark-extension-gfm-tagfilter": "^2.0.0", "micromark-extension-gfm-task-list-item": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w=="],
"micromark-factory-space": ["micromark-factory-space@1.1.0", "https://registry.npmmirror.com/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", { "dependencies": { "micromark-util-character": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ=="], "micromark-extension-gfm-autolink-literal": ["micromark-extension-gfm-autolink-literal@2.1.0", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw=="],
"micromark-factory-title": ["micromark-factory-title@1.1.0", "https://registry.npmmirror.com/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz", { "dependencies": { "micromark-factory-space": "^1.0.0", "micromark-util-character": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ=="], "micromark-extension-gfm-footnote": ["micromark-extension-gfm-footnote@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw=="],
"micromark-factory-whitespace": ["micromark-factory-whitespace@1.1.0", "https://registry.npmmirror.com/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz", { "dependencies": { "micromark-factory-space": "^1.0.0", "micromark-util-character": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ=="], "micromark-extension-gfm-strikethrough": ["micromark-extension-gfm-strikethrough@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw=="],
"micromark-util-character": ["micromark-util-character@1.2.0", "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-1.2.0.tgz", { "dependencies": { "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg=="], "micromark-extension-gfm-table": ["micromark-extension-gfm-table@2.1.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg=="],
"micromark-util-chunked": ["micromark-util-chunked@1.1.0", "https://registry.npmmirror.com/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz", { "dependencies": { "micromark-util-symbol": "^1.0.0" } }, "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ=="], "micromark-extension-gfm-tagfilter": ["micromark-extension-gfm-tagfilter@2.0.0", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg=="],
"micromark-util-classify-character": ["micromark-util-classify-character@1.1.0", "https://registry.npmmirror.com/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz", { "dependencies": { "micromark-util-character": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw=="], "micromark-extension-gfm-task-list-item": ["micromark-extension-gfm-task-list-item@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw=="],
"micromark-util-combine-extensions": ["micromark-util-combine-extensions@1.1.0", "https://registry.npmmirror.com/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz", { "dependencies": { "micromark-util-chunked": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA=="], "micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="],
"micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@1.1.0", "https://registry.npmmirror.com/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz", { "dependencies": { "micromark-util-symbol": "^1.0.0" } }, "sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw=="], "micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="],
"micromark-util-decode-string": ["micromark-util-decode-string@1.1.0", "https://registry.npmmirror.com/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz", { "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-util-character": "^1.0.0", "micromark-util-decode-numeric-character-reference": "^1.0.0", "micromark-util-symbol": "^1.0.0" } }, "sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ=="], "micromark-factory-space": ["micromark-factory-space@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg=="],
"micromark-util-encode": ["micromark-util-encode@1.1.0", "https://registry.npmmirror.com/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz", {}, "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw=="], "micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="],
"micromark-util-html-tag-name": ["micromark-util-html-tag-name@1.2.0", "https://registry.npmmirror.com/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz", {}, "sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q=="], "micromark-factory-whitespace": ["micromark-factory-whitespace@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ=="],
"micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@1.1.0", "https://registry.npmmirror.com/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz", { "dependencies": { "micromark-util-symbol": "^1.0.0" } }, "sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q=="], "micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="],
"micromark-util-resolve-all": ["micromark-util-resolve-all@1.1.0", "https://registry.npmmirror.com/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz", { "dependencies": { "micromark-util-types": "^1.0.0" } }, "sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA=="], "micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="],
"micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@1.2.0", "https://registry.npmmirror.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz", { "dependencies": { "micromark-util-character": "^1.0.0", "micromark-util-encode": "^1.0.0", "micromark-util-symbol": "^1.0.0" } }, "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A=="], "micromark-util-classify-character": ["micromark-util-classify-character@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q=="],
"micromark-util-subtokenize": ["micromark-util-subtokenize@1.1.0", "https://registry.npmmirror.com/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz", { "dependencies": { "micromark-util-chunked": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0", "uvu": "^0.5.0" } }, "sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A=="], "micromark-util-combine-extensions": ["micromark-util-combine-extensions@2.0.1", "", { "dependencies": { "micromark-util-chunked": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg=="],
"micromark-util-symbol": ["micromark-util-symbol@1.1.0", "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", {}, "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag=="], "micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="],
"micromark-util-types": ["micromark-util-types@1.1.0", "https://registry.npmmirror.com/micromark-util-types/-/micromark-util-types-1.1.0.tgz", {}, "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg=="], "micromark-util-decode-string": ["micromark-util-decode-string@2.0.1", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ=="],
"micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="],
"micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="],
"micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="],
"micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="],
"micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="],
"micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="],
"micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="],
"micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="],
"micromatch": ["micromatch@4.0.8", "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], "micromatch": ["micromatch@4.0.8", "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
@@ -1191,6 +1443,8 @@
"outvariant": ["outvariant@1.4.3", "https://registry.npmmirror.com/outvariant/-/outvariant-1.4.3.tgz", {}, "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA=="], "outvariant": ["outvariant@1.4.3", "https://registry.npmmirror.com/outvariant/-/outvariant-1.4.3.tgz", {}, "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA=="],
"package-manager-detector": ["package-manager-detector@1.7.0", "", {}, "sha512-xg1eHpwYL/D/HEdWw2goFZP6vV0FH7W+PZ5rFkGjdIDLtxq7EkzBUeT3m+lndYCt8wKbmofUu1MUdMCXkCk9ZQ=="],
"parent-module": ["parent-module@1.0.1", "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], "parent-module": ["parent-module@1.0.1", "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
"parse-entities": ["parse-entities@2.0.0", "https://registry.npmmirror.com/parse-entities/-/parse-entities-2.0.0.tgz", { "dependencies": { "character-entities": "^1.0.0", "character-entities-legacy": "^1.0.0", "character-reference-invalid": "^1.0.0", "is-alphanumerical": "^1.0.0", "is-decimal": "^1.0.0", "is-hexadecimal": "^1.0.0" } }, "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ=="], "parse-entities": ["parse-entities@2.0.0", "https://registry.npmmirror.com/parse-entities/-/parse-entities-2.0.0.tgz", { "dependencies": { "character-entities": "^1.0.0", "character-entities-legacy": "^1.0.0", "character-reference-invalid": "^1.0.0", "is-alphanumerical": "^1.0.0", "is-decimal": "^1.0.0", "is-hexadecimal": "^1.0.0" } }, "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ=="],
@@ -1199,10 +1453,14 @@
"parse-ms": ["parse-ms@4.0.0", "https://registry.npmmirror.com/parse-ms/-/parse-ms-4.0.0.tgz", {}, "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw=="], "parse-ms": ["parse-ms@4.0.0", "https://registry.npmmirror.com/parse-ms/-/parse-ms-4.0.0.tgz", {}, "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw=="],
"parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="],
"parseurl": ["parseurl@1.3.3", "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], "parseurl": ["parseurl@1.3.3", "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
"path-browserify": ["path-browserify@1.0.1", "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="], "path-browserify": ["path-browserify@1.0.1", "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="],
"path-data-parser": ["path-data-parser@0.1.0", "", {}, "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w=="],
"path-key": ["path-key@3.1.1", "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], "path-key": ["path-key@3.1.1", "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
"path-parse": ["path-parse@1.0.7", "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], "path-parse": ["path-parse@1.0.7", "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
@@ -1217,6 +1475,10 @@
"pkce-challenge": ["pkce-challenge@5.0.1", "https://registry.npmmirror.com/pkce-challenge/-/pkce-challenge-5.0.1.tgz", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="], "pkce-challenge": ["pkce-challenge@5.0.1", "https://registry.npmmirror.com/pkce-challenge/-/pkce-challenge-5.0.1.tgz", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="],
"points-on-curve": ["points-on-curve@0.2.0", "", {}, "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A=="],
"points-on-path": ["points-on-path@0.2.1", "", { "dependencies": { "path-data-parser": "0.1.0", "points-on-curve": "0.2.0" } }, "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g=="],
"postcss": ["postcss@8.5.12", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA=="], "postcss": ["postcss@8.5.12", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA=="],
"postcss-selector-parser": ["postcss-selector-parser@7.1.1", "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg=="], "postcss-selector-parser": ["postcss-selector-parser@7.1.1", "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg=="],
@@ -1295,9 +1557,21 @@
"refractor": ["refractor@3.6.0", "https://registry.npmmirror.com/refractor/-/refractor-3.6.0.tgz", { "dependencies": { "hastscript": "^6.0.0", "parse-entities": "^2.0.0", "prismjs": "~1.27.0" } }, "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA=="], "refractor": ["refractor@3.6.0", "https://registry.npmmirror.com/refractor/-/refractor-3.6.0.tgz", { "dependencies": { "hastscript": "^6.0.0", "parse-entities": "^2.0.0", "prismjs": "~1.27.0" } }, "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA=="],
"remark-parse": ["remark-parse@10.0.2", "https://registry.npmmirror.com/remark-parse/-/remark-parse-10.0.2.tgz", { "dependencies": { "@types/mdast": "^3.0.0", "mdast-util-from-markdown": "^1.0.0", "unified": "^10.0.0" } }, "sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw=="], "rehype-harden": ["rehype-harden@1.1.8", "", { "dependencies": { "unist-util-visit": "^5.0.0" } }, "sha512-Qn7vR1xrf6fZCrkm9TDWi/AB4ylrHy+jqsNm1EHOAmbARYA6gsnVJBq/sdBh6kmT4NEZxH5vgIjrscefJAOXcw=="],
"remark-rehype": ["remark-rehype@10.1.0", "https://registry.npmmirror.com/remark-rehype/-/remark-rehype-10.1.0.tgz", { "dependencies": { "@types/hast": "^2.0.0", "@types/mdast": "^3.0.0", "mdast-util-to-hast": "^12.1.0", "unified": "^10.0.0" } }, "sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw=="], "rehype-raw": ["rehype-raw@7.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-raw": "^9.0.0", "vfile": "^6.0.0" } }, "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww=="],
"rehype-sanitize": ["rehype-sanitize@6.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-sanitize": "^5.0.0" } }, "sha512-CsnhKNsyI8Tub6L4sm5ZFsme4puGfc6pYylvXo1AeqaGbjOYyzNv3qZPwvs0oMJ39eryyeOdmxwUIo94IpEhqg=="],
"remark-gfm": ["remark-gfm@4.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg=="],
"remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="],
"remark-rehype": ["remark-rehype@11.1.2", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "mdast-util-to-hast": "^13.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw=="],
"remark-stringify": ["remark-stringify@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-to-markdown": "^2.0.0", "unified": "^11.0.0" } }, "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw=="],
"remend": ["remend@1.3.0", "", {}, "sha512-iIhggPkhW3hFImKtB10w0dz4EZbs28mV/dmbcYVonWEJ6UGHHpP+bFZnTh6GNWJONg5m+U56JrL+8IxZRdgWjw=="],
"require-directory": ["require-directory@2.1.1", "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], "require-directory": ["require-directory@2.1.1", "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
@@ -1315,14 +1589,20 @@
"reusify": ["reusify@1.1.0", "https://registry.npmmirror.com/reusify/-/reusify-1.1.0.tgz", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], "reusify": ["reusify@1.1.0", "https://registry.npmmirror.com/reusify/-/reusify-1.1.0.tgz", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
"robust-predicates": ["robust-predicates@3.0.3", "", {}, "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA=="],
"rollup": ["rollup@4.62.2", "", { "dependencies": { "@types/estree": "1.0.9" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.62.2", "@rollup/rollup-android-arm64": "4.62.2", "@rollup/rollup-darwin-arm64": "4.62.2", "@rollup/rollup-darwin-x64": "4.62.2", "@rollup/rollup-freebsd-arm64": "4.62.2", "@rollup/rollup-freebsd-x64": "4.62.2", "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", "@rollup/rollup-linux-arm-musleabihf": "4.62.2", "@rollup/rollup-linux-arm64-gnu": "4.62.2", "@rollup/rollup-linux-arm64-musl": "4.62.2", "@rollup/rollup-linux-loong64-gnu": "4.62.2", "@rollup/rollup-linux-loong64-musl": "4.62.2", "@rollup/rollup-linux-ppc64-gnu": "4.62.2", "@rollup/rollup-linux-ppc64-musl": "4.62.2", "@rollup/rollup-linux-riscv64-gnu": "4.62.2", "@rollup/rollup-linux-riscv64-musl": "4.62.2", "@rollup/rollup-linux-s390x-gnu": "4.62.2", "@rollup/rollup-linux-x64-gnu": "4.62.2", "@rollup/rollup-linux-x64-musl": "4.62.2", "@rollup/rollup-openbsd-x64": "4.62.2", "@rollup/rollup-openharmony-arm64": "4.62.2", "@rollup/rollup-win32-arm64-msvc": "4.62.2", "@rollup/rollup-win32-ia32-msvc": "4.62.2", "@rollup/rollup-win32-x64-gnu": "4.62.2", "@rollup/rollup-win32-x64-msvc": "4.62.2", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA=="], "rollup": ["rollup@4.62.2", "", { "dependencies": { "@types/estree": "1.0.9" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.62.2", "@rollup/rollup-android-arm64": "4.62.2", "@rollup/rollup-darwin-arm64": "4.62.2", "@rollup/rollup-darwin-x64": "4.62.2", "@rollup/rollup-freebsd-arm64": "4.62.2", "@rollup/rollup-freebsd-x64": "4.62.2", "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", "@rollup/rollup-linux-arm-musleabihf": "4.62.2", "@rollup/rollup-linux-arm64-gnu": "4.62.2", "@rollup/rollup-linux-arm64-musl": "4.62.2", "@rollup/rollup-linux-loong64-gnu": "4.62.2", "@rollup/rollup-linux-loong64-musl": "4.62.2", "@rollup/rollup-linux-ppc64-gnu": "4.62.2", "@rollup/rollup-linux-ppc64-musl": "4.62.2", "@rollup/rollup-linux-riscv64-gnu": "4.62.2", "@rollup/rollup-linux-riscv64-musl": "4.62.2", "@rollup/rollup-linux-s390x-gnu": "4.62.2", "@rollup/rollup-linux-x64-gnu": "4.62.2", "@rollup/rollup-linux-x64-musl": "4.62.2", "@rollup/rollup-openbsd-x64": "4.62.2", "@rollup/rollup-openharmony-arm64": "4.62.2", "@rollup/rollup-win32-arm64-msvc": "4.62.2", "@rollup/rollup-win32-ia32-msvc": "4.62.2", "@rollup/rollup-win32-x64-gnu": "4.62.2", "@rollup/rollup-win32-x64-msvc": "4.62.2", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA=="],
"roughjs": ["roughjs@4.6.6", "", { "dependencies": { "hachure-fill": "^0.5.2", "path-data-parser": "^0.1.0", "points-on-curve": "^0.2.0", "points-on-path": "^0.2.1" } }, "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ=="],
"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=="], "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=="],
"run-applescript": ["run-applescript@7.1.0", "https://registry.npmmirror.com/run-applescript/-/run-applescript-7.1.0.tgz", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="], "run-applescript": ["run-applescript@7.1.0", "https://registry.npmmirror.com/run-applescript/-/run-applescript-7.1.0.tgz", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="],
"run-parallel": ["run-parallel@1.2.0", "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], "run-parallel": ["run-parallel@1.2.0", "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
"rw": ["rw@1.3.3", "", {}, "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ=="],
"sade": ["sade@1.8.1", "https://registry.npmmirror.com/sade/-/sade-1.8.1.tgz", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="], "sade": ["sade@1.8.1", "https://registry.npmmirror.com/sade/-/sade-1.8.1.tgz", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="],
"safe-stable-stringify": ["safe-stable-stringify@2.5.0", "https://registry.npmmirror.com/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="], "safe-stable-stringify": ["safe-stable-stringify@2.5.0", "https://registry.npmmirror.com/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="],
@@ -1373,12 +1653,16 @@
"stdin-discarder": ["stdin-discarder@0.2.2", "https://registry.npmmirror.com/stdin-discarder/-/stdin-discarder-0.2.2.tgz", {}, "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ=="], "stdin-discarder": ["stdin-discarder@0.2.2", "https://registry.npmmirror.com/stdin-discarder/-/stdin-discarder-0.2.2.tgz", {}, "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ=="],
"streamdown": ["streamdown@2.5.0", "", { "dependencies": { "clsx": "^2.1.1", "hast-util-to-jsx-runtime": "^2.3.6", "html-url-attributes": "^3.0.1", "marked": "^17.0.1", "mermaid": "^11.12.2", "rehype-harden": "^1.1.8", "rehype-raw": "^7.0.0", "rehype-sanitize": "^6.0.0", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remend": "1.3.0", "tailwind-merge": "^3.4.0", "unified": "^11.0.5", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.0" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, "sha512-/tTnURfIOxZK/pqJAxsfCvETG/XCJHoWnk3jq9xLcuz6CSpnjjuxSRBTTL4PKGhxiZQf0lqPxGhImdpwcZ2XwA=="],
"strict-event-emitter": ["strict-event-emitter@0.5.1", "https://registry.npmmirror.com/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", {}, "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ=="], "strict-event-emitter": ["strict-event-emitter@0.5.1", "https://registry.npmmirror.com/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", {}, "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ=="],
"string-convert": ["string-convert@0.2.1", "https://registry.npmmirror.com/string-convert/-/string-convert-0.2.1.tgz", {}, "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A=="], "string-convert": ["string-convert@0.2.1", "https://registry.npmmirror.com/string-convert/-/string-convert-0.2.1.tgz", {}, "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A=="],
"string-width": ["string-width@7.2.0", "https://registry.npmmirror.com/string-width/-/string-width-7.2.0.tgz", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], "string-width": ["string-width@7.2.0", "https://registry.npmmirror.com/string-width/-/string-width-7.2.0.tgz", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
"stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="],
"stringify-object": ["stringify-object@5.0.0", "https://registry.npmmirror.com/stringify-object/-/stringify-object-5.0.0.tgz", { "dependencies": { "get-own-enumerable-keys": "^1.0.0", "is-obj": "^3.0.0", "is-regexp": "^3.1.0" } }, "sha512-zaJYxz2FtcMb4f+g60KsRNFOpVMUyuJgA51Zi5Z1DOTC3S59+OQiVOzE9GZt0x72uBGWKsQIuBKeF9iusmKFsg=="], "stringify-object": ["stringify-object@5.0.0", "https://registry.npmmirror.com/stringify-object/-/stringify-object-5.0.0.tgz", { "dependencies": { "get-own-enumerable-keys": "^1.0.0", "is-obj": "^3.0.0", "is-regexp": "^3.1.0" } }, "sha512-zaJYxz2FtcMb4f+g60KsRNFOpVMUyuJgA51Zi5Z1DOTC3S59+OQiVOzE9GZt0x72uBGWKsQIuBKeF9iusmKFsg=="],
"strip-ansi": ["strip-ansi@7.2.0", "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.2.0.tgz", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], "strip-ansi": ["strip-ansi@7.2.0", "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.2.0.tgz", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
@@ -1389,6 +1673,8 @@
"style-mod": ["style-mod@4.1.3", "", {}, "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ=="], "style-mod": ["style-mod@4.1.3", "", {}, "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ=="],
"style-to-js": ["style-to-js@1.1.21", "", { "dependencies": { "style-to-object": "1.0.14" } }, "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ=="],
"style-to-object": ["style-to-object@0.4.4", "https://registry.npmmirror.com/style-to-object/-/style-to-object-0.4.4.tgz", { "dependencies": { "inline-style-parser": "0.1.1" } }, "sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg=="], "style-to-object": ["style-to-object@0.4.4", "https://registry.npmmirror.com/style-to-object/-/style-to-object-0.4.4.tgz", { "dependencies": { "inline-style-parser": "0.1.1" } }, "sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg=="],
"stylis": ["stylis@4.4.0", "https://registry.npmmirror.com/stylis/-/stylis-4.4.0.tgz", {}, "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA=="], "stylis": ["stylis@4.4.0", "https://registry.npmmirror.com/stylis/-/stylis-4.4.0.tgz", {}, "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA=="],
@@ -1409,6 +1695,8 @@
"tiny-invariant": ["tiny-invariant@1.3.3", "https://registry.npmmirror.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="], "tiny-invariant": ["tiny-invariant@1.3.3", "https://registry.npmmirror.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="],
"tinyexec": ["tinyexec@1.2.4", "", {}, "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg=="],
"tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="], "tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="],
"tldts": ["tldts@7.0.30", "https://registry.npmmirror.com/tldts/-/tldts-7.0.30.tgz", { "dependencies": { "tldts-core": "^7.0.30" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-ELrFxuqsDdHUwoh0XxDbxuLD3Wnz49Z57IFvTtvWy1hJdcMZjXLIuonjilCiWHlT2GbE4Wlv1wKVTzDFnXH1aw=="], "tldts": ["tldts@7.0.30", "https://registry.npmmirror.com/tldts/-/tldts-7.0.30.tgz", { "dependencies": { "tldts-core": "^7.0.30" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-ELrFxuqsDdHUwoh0XxDbxuLD3Wnz49Z57IFvTtvWy1hJdcMZjXLIuonjilCiWHlT2GbE4Wlv1wKVTzDFnXH1aw=="],
@@ -1425,6 +1713,8 @@
"trough": ["trough@2.2.0", "https://registry.npmmirror.com/trough/-/trough-2.2.0.tgz", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="], "trough": ["trough@2.2.0", "https://registry.npmmirror.com/trough/-/trough-2.2.0.tgz", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="],
"ts-dedent": ["ts-dedent@2.3.0", "", {}, "sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg=="],
"ts-morph": ["ts-morph@26.0.0", "https://registry.npmmirror.com/ts-morph/-/ts-morph-26.0.0.tgz", { "dependencies": { "@ts-morph/common": "~0.27.0", "code-block-writer": "^13.0.3" } }, "sha512-ztMO++owQnz8c/gIENcM9XfCEzgoGphTv+nKpYNM1bgsdOVC/jRZuEBf6N+mLLDNg68Kl+GgUZfOySaRiG1/Ug=="], "ts-morph": ["ts-morph@26.0.0", "https://registry.npmmirror.com/ts-morph/-/ts-morph-26.0.0.tgz", { "dependencies": { "@ts-morph/common": "~0.27.0", "code-block-writer": "^13.0.3" } }, "sha512-ztMO++owQnz8c/gIENcM9XfCEzgoGphTv+nKpYNM1bgsdOVC/jRZuEBf6N+mLLDNg68Kl+GgUZfOySaRiG1/Ug=="],
"tsconfig-paths": ["tsconfig-paths@4.2.0", "https://registry.npmmirror.com/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", { "dependencies": { "json5": "^2.2.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg=="], "tsconfig-paths": ["tsconfig-paths@4.2.0", "https://registry.npmmirror.com/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", { "dependencies": { "json5": "^2.2.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg=="],
@@ -1443,19 +1733,19 @@
"unicorn-magic": ["unicorn-magic@0.3.0", "https://registry.npmmirror.com/unicorn-magic/-/unicorn-magic-0.3.0.tgz", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="], "unicorn-magic": ["unicorn-magic@0.3.0", "https://registry.npmmirror.com/unicorn-magic/-/unicorn-magic-0.3.0.tgz", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="],
"unified": ["unified@10.1.2", "https://registry.npmmirror.com/unified/-/unified-10.1.2.tgz", { "dependencies": { "@types/unist": "^2.0.0", "bail": "^2.0.0", "extend": "^3.0.0", "is-buffer": "^2.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^5.0.0" } }, "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q=="], "unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="],
"unist-util-generated": ["unist-util-generated@2.0.1", "https://registry.npmmirror.com/unist-util-generated/-/unist-util-generated-2.0.1.tgz", {}, "sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A=="], "unist-util-generated": ["unist-util-generated@2.0.1", "https://registry.npmmirror.com/unist-util-generated/-/unist-util-generated-2.0.1.tgz", {}, "sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A=="],
"unist-util-is": ["unist-util-is@5.2.1", "https://registry.npmmirror.com/unist-util-is/-/unist-util-is-5.2.1.tgz", { "dependencies": { "@types/unist": "^2.0.0" } }, "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw=="], "unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="],
"unist-util-position": ["unist-util-position@4.0.4", "https://registry.npmmirror.com/unist-util-position/-/unist-util-position-4.0.4.tgz", { "dependencies": { "@types/unist": "^2.0.0" } }, "sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg=="], "unist-util-position": ["unist-util-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA=="],
"unist-util-stringify-position": ["unist-util-stringify-position@3.0.3", "https://registry.npmmirror.com/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz", { "dependencies": { "@types/unist": "^2.0.0" } }, "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg=="], "unist-util-stringify-position": ["unist-util-stringify-position@3.0.3", "https://registry.npmmirror.com/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz", { "dependencies": { "@types/unist": "^2.0.0" } }, "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg=="],
"unist-util-visit": ["unist-util-visit@4.1.2", "https://registry.npmmirror.com/unist-util-visit/-/unist-util-visit-4.1.2.tgz", { "dependencies": { "@types/unist": "^2.0.0", "unist-util-is": "^5.0.0", "unist-util-visit-parents": "^5.1.1" } }, "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg=="], "unist-util-visit": ["unist-util-visit@5.1.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg=="],
"unist-util-visit-parents": ["unist-util-visit-parents@5.1.3", "https://registry.npmmirror.com/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz", { "dependencies": { "@types/unist": "^2.0.0", "unist-util-is": "^5.0.0" } }, "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg=="], "unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="],
"universalify": ["universalify@2.0.1", "https://registry.npmmirror.com/universalify/-/universalify-2.0.1.tgz", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], "universalify": ["universalify@2.0.1", "https://registry.npmmirror.com/universalify/-/universalify-2.0.1.tgz", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="],
@@ -1473,6 +1763,8 @@
"util-deprecate": ["util-deprecate@1.0.2", "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], "util-deprecate": ["util-deprecate@1.0.2", "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
"uuid": ["uuid@14.0.1", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew=="],
"uvu": ["uvu@0.5.6", "https://registry.npmmirror.com/uvu/-/uvu-0.5.6.tgz", { "dependencies": { "dequal": "^2.0.0", "diff": "^5.0.0", "kleur": "^4.0.3", "sade": "^1.7.3" }, "bin": { "uvu": "bin.js" } }, "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA=="], "uvu": ["uvu@0.5.6", "https://registry.npmmirror.com/uvu/-/uvu-0.5.6.tgz", { "dependencies": { "dequal": "^2.0.0", "diff": "^5.0.0", "kleur": "^4.0.3", "sade": "^1.7.3" }, "bin": { "uvu": "bin.js" } }, "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA=="],
"validate-npm-package-name": ["validate-npm-package-name@7.0.2", "https://registry.npmmirror.com/validate-npm-package-name/-/validate-npm-package-name-7.0.2.tgz", {}, "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A=="], "validate-npm-package-name": ["validate-npm-package-name@7.0.2", "https://registry.npmmirror.com/validate-npm-package-name/-/validate-npm-package-name-7.0.2.tgz", {}, "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A=="],
@@ -1481,12 +1773,16 @@
"vfile": ["vfile@5.3.7", "https://registry.npmmirror.com/vfile/-/vfile-5.3.7.tgz", { "dependencies": { "@types/unist": "^2.0.0", "is-buffer": "^2.0.0", "unist-util-stringify-position": "^3.0.0", "vfile-message": "^3.0.0" } }, "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g=="], "vfile": ["vfile@5.3.7", "https://registry.npmmirror.com/vfile/-/vfile-5.3.7.tgz", { "dependencies": { "@types/unist": "^2.0.0", "is-buffer": "^2.0.0", "unist-util-stringify-position": "^3.0.0", "vfile-message": "^3.0.0" } }, "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g=="],
"vfile-message": ["vfile-message@3.1.4", "https://registry.npmmirror.com/vfile-message/-/vfile-message-3.1.4.tgz", { "dependencies": { "@types/unist": "^2.0.0", "unist-util-stringify-position": "^3.0.0" } }, "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw=="], "vfile-location": ["vfile-location@5.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg=="],
"vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="],
"vite": ["vite@7.3.6", "", { "dependencies": { "esbuild": "^0.27.0 || ^0.28.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg=="], "vite": ["vite@7.3.6", "", { "dependencies": { "esbuild": "^0.27.0 || ^0.28.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg=="],
"w3c-keyname": ["w3c-keyname@2.2.8", "", {}, "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="], "w3c-keyname": ["w3c-keyname@2.2.8", "", {}, "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="],
"web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="],
"web-streams-polyfill": ["web-streams-polyfill@3.3.3", "https://registry.npmmirror.com/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], "web-streams-polyfill": ["web-streams-polyfill@3.3.3", "https://registry.npmmirror.com/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="],
"which": ["which@4.0.0", "https://registry.npmmirror.com/which/-/which-4.0.0.tgz", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="], "which": ["which@4.0.0", "https://registry.npmmirror.com/which/-/which-4.0.0.tgz", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="],
@@ -1519,6 +1815,8 @@
"zustand": ["zustand@5.0.12", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g=="], "zustand": ["zustand@5.0.12", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g=="],
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
"@ant-design/cssinjs-utils/@ant-design/cssinjs": ["@ant-design/cssinjs@2.1.2", "https://registry.npmmirror.com/@ant-design/cssinjs/-/cssinjs-2.1.2.tgz", { "dependencies": { "@babel/runtime": "^7.11.1", "@emotion/hash": "^0.8.0", "@emotion/unitless": "^0.7.5", "@rc-component/util": "^1.4.0", "clsx": "^2.1.1", "csstype": "^3.1.3", "stylis": "^4.3.4" }, "peerDependencies": { "react": ">=16.0.0", "react-dom": ">=16.0.0" } }, "sha512-2Hy8BnCEH31xPeSLbhhB2ctCPXE2ZnASdi+KbSeS79BNbUhL9hAEe20SkUk+BR8aKTmqb6+FKFruk7w8z0VoRQ=="], "@ant-design/cssinjs-utils/@ant-design/cssinjs": ["@ant-design/cssinjs@2.1.2", "https://registry.npmmirror.com/@ant-design/cssinjs/-/cssinjs-2.1.2.tgz", { "dependencies": { "@babel/runtime": "^7.11.1", "@emotion/hash": "^0.8.0", "@emotion/unitless": "^0.7.5", "@rc-component/util": "^1.4.0", "clsx": "^2.1.1", "csstype": "^3.1.3", "stylis": "^4.3.4" }, "peerDependencies": { "react": ">=16.0.0", "react-dom": ">=16.0.0" } }, "sha512-2Hy8BnCEH31xPeSLbhhB2ctCPXE2ZnASdi+KbSeS79BNbUhL9hAEe20SkUk+BR8aKTmqb6+FKFruk7w8z0VoRQ=="],
"@ant-design/pro-components/@ant-design/icons": ["@ant-design/icons@5.6.1", "https://registry.npmmirror.com/@ant-design/icons/-/icons-5.6.1.tgz", { "dependencies": { "@ant-design/colors": "^7.0.0", "@ant-design/icons-svg": "^4.4.0", "@babel/runtime": "^7.24.8", "classnames": "^2.2.6", "rc-util": "^5.31.1" }, "peerDependencies": { "react": ">=16.0.0", "react-dom": ">=16.0.0" } }, "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg=="], "@ant-design/pro-components/@ant-design/icons": ["@ant-design/icons@5.6.1", "https://registry.npmmirror.com/@ant-design/icons/-/icons-5.6.1.tgz", { "dependencies": { "@ant-design/colors": "^7.0.0", "@ant-design/icons-svg": "^4.4.0", "@babel/runtime": "^7.24.8", "classnames": "^2.2.6", "rc-util": "^5.31.1" }, "peerDependencies": { "react": ">=16.0.0", "react-dom": ">=16.0.0" } }, "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg=="],
@@ -1575,6 +1873,8 @@
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"@types/mdast/@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
"accepts/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=="], "accepts/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=="],
"antd/@ant-design/cssinjs": ["@ant-design/cssinjs@2.1.2", "https://registry.npmmirror.com/@ant-design/cssinjs/-/cssinjs-2.1.2.tgz", { "dependencies": { "@babel/runtime": "^7.11.1", "@emotion/hash": "^0.8.0", "@emotion/unitless": "^0.7.5", "@rc-component/util": "^1.4.0", "clsx": "^2.1.1", "csstype": "^3.1.3", "stylis": "^4.3.4" }, "peerDependencies": { "react": ">=16.0.0", "react-dom": ">=16.0.0" } }, "sha512-2Hy8BnCEH31xPeSLbhhB2ctCPXE2ZnASdi+KbSeS79BNbUhL9hAEe20SkUk+BR8aKTmqb6+FKFruk7w8z0VoRQ=="], "antd/@ant-design/cssinjs": ["@ant-design/cssinjs@2.1.2", "https://registry.npmmirror.com/@ant-design/cssinjs/-/cssinjs-2.1.2.tgz", { "dependencies": { "@babel/runtime": "^7.11.1", "@emotion/hash": "^0.8.0", "@emotion/unitless": "^0.7.5", "@rc-component/util": "^1.4.0", "clsx": "^2.1.1", "csstype": "^3.1.3", "stylis": "^4.3.4" }, "peerDependencies": { "react": ">=16.0.0", "react-dom": ">=16.0.0" } }, "sha512-2Hy8BnCEH31xPeSLbhhB2ctCPXE2ZnASdi+KbSeS79BNbUhL9hAEe20SkUk+BR8aKTmqb6+FKFruk7w8z0VoRQ=="],
@@ -1589,12 +1889,52 @@
"cross-spawn/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=="], "cross-spawn/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=="],
"cytoscape-fcose/cose-base": ["cose-base@2.2.0", "", { "dependencies": { "layout-base": "^2.0.0" } }, "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g=="],
"d3-dsv/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="],
"d3-dsv/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
"d3-sankey/d3-array": ["d3-array@2.12.1", "", { "dependencies": { "internmap": "^1.0.0" } }, "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ=="],
"d3-sankey/d3-shape": ["d3-shape@1.3.7", "", { "dependencies": { "d3-path": "1" } }, "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw=="],
"decode-named-character-reference/character-entities": ["character-entities@2.0.2", "https://registry.npmmirror.com/character-entities/-/character-entities-2.0.2.tgz", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="], "decode-named-character-reference/character-entities": ["character-entities@2.0.2", "https://registry.npmmirror.com/character-entities/-/character-entities-2.0.2.tgz", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="],
"express/cookie": ["cookie@0.7.2", "https://registry.npmmirror.com/cookie/-/cookie-0.7.2.tgz", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], "express/cookie": ["cookie@0.7.2", "https://registry.npmmirror.com/cookie/-/cookie-0.7.2.tgz", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
"express/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=="], "express/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=="],
"hast-util-from-parse5/@types/hast": ["@types/hast@3.0.5", "", { "dependencies": { "@types/unist": "*" } }, "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g=="],
"hast-util-from-parse5/@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
"hast-util-from-parse5/hastscript": ["hastscript@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w=="],
"hast-util-from-parse5/property-information": ["property-information@7.2.0", "", {}, "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg=="],
"hast-util-from-parse5/vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="],
"hast-util-raw/@types/hast": ["@types/hast@3.0.5", "", { "dependencies": { "@types/unist": "*" } }, "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g=="],
"hast-util-raw/@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
"hast-util-raw/vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="],
"hast-util-sanitize/@types/hast": ["@types/hast@3.0.5", "", { "dependencies": { "@types/unist": "*" } }, "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g=="],
"hast-util-to-jsx-runtime/@types/hast": ["@types/hast@3.0.5", "", { "dependencies": { "@types/unist": "*" } }, "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g=="],
"hast-util-to-jsx-runtime/@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
"hast-util-to-jsx-runtime/hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="],
"hast-util-to-jsx-runtime/property-information": ["property-information@7.2.0", "", {}, "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg=="],
"hast-util-to-parse5/@types/hast": ["@types/hast@3.0.5", "", { "dependencies": { "@types/unist": "*" } }, "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g=="],
"hast-util-to-parse5/property-information": ["property-information@7.2.0", "", {}, "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg=="],
"hastscript/comma-separated-tokens": ["comma-separated-tokens@1.0.8", "https://registry.npmmirror.com/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz", {}, "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw=="], "hastscript/comma-separated-tokens": ["comma-separated-tokens@1.0.8", "https://registry.npmmirror.com/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz", {}, "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw=="],
"hastscript/property-information": ["property-information@5.6.0", "https://registry.npmmirror.com/property-information/-/property-information-5.6.0.tgz", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA=="], "hastscript/property-information": ["property-information@5.6.0", "https://registry.npmmirror.com/property-information/-/property-information-5.6.0.tgz", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA=="],
@@ -1603,8 +1943,40 @@
"headers-polyfill/set-cookie-parser": ["set-cookie-parser@3.1.0", "https://registry.npmmirror.com/set-cookie-parser/-/set-cookie-parser-3.1.0.tgz", {}, "sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw=="], "headers-polyfill/set-cookie-parser": ["set-cookie-parser@3.1.0", "https://registry.npmmirror.com/set-cookie-parser/-/set-cookie-parser-3.1.0.tgz", {}, "sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw=="],
"katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="],
"log-symbols/is-unicode-supported": ["is-unicode-supported@1.3.0", "https://registry.npmmirror.com/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", {}, "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="], "log-symbols/is-unicode-supported": ["is-unicode-supported@1.3.0", "https://registry.npmmirror.com/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", {}, "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="],
"mdast-util-definitions/@types/mdast": ["@types/mdast@3.0.15", "https://registry.npmmirror.com/@types/mdast/-/mdast-3.0.15.tgz", { "dependencies": { "@types/unist": "^2" } }, "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ=="],
"mdast-util-definitions/unist-util-visit": ["unist-util-visit@4.1.2", "https://registry.npmmirror.com/unist-util-visit/-/unist-util-visit-4.1.2.tgz", { "dependencies": { "@types/unist": "^2.0.0", "unist-util-is": "^5.0.0", "unist-util-visit-parents": "^5.1.1" } }, "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg=="],
"mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
"mdast-util-from-markdown/@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
"mdast-util-from-markdown/unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="],
"mdast-util-mdx-expression/@types/hast": ["@types/hast@3.0.5", "", { "dependencies": { "@types/unist": "*" } }, "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g=="],
"mdast-util-mdx-jsx/@types/hast": ["@types/hast@3.0.5", "", { "dependencies": { "@types/unist": "*" } }, "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g=="],
"mdast-util-mdx-jsx/@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
"mdast-util-mdx-jsx/parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="],
"mdast-util-mdx-jsx/unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="],
"mdast-util-mdxjs-esm/@types/hast": ["@types/hast@3.0.5", "", { "dependencies": { "@types/unist": "*" } }, "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g=="],
"mdast-util-to-hast/@types/hast": ["@types/hast@3.0.5", "", { "dependencies": { "@types/unist": "*" } }, "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g=="],
"mdast-util-to-hast/vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="],
"mdast-util-to-markdown/@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
"mermaid/marked": ["marked@16.4.2", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA=="],
"micromatch/picomatch": ["picomatch@2.3.2", "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.2.tgz", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], "micromatch/picomatch": ["picomatch@2.3.2", "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.2.tgz", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
"npm-run-path/path-key": ["path-key@4.0.0", "https://registry.npmmirror.com/path-key/-/path-key-4.0.0.tgz", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], "npm-run-path/path-key": ["path-key@4.0.0", "https://registry.npmmirror.com/path-key/-/path-key-4.0.0.tgz", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
@@ -1615,20 +1987,64 @@
"prop-types/react-is": ["react-is@16.13.1", "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], "prop-types/react-is": ["react-is@16.13.1", "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
"react-markdown/remark-parse": ["remark-parse@10.0.2", "https://registry.npmmirror.com/remark-parse/-/remark-parse-10.0.2.tgz", { "dependencies": { "@types/mdast": "^3.0.0", "mdast-util-from-markdown": "^1.0.0", "unified": "^10.0.0" } }, "sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw=="],
"react-markdown/remark-rehype": ["remark-rehype@10.1.0", "https://registry.npmmirror.com/remark-rehype/-/remark-rehype-10.1.0.tgz", { "dependencies": { "@types/hast": "^2.0.0", "@types/mdast": "^3.0.0", "mdast-util-to-hast": "^12.1.0", "unified": "^10.0.0" } }, "sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw=="],
"react-markdown/unified": ["unified@10.1.2", "https://registry.npmmirror.com/unified/-/unified-10.1.2.tgz", { "dependencies": { "@types/unist": "^2.0.0", "bail": "^2.0.0", "extend": "^3.0.0", "is-buffer": "^2.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^5.0.0" } }, "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q=="],
"react-markdown/unist-util-visit": ["unist-util-visit@4.1.2", "https://registry.npmmirror.com/unist-util-visit/-/unist-util-visit-4.1.2.tgz", { "dependencies": { "@types/unist": "^2.0.0", "unist-util-is": "^5.0.0", "unist-util-visit-parents": "^5.1.1" } }, "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg=="],
"refractor/prismjs": ["prismjs@1.27.0", "https://registry.npmmirror.com/prismjs/-/prismjs-1.27.0.tgz", {}, "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA=="], "refractor/prismjs": ["prismjs@1.27.0", "https://registry.npmmirror.com/prismjs/-/prismjs-1.27.0.tgz", {}, "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA=="],
"rehype-raw/@types/hast": ["@types/hast@3.0.5", "", { "dependencies": { "@types/unist": "*" } }, "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g=="],
"rehype-raw/vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="],
"rehype-sanitize/@types/hast": ["@types/hast@3.0.5", "", { "dependencies": { "@types/unist": "*" } }, "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g=="],
"remark-rehype/@types/hast": ["@types/hast@3.0.5", "", { "dependencies": { "@types/unist": "*" } }, "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g=="],
"remark-rehype/vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="],
"restore-cursor/onetime": ["onetime@7.0.0", "https://registry.npmmirror.com/onetime/-/onetime-7.0.0.tgz", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], "restore-cursor/onetime": ["onetime@7.0.0", "https://registry.npmmirror.com/onetime/-/onetime-7.0.0.tgz", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="],
"router/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=="], "router/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=="],
"send/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=="], "send/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=="],
"stringify-entities/character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="],
"style-to-js/style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="],
"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=="], "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=="],
"type-is/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=="], "type-is/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=="],
"unified/@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
"unified/vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="],
"unist-util-is/@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
"unist-util-position/@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
"unist-util-visit/@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
"unist-util-visit-parents/@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
"uvu/diff": ["diff@5.2.2", "https://registry.npmmirror.com/diff/-/diff-5.2.2.tgz", {}, "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A=="], "uvu/diff": ["diff@5.2.2", "https://registry.npmmirror.com/diff/-/diff-5.2.2.tgz", {}, "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A=="],
"vfile/vfile-message": ["vfile-message@3.1.4", "https://registry.npmmirror.com/vfile-message/-/vfile-message-3.1.4.tgz", { "dependencies": { "@types/unist": "^2.0.0", "unist-util-stringify-position": "^3.0.0" } }, "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw=="],
"vfile-location/@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
"vfile-location/vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="],
"vfile-message/@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
"vfile-message/unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="],
"wrap-ansi/string-width": ["string-width@4.2.3", "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], "wrap-ansi/string-width": ["string-width@4.2.3", "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
@@ -1657,10 +2073,68 @@
"cross-spawn/which/isexe": ["isexe@2.0.0", "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], "cross-spawn/which/isexe": ["isexe@2.0.0", "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
"cytoscape-fcose/cose-base/layout-base": ["layout-base@2.0.1", "", {}, "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg=="],
"d3-sankey/d3-shape/d3-path": ["d3-path@1.0.9", "", {}, "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="],
"express/mime-types/mime-db": ["mime-db@1.54.0", "https://registry.npmmirror.com/mime-db/-/mime-db-1.54.0.tgz", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], "express/mime-types/mime-db": ["mime-db@1.54.0", "https://registry.npmmirror.com/mime-db/-/mime-db-1.54.0.tgz", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
"hast-util-from-parse5/hastscript/hast-util-parse-selector": ["hast-util-parse-selector@4.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A=="],
"hast-util-sanitize/@types/hast/@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
"hast-util-to-parse5/@types/hast/@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
"mdast-util-definitions/unist-util-visit/unist-util-is": ["unist-util-is@5.2.1", "https://registry.npmmirror.com/unist-util-is/-/unist-util-is-5.2.1.tgz", { "dependencies": { "@types/unist": "^2.0.0" } }, "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw=="],
"mdast-util-definitions/unist-util-visit/unist-util-visit-parents": ["unist-util-visit-parents@5.1.3", "https://registry.npmmirror.com/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz", { "dependencies": { "@types/unist": "^2.0.0", "unist-util-is": "^5.0.0" } }, "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg=="],
"mdast-util-mdx-expression/@types/hast/@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
"mdast-util-mdx-jsx/parse-entities/@types/unist": ["@types/unist@2.0.11", "https://registry.npmmirror.com/@types/unist/-/unist-2.0.11.tgz", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
"mdast-util-mdx-jsx/parse-entities/character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="],
"mdast-util-mdx-jsx/parse-entities/character-reference-invalid": ["character-reference-invalid@2.0.1", "", {}, "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw=="],
"mdast-util-mdx-jsx/parse-entities/is-alphanumerical": ["is-alphanumerical@2.0.1", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw=="],
"mdast-util-mdx-jsx/parse-entities/is-decimal": ["is-decimal@2.0.1", "", {}, "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="],
"mdast-util-mdx-jsx/parse-entities/is-hexadecimal": ["is-hexadecimal@2.0.1", "", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="],
"mdast-util-mdxjs-esm/@types/hast/@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
"mdast-util-to-hast/@types/hast/@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
"mdast-util-to-hast/vfile/@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
"react-markdown/remark-parse/@types/mdast": ["@types/mdast@3.0.15", "https://registry.npmmirror.com/@types/mdast/-/mdast-3.0.15.tgz", { "dependencies": { "@types/unist": "^2" } }, "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ=="],
"react-markdown/remark-parse/mdast-util-from-markdown": ["mdast-util-from-markdown@1.3.1", "https://registry.npmmirror.com/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz", { "dependencies": { "@types/mdast": "^3.0.0", "@types/unist": "^2.0.0", "decode-named-character-reference": "^1.0.0", "mdast-util-to-string": "^3.1.0", "micromark": "^3.0.0", "micromark-util-decode-numeric-character-reference": "^1.0.0", "micromark-util-decode-string": "^1.0.0", "micromark-util-normalize-identifier": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0", "unist-util-stringify-position": "^3.0.0", "uvu": "^0.5.0" } }, "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww=="],
"react-markdown/remark-rehype/@types/mdast": ["@types/mdast@3.0.15", "https://registry.npmmirror.com/@types/mdast/-/mdast-3.0.15.tgz", { "dependencies": { "@types/unist": "^2" } }, "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ=="],
"react-markdown/remark-rehype/mdast-util-to-hast": ["mdast-util-to-hast@12.3.0", "https://registry.npmmirror.com/mdast-util-to-hast/-/mdast-util-to-hast-12.3.0.tgz", { "dependencies": { "@types/hast": "^2.0.0", "@types/mdast": "^3.0.0", "mdast-util-definitions": "^5.0.0", "micromark-util-sanitize-uri": "^1.1.0", "trim-lines": "^3.0.0", "unist-util-generated": "^2.0.0", "unist-util-position": "^4.0.0", "unist-util-visit": "^4.0.0" } }, "sha512-pits93r8PhnIoU4Vy9bjW39M2jJ6/tdHyja9rrot9uujkN7UTU9SDnE6WNJz/IGyQk3XHX6yNNtrBH6cQzm8Hw=="],
"react-markdown/unist-util-visit/unist-util-is": ["unist-util-is@5.2.1", "https://registry.npmmirror.com/unist-util-is/-/unist-util-is-5.2.1.tgz", { "dependencies": { "@types/unist": "^2.0.0" } }, "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw=="],
"react-markdown/unist-util-visit/unist-util-visit-parents": ["unist-util-visit-parents@5.1.3", "https://registry.npmmirror.com/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz", { "dependencies": { "@types/unist": "^2.0.0", "unist-util-is": "^5.0.0" } }, "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg=="],
"rehype-raw/@types/hast/@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
"rehype-raw/vfile/@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
"rehype-sanitize/@types/hast/@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
"remark-rehype/@types/hast/@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
"remark-rehype/vfile/@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
"send/mime-types/mime-db": ["mime-db@1.54.0", "https://registry.npmmirror.com/mime-db/-/mime-db-1.54.0.tgz", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], "send/mime-types/mime-db": ["mime-db@1.54.0", "https://registry.npmmirror.com/mime-db/-/mime-db-1.54.0.tgz", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
"style-to-js/style-to-object/inline-style-parser": ["inline-style-parser@0.2.7", "", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="],
"type-is/mime-types/mime-db": ["mime-db@1.54.0", "https://registry.npmmirror.com/mime-db/-/mime-db-1.54.0.tgz", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], "type-is/mime-types/mime-db": ["mime-db@1.54.0", "https://registry.npmmirror.com/mime-db/-/mime-db-1.54.0.tgz", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
"wrap-ansi/string-width/emoji-regex": ["emoji-regex@8.0.0", "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "wrap-ansi/string-width/emoji-regex": ["emoji-regex@8.0.0", "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
@@ -1673,6 +2147,66 @@
"@ant-design/pro-components/@ant-design/icons/@ant-design/colors/@ant-design/fast-color": ["@ant-design/fast-color@2.0.6", "https://registry.npmmirror.com/@ant-design/fast-color/-/fast-color-2.0.6.tgz", { "dependencies": { "@babel/runtime": "^7.24.7" } }, "sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA=="], "@ant-design/pro-components/@ant-design/icons/@ant-design/colors/@ant-design/fast-color": ["@ant-design/fast-color@2.0.6", "https://registry.npmmirror.com/@ant-design/fast-color/-/fast-color-2.0.6.tgz", { "dependencies": { "@babel/runtime": "^7.24.7" } }, "sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA=="],
"mdast-util-mdx-jsx/parse-entities/is-alphanumerical/is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="],
"react-markdown/remark-parse/mdast-util-from-markdown/mdast-util-to-string": ["mdast-util-to-string@3.2.0", "https://registry.npmmirror.com/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", { "dependencies": { "@types/mdast": "^3.0.0" } }, "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg=="],
"react-markdown/remark-parse/mdast-util-from-markdown/micromark": ["micromark@3.2.0", "https://registry.npmmirror.com/micromark/-/micromark-3.2.0.tgz", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "micromark-core-commonmark": "^1.0.1", "micromark-factory-space": "^1.0.0", "micromark-util-character": "^1.0.0", "micromark-util-chunked": "^1.0.0", "micromark-util-combine-extensions": "^1.0.0", "micromark-util-decode-numeric-character-reference": "^1.0.0", "micromark-util-encode": "^1.0.0", "micromark-util-normalize-identifier": "^1.0.0", "micromark-util-resolve-all": "^1.0.0", "micromark-util-sanitize-uri": "^1.0.0", "micromark-util-subtokenize": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.1", "uvu": "^0.5.0" } }, "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA=="],
"react-markdown/remark-parse/mdast-util-from-markdown/micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@1.1.0", "https://registry.npmmirror.com/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz", { "dependencies": { "micromark-util-symbol": "^1.0.0" } }, "sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw=="],
"react-markdown/remark-parse/mdast-util-from-markdown/micromark-util-decode-string": ["micromark-util-decode-string@1.1.0", "https://registry.npmmirror.com/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz", { "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-util-character": "^1.0.0", "micromark-util-decode-numeric-character-reference": "^1.0.0", "micromark-util-symbol": "^1.0.0" } }, "sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ=="],
"react-markdown/remark-parse/mdast-util-from-markdown/micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@1.1.0", "https://registry.npmmirror.com/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz", { "dependencies": { "micromark-util-symbol": "^1.0.0" } }, "sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q=="],
"react-markdown/remark-parse/mdast-util-from-markdown/micromark-util-symbol": ["micromark-util-symbol@1.1.0", "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", {}, "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag=="],
"react-markdown/remark-parse/mdast-util-from-markdown/micromark-util-types": ["micromark-util-types@1.1.0", "https://registry.npmmirror.com/micromark-util-types/-/micromark-util-types-1.1.0.tgz", {}, "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg=="],
"react-markdown/remark-rehype/mdast-util-to-hast/micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@1.2.0", "https://registry.npmmirror.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz", { "dependencies": { "micromark-util-character": "^1.0.0", "micromark-util-encode": "^1.0.0", "micromark-util-symbol": "^1.0.0" } }, "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A=="],
"react-markdown/remark-rehype/mdast-util-to-hast/unist-util-position": ["unist-util-position@4.0.4", "https://registry.npmmirror.com/unist-util-position/-/unist-util-position-4.0.4.tgz", { "dependencies": { "@types/unist": "^2.0.0" } }, "sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg=="],
"yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"react-markdown/remark-parse/mdast-util-from-markdown/micromark/micromark-core-commonmark": ["micromark-core-commonmark@1.1.0", "https://registry.npmmirror.com/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz", { "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-factory-destination": "^1.0.0", "micromark-factory-label": "^1.0.0", "micromark-factory-space": "^1.0.0", "micromark-factory-title": "^1.0.0", "micromark-factory-whitespace": "^1.0.0", "micromark-util-character": "^1.0.0", "micromark-util-chunked": "^1.0.0", "micromark-util-classify-character": "^1.0.0", "micromark-util-html-tag-name": "^1.0.0", "micromark-util-normalize-identifier": "^1.0.0", "micromark-util-resolve-all": "^1.0.0", "micromark-util-subtokenize": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.1", "uvu": "^0.5.0" } }, "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw=="],
"react-markdown/remark-parse/mdast-util-from-markdown/micromark/micromark-factory-space": ["micromark-factory-space@1.1.0", "https://registry.npmmirror.com/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", { "dependencies": { "micromark-util-character": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ=="],
"react-markdown/remark-parse/mdast-util-from-markdown/micromark/micromark-util-character": ["micromark-util-character@1.2.0", "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-1.2.0.tgz", { "dependencies": { "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg=="],
"react-markdown/remark-parse/mdast-util-from-markdown/micromark/micromark-util-chunked": ["micromark-util-chunked@1.1.0", "https://registry.npmmirror.com/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz", { "dependencies": { "micromark-util-symbol": "^1.0.0" } }, "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ=="],
"react-markdown/remark-parse/mdast-util-from-markdown/micromark/micromark-util-combine-extensions": ["micromark-util-combine-extensions@1.1.0", "https://registry.npmmirror.com/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz", { "dependencies": { "micromark-util-chunked": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA=="],
"react-markdown/remark-parse/mdast-util-from-markdown/micromark/micromark-util-encode": ["micromark-util-encode@1.1.0", "https://registry.npmmirror.com/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz", {}, "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw=="],
"react-markdown/remark-parse/mdast-util-from-markdown/micromark/micromark-util-resolve-all": ["micromark-util-resolve-all@1.1.0", "https://registry.npmmirror.com/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz", { "dependencies": { "micromark-util-types": "^1.0.0" } }, "sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA=="],
"react-markdown/remark-parse/mdast-util-from-markdown/micromark/micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@1.2.0", "https://registry.npmmirror.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz", { "dependencies": { "micromark-util-character": "^1.0.0", "micromark-util-encode": "^1.0.0", "micromark-util-symbol": "^1.0.0" } }, "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A=="],
"react-markdown/remark-parse/mdast-util-from-markdown/micromark/micromark-util-subtokenize": ["micromark-util-subtokenize@1.1.0", "https://registry.npmmirror.com/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz", { "dependencies": { "micromark-util-chunked": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0", "uvu": "^0.5.0" } }, "sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A=="],
"react-markdown/remark-parse/mdast-util-from-markdown/micromark-util-decode-string/micromark-util-character": ["micromark-util-character@1.2.0", "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-1.2.0.tgz", { "dependencies": { "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg=="],
"react-markdown/remark-rehype/mdast-util-to-hast/micromark-util-sanitize-uri/micromark-util-character": ["micromark-util-character@1.2.0", "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-1.2.0.tgz", { "dependencies": { "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg=="],
"react-markdown/remark-rehype/mdast-util-to-hast/micromark-util-sanitize-uri/micromark-util-encode": ["micromark-util-encode@1.1.0", "https://registry.npmmirror.com/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz", {}, "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw=="],
"react-markdown/remark-rehype/mdast-util-to-hast/micromark-util-sanitize-uri/micromark-util-symbol": ["micromark-util-symbol@1.1.0", "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", {}, "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag=="],
"react-markdown/remark-parse/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-destination": ["micromark-factory-destination@1.1.0", "https://registry.npmmirror.com/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz", { "dependencies": { "micromark-util-character": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg=="],
"react-markdown/remark-parse/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-label": ["micromark-factory-label@1.1.0", "https://registry.npmmirror.com/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz", { "dependencies": { "micromark-util-character": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0", "uvu": "^0.5.0" } }, "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w=="],
"react-markdown/remark-parse/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-title": ["micromark-factory-title@1.1.0", "https://registry.npmmirror.com/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz", { "dependencies": { "micromark-factory-space": "^1.0.0", "micromark-util-character": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ=="],
"react-markdown/remark-parse/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-whitespace": ["micromark-factory-whitespace@1.1.0", "https://registry.npmmirror.com/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz", { "dependencies": { "micromark-factory-space": "^1.0.0", "micromark-util-character": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ=="],
"react-markdown/remark-parse/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-util-classify-character": ["micromark-util-classify-character@1.1.0", "https://registry.npmmirror.com/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz", { "dependencies": { "micromark-util-character": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw=="],
"react-markdown/remark-parse/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-util-html-tag-name": ["micromark-util-html-tag-name@1.2.0", "https://registry.npmmirror.com/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz", {}, "sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q=="],
"react-markdown/remark-rehype/mdast-util-to-hast/micromark-util-sanitize-uri/micromark-util-character/micromark-util-types": ["micromark-util-types@1.1.0", "https://registry.npmmirror.com/micromark-util-types/-/micromark-util-types-1.1.0.tgz", {}, "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg=="],
} }
} }
+22
View File
@@ -0,0 +1,22 @@
#!/bin/sh
set -e
# 由 nginx 官方镜像的入口在启动前自动执行(/docker-entrypoint.d/*.sh),随后 nginx 正常拉起。
# 从环境变量生成运行期配置 config.js;每家统计一个独立变量,未设置的留空,
# 前端据此判定该家「关闭」,不加载对应脚本、不发外部请求。可同时启用多家。
# GA4 / 百度 ID 只含字母、数字和连字符;过滤掉其它字符,
# 避免值里的引号等破坏 config.js 的 JS 字符串(纵深防御)。
sanitize_id() {
printf '%s' "$1" | tr -cd 'A-Za-z0-9-'
}
GA4_ID=$(sanitize_id "${ANALYTICS_GA4_ID:-}")
BAIDU_ID=$(sanitize_id "${ANALYTICS_BAIDU_ID:-}")
cat > /usr/share/nginx/html/config.js <<EOF
window.__RUNTIME_CONFIG__ = {
ANALYTICS_GA4_ID: "${GA4_ID}",
ANALYTICS_BAIDU_ID: "${BAIDU_ID}"
};
EOF

Some files were not shown because too many files have changed in this diff Show More